python核心编程--第十三章

博客专区 >  fzyz_sb的博客 > 博客详情
python核心编程--第十三章
fzyz_sb 发表于4年前
 
 
 
python核心编程--第十三章
 
  • 发表于 4年前 
  • 阅读 4679 
  • 收藏 16 
  • 点赞 2 
  • 评论 9

13.1 介绍

https://my.oschina.net/voler/blog/138242

类与实例

类与实例相互关联着:类是对象的定义,而实例是"真正的实物",它存放了类中所定义的对象
的具体信息。

>>> class MyData():
	pass

>>> mathObj = MyData()
>>> mathObj.x = 4
>>> mathObj.y = 5
>>> mathObj.x + mathObj.y
9
>>> mathObj.x * mathObj.y
20
这里注意一点是:x,y不是类MyData的属性,它们是实例对象mathObj的独有属性,并且是动态的:你不需要在构造器中,或其它任何地方为它们预先声明或者赋值。

方法

方法为类的属性,除了静态方法(个人理解为静态方法为函数),方法必须有实例来调用。

>>> class MyDataWithMethod(object):
	def printFoo(self):
		print "you invoked printFoo()!"

		
>>> oneObj = MyDataWithMethod()
>>> oneObj.printFoo()
you invoked printFoo()!
特殊的参数:self。参数self恰恰表明了方法是类的属性,方式的调用通过self来关联到类中。

我们来看一个稍微完整的类的定义及其应用:

类的定义:

>>> class AddrBookEntry(object):
	"address book entry class"
	def __init__(self, nm, ph):
		self.name = nm
		self.phone = ph
		print "created instance for:", self.name
	def updatePhone(self, newph):
		self.phone = newph
		print "updated phone# for:", self.name
创建实例(实例化)
>>> john = AddrBookEntry("john doe","408-555-1212")
created instance for: john doe
>>> jane = AddrBookEntry("jane doe","650-555-1212")
created instance for: jane doe
你会发现,当创建实例的时候,__init__会自动被调用

访问实例属性

>>> john
<__main__.AddrBookEntry object at 0x020D8190>
>>> john.name
'john doe'
>>> john.phone
'408-555-1212'
>>> jane.name
'jane doe'
>>> jane.phone
'650-555-1212'
方法调用(通过实例)
>>> john.updatePhone("415-555-1212")
updated phone# for: john doe
>>> john.phone
'415-555-1212'
创建子类

靠继承来进行子类化是创建和定制新类类型的一种方式,新的类将保持已存在类所有的特性,而不会改动原来类的定义。并且子类可以定制自己的方法。

扫描二维码关注公众号,回复: 1144231 查看本文章
>>> class EmplAddrBookEntry(AddrBookEntry):
	"employee address book entry class"
	def __init__(self, nm, ph, id, em):
		AddrBookEntry.__init__(self, nm, ph)
		self.empid = id
		self.email = em
	def updateEmail(self, newem):
		self.email = newem
		print "updated e-mail address for:", self.name
使用子类
>>> john = EmplAddrBookEntry("john doe","408-555-1212", 42, "[email protected]")
created instance for: john doe
>>> john
<__main__.EmplAddrBookEntry object at 0x02115FD0>
>>> john.name
'john doe'
>>> john.phone
'408-555-1212'
>>> john.email
'[email protected]'
>>> john.updatePhone("415-555-1212")
updated phone# for: john doe
>>> john.phone
'415-555-1212'
>>> john.updateEmail("[email protected]")
updated e-mail address for: john doe
>>> john.email
'[email protected]'

13.2 面向对象编程

常用术语

抽象/实现

抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于描绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。对某种抽象的实现就是对此数据及与之相关接口的现实化。现实化这个过程对于客户程序应当是透明而且无关的。

封装/接口

封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。由于python中,所有的类属性都是公开的,所以在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。

合成

合成扩充了对类的描述,使得多个不同的类合成为一个大的类,来解决现实问题。

派生/继承/继承结构

派生描述了子类的创建,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。继承描述了子类属性从祖先类继承这样一种方式。

泛化/特化

泛化表示所有子类与其父类及祖先类有一样的特定。而特化描述所有子类的自定义,即什么属性让它与其祖先类不同。

多态

多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需要考虑他们具体的类。多态表明了动态绑定的存在,允许重载及运行时类型确定和验证。

自省/反射

表明程序员运行期检查的能力。


13.3 类

13.3.1 创建类

>>> class ClassName(object):
	"class documentation string"
	pass
基类是一个或多个用于继承的父类的集合;类体由所有声明语句,类成员定义,数据属性和函数组成。类通常在一个模块的顶层进行定义,以便类实例能够在类所定义的源代码文件中的任何地方被创建。

在python中有点特别的重要:声明和定义是一起的。


13.4 类属性

属性就是属于另一个对象的数据或者函数元素,可以通过我们熟悉的句点属性标识来访问。一些python类型比如复数有数据属性(实部和虚部),而列表和字典,拥有方法(函数属性)

关于属性,有个有趣的地方是:当你访问一个属性时,它同时也是一个对象,拥有它自己的属性,可以访问,这导致一个属性链:

sys.stdout.write("foo")
print myModule.myClass.__doc__
myList.extend(map(upper, open("x").readlines()))
13.4.1 类的数据属性和方法

数据属性仅仅是所定义的类的变量。它们可以像任何其它变量一样在类创建后被使用,并且被更新。在C++或者Java中就是静态变量。数据属性表示这些数据是与它们所属的类对象绑定的,不依赖于任何类实例。

>>> class C(object):
	foo = 100

	
>>> C.foo
100
>>> C.foo += 1
>>> C.foo
101
方法仅仅是一个作为类定义一部分定义的函数(这使得方法成为类属性),并且只能通过实例来调用(我并没有把静态方法和类方法当作方法,仅仅认为它们只是函数---独立于类对象)
>>> class MyClass(object):
	def myMethod(self):
		print "hello world"

		
>>> oneObj = MyClass()
>>> oneObj.myMethod()
hello world
>>> MyClass.myMethod()

Traceback (most recent call last):
  File "<pyshell#69>", line 1, in <module>
    MyClass.myMethod()
TypeError: unbound method myMethod() must be called with MyClass instance as first argument (got nothing instead)
13.4.2 决定类的属性

我们可以使用方法来查看类的属性:dir()和__dict__

>>> class MyClass(object):
	"myclass class definition"
	myVersion = "1.1"
	def showMyVersion(self):
		print MyClass.myVersion

		
>>> dir(MyClass)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'myVersion', 'showMyVersion']
>>> MyClass.__dict__
dict_proxy({'__module__': '__main__', 'showMyVersion': <function showMyVersion at 0x02114C70>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, 'myVersion': '1.1', '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': 'myclass class definition'})
13.4.3 特殊的类属性

C.__name__            类C的名字(字符串)
C.__doc__               类C的文档字符串
C.__bases__            类C的所有父类构成的元组
C.__dict__               类C的属性
C.__module__          类C定义所在的模块(1.5 版本新增)
C.__class__             实例C对应的类(仅新式类中)

>>> MyClass.__name__
'MyClass'
>>> MyClass.__doc__
'myclass class definition'
>>> MyClass.__bases__
(<type 'object'>,)
>>> MyClass.__module__
'__main__'
>>> MyClass.__class__
<type 'type'>

13.5 实例

如果说类是一种数据结构定义类型,那么实例则声明了一个这种类型的变量。

13.5.1 初始化:通过调用类对象来创建实例

>>> class MyClass(object):
	pass

>>> mc = MyClass()
13.5.2 __init__()“构造器”方法

当类被调用,实例化的第一步是创建实例对象。一旦对象创建了,Python 检查是否实现了__init__()方法。默认情况下,如果没有定义(或覆盖)特殊方法__init__(),对实例不会施加任何特别的操作.任何所需的特定操作,都需要程序员实现__init__(),覆盖它的默认行为。如果__init__()没有实现,则返回它的对象,实例化过程完毕。
然而,如果__init__()已经被实现,那么它将被调用,实例对象作为第一个参数(self)被传递进去,像标准方法调用一样。调用类时,传进的任何参数都交给了__init__()。实际中,你可以想像成这样:把创建实例的调用当成是对构造器的调用。

13.5.3 __new__()"构造器"方法

我们可以通过__new__()来实例化不可变对象。

13.5.4 __del__()"解构器"方法

我们来通过跟踪实例来理解__del__()的意义:

>>> class InstCt(object):
	count = 0
	def __init__(self):
		InstCt.count += 1
	def __del__(self):
		InstCt.count -= 1
	def howMany(self):
		return InstCt.count

	
>>> a = InstCt()
>>> b = InstCt()
>>> b.howMany()
2
>>> a.howMany()
2
>>> del b
>>> a.howMany()
1
>>> del a
>>> InstCt.count
0

13.6 实例属性

实例仅拥有数据属性(方法严格来说是类属性),当一个实例被释放后,它的属性同时也被清除了。

13.6.1 “实例化”实例属性(或创建一个更好的构造器)

设置实例的属性可以在实例创建后任意时间进行,也可以在能够访问实例的代码中进行。构造器__init__是设置这些属性的关键点之一。

在构造器中首先设置实例属性

构造器是最早可以设置实例属性的地方,因为__init__()是实例创建后第一个调用的方法。一旦__init__()执行完毕,返回实例对象,即完成了实例化过程。

默认参数提供默认的实例安装,且默认参数必须是不变得对象。

>>> class Person(object):
	def __init__(self, name = "voler", age = 24):
		self.name = name
		self.age = age
	def show(self):
		print "name is:%s and age is:%d" % (self.name, self.age)

		
>>> onePerson = Person()
>>> onePerson.show()
name is:voler and age is:24
备注:__init__()返回None

13.6.2 查看实例属性 + 13.6.3 特殊的实例属性

dir(),__dict__,__class__

>>> class C(object):
	def __init__(self, x = 0, y = 0):
		self.x = x
		self.y = y

		
>>> c = C()
>>> dir(c)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y']
>>> c.__dict__
{'y': 0, 'x': 0}
>>> c.__class__
<class '__main__.C'>
13.6.5 实例属性VS类属性

访问类属性

类属性可通过类或实例来访问。当然,通过实例访问一个类属性时,只是建立一个临时的类属性引用,当实例消失后,对应的类属性也消失。

>>> class C(object):
	version = 1.2

	
>>> c = C()
>>> C.version
1.2
>>> c.version
1.2
>>> C.version += 0.1
>>> C.version
1.3
>>> c.version
1.3
>>> c.version += 0.1
>>> c.version
1.4000000000000001
>>> C.version
1.3
但是,如果类属性可变的话,一切都不同了:
>>> class Foo(object):
	x = {2003:"poe2"}

	
>>> foo = Foo()
>>> foo.x
{2003: 'poe2'}
>>> foo.x[2004] = "valid path"
>>> foo.x
{2003: 'poe2', 2004: 'valid path'}
>>> Foo.x
{2003: 'poe2', 2004: 'valid path'}
>>> del foo.x

Traceback (most recent call last):
  File "<pyshell#142>", line 1, in <module>
    del foo.x
AttributeError: 'Foo' object attribute 'x' is read-only
>>> del foo
所以,并不推荐使用实例来访问类属性。虽然方法也被称为类属性,但方法特殊就特殊在于:它通过self参数绑定到了实例对象去了,所以方法通常要用到实例来调用,用类调用不行-----原因也挺简单的,方法中操作的是实例数据,一般很少操作类数据属性,所以要用实例来调用。

13.7 从这里开始校对----绑定和方法调用

方法三要点:

首先,方法仅仅是类内部定义的函数(这意味着方法是类属性而不是实例属性)

其次,方法只有在其所属的类拥有实例时,才能被调用。当存在一个实例时,方法才被认为是绑定到那个实例来。没有实例时方法就是未绑定的。

最后,任何一个方法定义中的第一个参数就是变量self,它表示调用此方法的实例对象。

核心笔记:self是什么?

self变量用于在类实例方法中引用方法所绑定的实例。因为方法的实例在任何方法调用中总是作为第一个参数传递的,self被选中用来代表实例。你必须在方法声明中放上self,但可以在方法中不使用实例(self)。如果你的方法中没有用到self,那么请考虑创建一个常规函数,除非你有特别的原因。毕竟,你的方法代码没有使用实例,没有与类关联其他功能,这使得它看起来更像一个常规函数。

13.7.1 调用绑定方法

简单理解就是:通过实例来调用

13.7.2 调用非绑定方法

继承时构造器的实现上是调用非绑定方法:

class EmplAddrBookEntry(AddrBookEntry):
    "employee address book entry class"
    def __init__(self, nm, ph, em):
        AddrBookEntry.__init__(self, nm, ph)
        self.empid = id
        self.email = em

13.8 静态方法和类方法

并不推荐使用静态方法,一般来说,我们可以使用模块函数来达到目的。

13.8.1 staticmethod()和classmethod()内建函数

class TestStaticMethod(object):
    def foo():
        print "calling static method foo()"
    foo = staticmethod(foo)

class TestClassMethod(object):
    def foo(cls):
        print "calling class method foo()"
        print "foo() is part of class:", cls.__name__
    foo = clasmethod(foo)
>>> tsm = TestStaticMethod()
>>> TestStaticMethod.foo()
calling static method foo()
>>> tsm.foo()
calling static method foo()
>>> tcm = TestClassMethod()
>>> TestClassMethod.foo()
calling class method foo()
foo() is part of class: TestClassMethod
>>> tcm.foo()
calling class method foo()
foo() is part of class: TestClassMethod
13.8.2 使用函数修饰符(也称为装饰器)
class TestStaticMethod(object):
    @staticmethod
    def foo():
        print "calling static method foo()"

class TestClassMethod(object):
    @classmethod
    def foo(cls):
        print "calling class method foo()"
        print "foo() is part of class:", cls.__name__

13.9 组合

写在前面:组合优于派生

一个类被定义后,目标就是要把它当成一个模块来使用,并把这些对象嵌入到你的代码中去,同其它数据类型及逻辑执行流混合使用。有两种方法可以在你的代码中利用类。第一种是组合,就是让不同的类混合并加入到其它类中,来增加功能和代码重用性。你可以在一个大点的类中创建你自己的类的实例,显示一些其它的属性和方法来增强原来的类对象。另一种方法是通过派生。

13.10 子类和派生

当类之间有显著不同,并且较小的类是较大的类所需要的组件时,组合表现的很好,当当你设计“相同的类但又一些不同的功能”时,派生就是一个更加合理的选择了。

OOP的更强大方面之一是能够使用一个已经定义好的类,扩展它或者对其进行修改,而不会影响系统中使用现存类的其他代码片段。

13.10.1 创建子类

>>> class Parent(object):
	def parentMethod(self):
		print "calling parent method"

		
>>> class Child(Parent):
	def childMethod(self):
		print "calling child method"

		
>>> p = Parent()
>>> p.parentMethod()
calling parent method
>>> c = Child()
>>> c.childMethod()
calling child method
>>> c.parentMethod()
calling parent method

13.11 继承

继承描述了基类的属性如何“遗传”给派生类。一个子类可以继承它的基类的任何属性,不管是数据属性还是方法:

class Parent(object):
    def __init__(self):
        self.x = 1
class Child(Parent):
    pass
c = Child()
print c.x
程序输出:1

13.11.1 __base__类属性

__base__类属性包含其父类的集合的元祖:

class A(object):
    pass
class B(A):
    pass
class C(B):
    pass
class D(B,A):
    pass
程序输出:
>>> A.__bases__
(<type 'object'>,)
>>> B.__bases__
(<class '__main__.A'>,)
>>> C.__bases__
(<class '__main__.B'>,)
>>> D.__bases__
(<class '__main__.B'>, <class '__main__.A'>)
这里有点需要注意:D必须先继承B,才能继承A,因为继承是广度优先,深度其次,所以要先继承到小的父类,最后才是大的父类。

13.11.2 通过继承覆盖方法

子类的方法覆盖掉父类的方法很正常,但是当你想调用父类的方法的时候,只要通过父类类名直接调用即可,当然记得传递一个对象进去。

class P(object):
    def foo(self):
        print "parent"
class C(P):
    def foo(self):
        print "child"
c = C()
print P.foo(c)
程序输出:
>>> 
parent
None
这里怎么多出一个None呢?很简单,任何一个方法都会返回一个对象(__init__()会返回一个None)。我们这样修改的话,None就会消失:
class P(object):
    def foo(self):
        return "parent"
class C(P):
    def foo(self):
        return "child"
c = C()
print P.foo(c)
不过通常情况下,我们会这样编码:
class P(object):
    def foo(self):
        print "parent"
class C(P):
    def foo(self):
        super(C, self).foo()
        print "child"
c = C()
c.foo()
程序输出;
>>> 
parent
child
备注:重写__init__不会自动调用基类的__init__,所以请添加类似下面的代码:
class C(P):
    def __init__(self):
        super(C, self).__init__()
这里用super的漂亮之处在于:你不需要写出任何基类的名字(当你继承了很多基类的时候你会感谢super的)

13.11.3 从标准类型派生

子类化python类型之不可变类型

我们定制一个类,继承于float,但是控制小数位为2位:

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2))
当然,用super更好:
class RoundFloat(float):
    def __new__(cls, val):
        return super(RoundFloat, cls).__new__(cls, round(val, 2))
程序输出:
>>> RoundFloat(1.5955)
1.6
>>> RoundFloat(1.5945)
1.59
>>> RoundFloat(-1.9955)
-2.0
子类化python类型之可变类型
class SortedKeyDict(dict):
    def keys(self):
        return sorted(super(SortedKeyDict, self).keys())
d = SortedKeyDict((("zheng-cai", 67),("hui-jun",68),("xin-yi",2)))
print "by iterator:", [key for key in d]
print "by keys():", d.keys()
程序输出:
>>> 
by iterator: ['zheng-cai', 'xin-yi', 'hui-jun']
by keys(): ['hui-jun', 'xin-yi', 'zheng-cai']
13.11.4 多重继承

方法解释顺序(MRO)

在python2.2以前的版本,算法非常简单:深度优先,从左至右进行搜索,取得在子类中使用的属性。

我们先来看看经典类的MRO:

class P1:
    def foo(self):
        print "called P1-foo()"
class P2:
    def foo(self):
        print "called P2-foo()"
    def bar(self):
        print "called P2-bar()"
class C1(P1, P2):
    pass
class C2(P1, P2):
    def bar(self):
        print "called C2-bar()"
class GC(C1, C2):
    pass

经典类的解释顺序是:深度优先,从左至右:

>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> P1 ==> P2
called P2-bar()
但新式类就不同了,广度优先,从左至右(将上述代码中继承object即可成为新式类):
>>> gc = GC()
>>> gc.foo() # GC ==> C1 ==> C2 ==> P1
called P1-foo()
>>> gc.bar() # GC ==> C1 ==> C2
called C2-bar()
新式类有一个__mro__属性,告诉你查找顺序是怎样的:
>>> GC.__mro__
(<class '__main__.GC'>, <class '__main__.C1'>, <class '__main__.C2'>, <class '__main__.P1'>, <class '__main__.P2'>, <type 'object'>)

经典类的MRO问题根源在于:当你深度优先的时候,一旦到达object(所有类的基类)并且找到你想找的(比如__init__()),那么你就会放弃右边的类(从左至右)的方法(但是我写的代码结果居然和书上的结果不一样,可能python2.7进行了优化吧,下面代码中理应不可能有输出的

class B(object):
    pass
class C(object):
    def __init__(self):
        print "the default constructor"
class D(B,C):
    pass
>>> d = D()
the default constructor

13.12 类,实例和其他对象的内建函数

13.12.1 issubclass()

issubclass()布尔函数判断一个类是否是另一个类的子类或子孙类:

issubclass(sub, sup),当然,sup可以为一个父类组成的元祖

>>> issubclass(int, object)
True

13.12.2 isinstance()

isinstance()布尔函数在判定一个对象是否是另一个给定类的实例时,非常有用。

isinstance(obj1, obj2)

>>> class C1(object):pass

>>> class C2(object):pass

>>> c1 = C1()
>>> c2 = C2()
>>> isinstance(c1, C1)
True
>>> isinstance(c2, C1)
False
>>> isinstance(c1, C2)
False
>>> isinstance(c2, C2)
True
甚至,我们可以进行类型的判断:
>>> isinstance(4, int)
True
>>> isinstance(4, str)
False
>>> isinstance("hello", str)
True
13.12.3 hasattr(), getattr(), setattr(), delattr()

hasattr()函数是boolean型的,它的目的就是为了决定一个对象是否有一个特定的属性,一般用于访问某属性前先做一个检查。getattr()和setattr()函数相应的取得和赋值给对象的属性,getattr()会在你试图读取一个不存在的属性时,引发AttributeError异常,除非给出那个可选的默认参数。setattr()将要么加入一个新的属性,要么取代一个已存在的属性。而delattr()函数会从一个对象中删除属性。

>>> class myClass(object):
	def __init__(self):
		self.foo = 100

		
>>> myInst = myClass()
>>> hasattr(myInst,"foo")
True
>>> getattr(myInst,"foo")
100
>>> hasattr(myInst,"bar")
False
>>> getattr(myInst,"bar")

Traceback (most recent call last):
  File "<pyshell#30>", line 1, in <module>
    getattr(myInst,"bar")
AttributeError: 'myClass' object has no attribute 'bar'
>>> setattr(myInst, "bar","my attr")
>>> dir(myInst)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bar', 'foo']
>>> myInst.__dict__
{'foo': 100, 'bar': 'my attr'}
>>> delattr(myInst,"foo")
>>> myInst.__dict__
{'bar': 'my attr'}

13.12.4 dir()

我觉得dir唯一的好处就是:当你对一个模块不熟悉,又要调用里面的方法时候,可以用到dir.

13.12.5 super()

super()的主要用途就是:查找父类的属性,并且出奇的方便。

13.12.6 vars()

vars()内建函数与dir()相似,只是给定的对象参数都必须有一个__dict__属性。vars()返回一个字典,它包含了对象存储于其__dict__中的属性(键)及值。如果提供的对象没有这样一个属性,则会引发一个TypeError 异常。如果没有提供对象作为vars()的一个参数,它将显示一个包含本地名字空间的属性(键)及其值的字典,也就是,locals()。

>>> class C(object):
	pass

>>> c = C()
>>> c.foo = 100
>>> c.bar = "python"
>>> c.__dict__
{'foo': 100, 'bar': 'python'}
>>> vars(c)
{'foo': 100, 'bar': 'python'}

13.13 用特殊方法定制类

一堆定制类的特殊方法

通过截图还是方便了很多。。。。

13.13.1 简单定制(RoundFloat2)

我们需要一个类保存浮点数,四舍五入,保留两位小数位:

class RoundFloatManual(object):
    def __init__(self, val):
        assert isinstance(val, float), "value must be a float"
        self.value = round(val, 2)
我们会很可悲的发现,数据无法正常显示:
>>> rfm = RoundFloatManual(4.22)
>>> rfm
<__main__.RoundFloatManual object at 0x021F8510>
>>> print rfm
<__main__.RoundFloatManual object at 0x021F8510>
所以,我们得重写__str__属性,毕竟print关系到的是str()。当然,我们顺便也把__repr__给重写了。

当我们没有重写__repr__的时候:

class RoundFloatManual(object):
    def __init__(self, val):
        assert isinstance(val, float), "value must be a float"
        self.value = round(val, 2)
    def __str__(self):
        return "%.2f" % self.value
程序输出:
>>> rfm = RoundFloatManual(4.2234)
>>> rfm
<__main__.RoundFloatManual object at 0x02078510>
>>> print rfm
4.22
当我们重写__repr__的时候:
class RoundFloatManual(object):
    def __init__(self, val):
        assert isinstance(val, float), "value must be a float"
        self.value = round(val, 2)
    def __str__(self):
        return "%.2f" % self.value
    __repr__ = __str__
程序输出:
>>> rfm = RoundFloatManual(4.2234)
>>> rfm
4.22
>>> print rfm
4.22
13.13.2 数值定制(Time60)
class Time60(object):
    def __init__(self, hr, min):
        self.hr = hr
        self.min = min
    def __str__(self):
        return "%d:%d" % (self.hr, self.min)
    __repr__ = __str__
    def __add__(self, other):
        return self.__class__(self.hr + other.hr, self.min + other.min)
    def __iadd__(self, other):
        self.hr += other.hr
        self.min += other.min
        return self
程序输出:
>>> time1 = Time60(4,5)
>>> time2 = Time60(6,7)
>>> time1 + time2
10:12
>>> time1 += time2
>>> time1
10:12
这里唯一要注意到是:self.__class__的使用,它会实例化self.

最后我们来看个随机序列迭代器的例子:

from random import choice
class RandSeq(object):
    def __init__(self, seq):
        self.data = seq
    def __iter__(self):
        return self
    def next(self):
        return choice(self.data)
程序输出:
>>> randObj = RandSeq([1,2,3,4,5,6])
>>> for i in range(10):
	print randObj.next(),

	
6 4 2 4 3 3 2 3 2 3


13.13.3 迭代器

上述例子中,__init__()方法执行赋值操作,而__iter__()仅返回self,这就是如何将一个对象声明为迭代器的方式,最后,调用next()来得到迭代器中连续的值。

我们来编写一个任意项的迭代器:

class AnyIter(object):
    def __init__(self, data, safe = False):
        self.safe = safe
        self.iter = iter(data)
    def __iter__(self):
        return self
    def next(self, howmany = 1):
        retval = []
        for eachItem in range(howmany):
            try:
                retval.append(self.iter.next())
            except StopIteration:
                if self.safe:
                    break
                else:
                    raise
        return retval
正常的输出:
>>> a = AnyIter(range(10))
>>> for j in range(1,5):
	print j,":", a.next(j)

	
1 : [0]
2 : [1, 2]
3 : [3, 4, 5]
4 : [6, 7, 8, 9]
出异常的输出:
>>> a.next(14)

Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    a.next(14)
  File "C:\Python27\hello.py", line 11, in next
    retval.append(self.iter.next())
StopIteration
对异常进行处理的输出:
>>> a = AnyIter(range(10), True)
>>> a.next(14)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
13.13.4 多类型定制(NumStr)
class NumStr(object):
    def __init__(self, num = 0, string = ""):
        self.__num = num
        self.__string = string
    def __str__(self):
        return "[%d::%r]" % (self.__num, self.__string)
    __repr__ = __str__
    def __add__(self, other):
        if isinstance(other, NumStr):
            return self.__class__(self.__num + other.__num, self.__string + other.__string)
        else:
            raise TypeError, "illegal argument type for built-in operation"
    def __mul__(self, num):
        if isinstance(num, int):
            return self.__class__(self.__num * num, self.__string * num)
        else:
            raise TypeError,"ilegal argument type for built-in operation"
    def __nonzero__(self):
        return self.__num or len(self.__string)
    def __norm_cval(self, cmpres):
        return cmp(cmpres, 0)
    def __cmp__(self, other):
        return self.__norm_cval(cmp(self.__num, other.__num)) + self.__norm_cval(cmp(self.__string, other.__string))
程序输出:
>>> a = NumStr(3, "foo")
>>> b = NumStr(3, "goo")
>>> c = NumStr(2,"foo")
>>> d = NumStr()
>>> e = NumStr(string="boo")
>>> f = NumStr(1)
>>> a
[3::'foo']
>>> a < b
True
>>> a == a
True
>>> b * 2
[6::'googoo']
>>> b + c
[5::'goofoo']
>>> cmp(a,b)
-1
>>> cmp(b,c)
1
>>> cmp(a,a)
0

13.14 私有化

在变量名加双下划线,这是简单的数据私有化方法,不过个人并不推荐。


13.15 授权

13.15.1 包装

对一个已存在的对象进行包装,不管它是数据类型,还是一段代码,可以是对一个已存在的对象,增加新的,删除不要的,或者修改其它已存在的功能。

13.15.2 实现授权

授权是包装的一个特性,可用于简化处理有关dictating功能,采用已存在的功能以达到最大限度的代码重用。

包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的保持原样,或者保留已存功能和行为。授权的过程,即是所有更新功能都是由昕蕾的某部分来处理,但已存在的功能就授权给对象的默认属性。

实现授权的关键点就是覆盖__getattr__()方法,在代码中包含一个对getattr()内建函数的调用。特别地,调用getattr()以得到默认对象属性(数据属性或者方法)并返回它以便访问或调用。特殊方法__getattr__()的工作方式是,当搜索一个属性时,任何局部对象首先被找到(定制的对象)。如果搜索失败了,则__getattr__()会被调用,然后调用getattr()得到一个对象的默认行为。
下面是包装对象的简例:

class WrapMe(object):
    def __init__(self, obj):
        self.__data = obj
    def get(self):
        return self.__data
    def __repr__(self):
        return repr(self.__data)
    def __str__(self):
        return str(self.__data)
    def __getattr__(self, attr):
        return getattr(self.__data, attr)
注意程序的输出:
>>> wrappedComplex = WrapMe(3.5+4.2j)
>>> wrappedComplex
(3.5+4.2j)
>>> wrappedComplex.real
3.5
>>> wrappedComplex.imag
4.2
>>> wrappedComplex.conjugate()
(3.5-4.2j)
>>> wrappedComplex.get()
(3.5+4.2j)
这里,我们调用复数的三种属性,但是这三种属性在类的定义中并不存在。对这些属性的访问,是通过getattr()方法,授权给对象。最终调用get()方法没有授权,因为它是为我们的对象定义的--它返回包装的真实的数据对象。
>>> wrappedList = WrapMe([123, 'foo', 45.67])
>>> wrappedList.append('bar')
>>> wrappedList.append(123)
>>> wrappedList
[123, 'foo', 45.67, 'bar', 123]
>>> wrappedList.index(45.67)
2
>>> wrappedList.count(123)
2
>>> wrappedList.pop()
123
>>> wrappedList
[123, 'foo', 45.67, 'bar']
但是我们为什么要定义get()函数呢?只是这样我们就可以取得原对象,进行某些特殊的操作,比如:
>>> wrappedList
[123, 'foo', 45.67, 'bar']
>>> wrappedList[3]

Traceback (most recent call last):
  File "<pyshell#35>", line 1, in <module>
    wrappedList[3]
TypeError: 'WrapMe' object does not support indexing
>>> wrappedList.get()[3]
'bar'
更新简单的包裹类
from time import time, ctime
class TimedWrapMe(object):
    def __init__(self, obj):
        self.__data = obj
        self.__ctime = self.__mtime = self.__atime = time()
    def get(sefl):
        self.__atime = time()
        return self.__data
    def gettimeval(self, t_type):
        if not isinstance(t_type, str) or t_type[0] not in "cma":
            raise TypeError, "argument of 'c','m', or 'a' required"
        return getattr(self, "_%s__%stime" % (self.__class__.__name__, t_type[0]))
    def gettimestr(self, t_type):
        return ctime(self.gettimeval(t_type))
    def set(self, obj):
        self.__data = obj
        self.__mtime = self.__atime = time()
    def __repr__(self):
        self.__atime = time()
        return repr(self.__data)
    def __str__(self):
        self.__atime = time()
        return str(self.__data)
    def __getattr__(self, attr):
        self.__atime = time()
        return getattr(self.__data, attr)
>>> timeWrappedObj = TimedWrapMe(932)
>>> timeWrappedObj.gettimestr('c')
'Tue Jun 18 19:49:03 2013'
>>> timeWrappedObj.gettimestr('m')
'Tue Jun 18 19:49:03 2013'
>>> timeWrappedObj.gettimestr('a')
'Tue Jun 18 19:49:03 2013'
>>> timeWrappedObj
932
>>> timeWrappedObj.set('time is up!')
>>> timeWrappedObj
'time is up!'
>>> timeWrappedObj.gettimestr('c')
'Tue Jun 18 19:49:03 2013'
>>> timeWrappedObj.gettimestr('m')
'Tue Jun 18 19:49:43 2013'
>>> timeWrappedObj.gettimestr('a')
'Tue Jun 18 19:49:57 2013'

13.16 新式类的高级特性

13.16.1 新式类的通用特性

工厂函数的诞生:

int(), long(), float(), complex(),str(), unicode(),list(), tuple(), type(),basestring(), dict(), bool(), set(), frozenset(),object(), classmethod(), staticmethod(), super(), property(), file()
在判断类型的时候:

old:

if type(obj) == type(0)

if type(obj) == types.IntType

Better:

if type(obj) is type(0)

even better:

if isinstance(obj, int)

if isinstance(obj, (int,long))

if type(obj) is int

13.16.2 __slots__类属性

字典位于实例的“心脏”。__dict__属性跟踪所有实例属性。比如一个实例inst,它有个属性foo,那使用inst.foo来访问它等价于inst.__dict__["foo"]

字典会占据大量内存,如果你有一个属性数量很少的类,但有很多实例,那么正好是这种情况。为内存上的考虑,用户现在可以使用__slots__属性来替代__dict__。

基本上,__slots__是一个类变量,由一序列型对象组成,由所有合法标识构成的实例属性的集合来表示。它可以是一个列表,元组或可迭代对象。也可以是标识实例能拥有的唯一的属性的简单字符串。任何试图创建一个其名不在__slots__中的名字的实例属性都将导致AttributeError 异常:

>>> class SlottedClass(object):
	__slots__ = ("foo","bar")

	
>>> c = SlottedClass()
>>> c.foo = 42
>>> c.xxx = "do not think so"

Traceback (most recent call last):
  File "<pyshell#52>", line 1, in <module>
    c.xxx = "do not think so"
AttributeError: 'SlottedClass' object has no attribute 'xxx'
这种特性的主要目的是节约内存,其副作用是某种类型的“安全”,它能防止用户随心所欲的动态增加实例属性。 
13.16.3 特殊方法__getattribute__()

python类有一个名为__getattr__()的特殊方法,它仅当属性不能在实例的__dict__或它的类(类的__dict__),或者祖先类(其__dict__)中找到时,才被调用。

__getattribute__的作用是:当属性被访问时,它就一直都可以被调用,而不局限于不能找到的情况。

13.16.4 描述符

描述符表示对象属性的一个代理。当需要属性时,可通过描述符或者采用常规方式(句点属性标识法)来访问它。

__get__(),__set__(),__delete__()特殊方法

__getattribute__()特殊方法

使用描述符的顺序很重要,有一些描述符的级别要高于其它的。整个描述符系统的心脏是__getattribute__(),因为对每个属性的实例都会调用到这个特殊的方法。这个方法被用来查找类的属性,同时也是你的一个代理,调用它可以进行属性的访问等操作。
如果一个实例调用了__get__()方法,这就可能传入了一个类型或类的对象。举例来说,给定类X 和实例x, x.foo 由__getattribute__()转化成:

type(x).__dict__['foo'].__get__(x, type(x))
如果类调用了__get__()方法,那么None 将作为对象被传入(对于实例, 传入的是self):
X.__dict__['foo'].__get__(None, X)
最后,如果super()被调用了,比如,给定Y 为X 的子类,然后用super(Y,obj).foo 在obj.__class__.__mro__中紧接类Y 沿着继承树来查找类X,然后调用: 
X.__dict__['foo'].__get__(obj, X)
然后,描述符会负责返回需要的对象。

优先级别

1. 类属性

2. 数据描述符

3. 实例属性

4. 非数据描述符

5. 默认为__getattr__()

描述符是一个类属性,因此所有的类属性皆具有最高的优先级。你其实可以通过把一个描述符的引用赋给其它对象来替换这个描述符。比它们优先级别低一等的是实现了__get__()和__set__()方法的描述符。如果你实现了这个描述符,它会像一个代理那样帮助你完成所有的工作!

否则,它就默认为局部对象的__dict__的值,也就是说,它可以是一个实例属性。接下来是非数据描述符。
描述符举例:

>>> class DevNull1(object):
	def __get__(self, obj, typ = None):
		pass
	def __set__(self, obj, val):
		pass

	
>>> class C1(object):
	foo = DevNull1()

	
>>> c1 = C1()
>>> c1.foo = "bar"
>>> c1.foo
>>> print c1.foo
None
任何一件事情,刚开始的时候,总是索然无趣,而且简单。后面则慢慢的。。。。。。
>>> class DevNull2(object):
	def __get__(self, obj, typ = None):
		print "accessing attribute...ignoring"
	def __set__(self, obj, val):
		print "attempt to asign %r...ignoring" % (val)

		
>>> class C2(object):
	foo = DevNull2()

	
>>> c2 = C2()
>>> c2.foo = "bar"
attempt to asign 'bar'...ignoring
>>> x = c2.foo
accessing attribute...ignoring
>>> print x
None
突然,就这样变得有趣起来了。那么,来点更刺激的吧:
>>> class DevNull3(object):
	def __init__(self, name = None):
		self.name = name
	def __get__(self, obj, typ = None):
		print "accessing [%s]...ignoring" % (self.name)
	def __set__(self, obj, val):
		print "assigning %r to [%s]...ignoring" % (val, self.name)

		
>>> class C3(object):
	foo = DevNull3("foo")

	
>>> c3 = C3()
>>> c3.foo = "bar"
assigning 'bar' to [foo]...ignoring
>>> x = c3.foo
accessing [foo]...ignoring
>>> print x
None
>>> c3.__dict__["foo"] = "bar"
>>> x = c3.foo
accessing [foo]...ignoring
>>> print x
None
>>> print c3.__dict__["foo"]
bar
最后两个输出很特殊,说明了数据描述符比实例属性的优先级高,所赋的值“bar”被隐藏或覆盖了。

同样地,由于实例属性比非数据描述符的优先级高,你也可以将非数据描述符隐藏。这就和你给一个实例属性赋值,将对应类的同名属性隐藏起来是同一个道理:

>>> class FooFoo(object):
	def foo(self):
		print "very important foo() method."

		
>>> bar = FooFoo()
>>> bar.foo()
very important foo() method.
>>> bar.foo = "it is no longer here"
>>> bar.foo
'it is no longer here'
>>> del bar.foo
>>> bar.foo
<bound method FooFoo.foo of <__main__.FooFoo object at 0x01E166F0>>
>>> bar.foo()
very important foo() method.
但是,下面代码可能更能清楚的表达意思:
>>> def barBar():
	print "foo() hidden by barBar()"

	
>>> bar.foo = barBar
>>> bar.foo()
foo() hidden by barBar()
>>> del bar.foo
>>> bar.foo()
very important foo() method.
让我们来看最后一个例子:使用文件来存储属性
import os
import pickle

class FileDescr(object):
    saved = []
    def __init__(self, name = None):
        self.name = name
    def __get__(self, obj, typ = None):
        if self.name not in FileDescr.saved:
            raise AttributeError,"%r used before assignment" % self.name
        try:
            f = open(self.name,"r")
            val = pickle.load(f)
            f.close()
            return val
        except(pickle.InpicklingError, IOError,
               EOFError,AttributeError,
               ImportError,IndexError),e:
            raise AttributeError,"could not read %r" % (self.name)
    def __set__(self, obj, val):
        f = open(self.name, "w")
        try:
            pickle.dump(val, f)
            FileDescr.saved.append(self.name)
        except (TypeError,pickle.PicklingError),e:
            raise AttributeError,"could not pickle %r" % self.name
        finally:
            f.close()
    def __delete__(self, obj):
        try:
            os.unlink(self.name)
            FileDescr.saved.remove(self.name)
        except (OSError, ValueError),e:
            pass
程序输出:
>>> class MyFileVarClass(object):
	foo = FileDescr("foo")
	bar = FileDescr("bar")

	
>>> fvc = MyFileVarClass()
>>> print fvc.foo

Traceback (most recent call last):
  File "<pyshell#121>", line 1, in <module>
    print fvc.foo
  File "C:\Python27\hello.py", line 10, in __get__
    raise AttributeError,"%r used before assignment" % self.name
AttributeError: 'foo' used before assignment
>>> fvc.foo = 42
>>> fvc.bar = "leanna"
>>> print fvc.foo,fvc.bar
42 leanna
>>> del fvc.foo
>>> print fvc.foo

Traceback (most recent call last):
  File "<pyshell#126>", line 1, in <module>
    print fvc.foo
  File "C:\Python27\hello.py", line 10, in __get__
    raise AttributeError,"%r used before assignment" % self.name
AttributeError: 'foo' used before assignment
>>> print fvc.bar
leanna

属性和property()内建函数

property()内建函数有四个参数,它们是 :
property(fget=None, fset=None, fdel=None, doc=None)

请注意property()的一般用法是,将它写在一个类定义中,property()接受一些传进来的函数(其实是方法)作为参数。实际上,property()是在它所在的类被创建时被调用的,这些传进来的(作为参数的)方法是非绑定的,所以这些方法其实就是函数!

>>> class ProtectAndHideX(object):
	def __init__(self, x):
		assert isinstance(x, int),"x must be an integer!"
		self.__x = ~x
	def get_x(self):
		return ~self.__x
	x = property(get_x)

	
>>> inst = ProtectAndHideX(12)
>>> print inst.x
12
>>> inst.x = 20

Traceback (most recent call last):
  File "<pyshell#138>", line 1, in <module>
    inst.x = 20
AttributeError: can't set attribute
并没有定义set的方法,故最后进行赋值出现异常。

那么,我们增加set方法吧。

>>> class HideX(object):
	def __init__(self, x):
		self.__x = x
	def get_x(self):
		return ~self.__x
	def set_x(self, x):
		assert isinstance(x, int),"x must be an integer!"
		self.__x = ~x
	x = property(get_x, set_x)

	
>>> inst = HideX(20)
>>> print inst.x
-21
>>> inst.x = -21
>>> print inst.x
-21
>>> inst.x = 20
>>> print inst.x
20
我有个疑问是:为什么初始化为20的时候,会输出21呢???

但是这样会搞乱类的命名空间,书上提供了一种方法,不过不是看得很懂。。。。

1 “借用”一个函数的名字空间
2编写一个用作内部函数的方法作为 property()的(关键字)参数
3 (用 locals())返回一个包含所有的(函数/方法)名和对应对象的字典
4 把字典传入 property(),然后
5去掉临时的名字空间

>>> class HideX(object):
	def __init__(self, x):
		self.__x = x
	@property
	def x():
		def fget(self):
			return ~self.__x
	def fset(self, x):
		assert isinstance(x, int),"x must be an integer!"
		self.__x = ~x
		return locals()
不太理解,为什么x()被定义成一个函数而不是一个方法  .


13.6.5 Metaclasses和__metaclass__

无论发生什么,都要问问自己,当初自己是为了什么而出发的!!!

元类(Metaclasses)是什么?

元类让你来定义某些类是如何被创建的,从根本上说,赋予你如何创建类的控制权。

从根本上说,你可以把元类想成是一个类中类,或是一个类,它的实例是其它的类。实际上,当你创建一个新类时,你就是在使用默认的元类,它是一个类型对象。(对传统的类来说,它们的元类是types.ClassType.)当某个类调用type()函数时,你就会看到它到底是谁的实例:

>>> class C(object):
	pass

>>> class CC:
	pass

>>> type(C)
<type 'type'>
>>> type(CC)
<type 'classobj'>
>>> import types
>>> type(CC) is types.ClassType
True
什么时候使用元类?

元类一般用于创建类。在执行类定义时,解释器必须要知道这个类的正确的元类。解释器会先寻找类属性__metaclass__,如果此属性存在,就将这个属性赋值给此类作为它的元类。如果此属性没有定义,它会向上查找父类值中的__metaclass__.所有新风格的类如果没有任何父类,会从对象或类型中继承,当然是object。
在执行类定义的时候,将检查此类正确的(一般是默认的)元类,元类(通常)传递三个参数(到构造器):类名,从基类继承数据的元组,和(类的)属性字典。

谁在用元类?

程序员

元类示例1

from time import ctime

print "***welcome to metaclasses!"
print "\tmetaclass declaration first"
class MetaC(type):
    def __init__(cls, name, bases, attrd):
        super(MetaC, cls).__init__(name, bases, attrd)
        print "***created class %r at:%s" % (name, ctime())
print "\tclass 'foo' declaration next"
class Foo(object):
    __metaclass__ = MetaC
    def __init__(self):
        print "***instantiated class %r at:%s" % (self.__class__.__name__, ctime())
print "\tclass 'foo' instantiation next"
f = Foo()
print "\tDone"
程序输出:
>>> 
***welcome to metaclasses!
	metaclass declaration first
	class 'foo' declaration next
***created class 'Foo' at:Tue Jun 18 22:11:12 2013
	class 'foo' instantiation next
***instantiated class 'Foo' at:Tue Jun 18 22:11:12 2013
	Done
似乎明白了什么。

元类示例2

from warnings import warn
class ReqStrSugRepr(type):
    def __init__(cls, name, bases, attrd):
        super(ReqStrSugRepr, cls).__init__(name, bases, attrd)
        if "__str__" not in attrd:
            raise TypeError("class requires overriding of __str__()")
        if "__repr__" not in attrd:
            warn("class suggests overriding of __repr__()\n", stacklevel = 3)
print "***defined ReqStrSugRepr (meta)class\n"

class Foo(object):
    __metaclass__ = ReqStrSugRepr
    def __str__(self):
        return "instance of class:",self.__class__.__name__
    def __repr__(self):
        return self.__class__.__name__
print "***defined foo class\n"
class Bar(object):
    __metaclass__ = ReqStrSugRepr
    def __str__(self):
        return "instance of class:",self.__class__.__name__
print "***defined bar class\n"
class FooBar(object):
    __metaclass__ = ReqStrSugRepr
print "***defined foobar class\n"
坦白说来,看不懂。。。。。。。。 

13.18 练习

13-1. 程序设计。请列举一些面向对象编程与传统旧的程序设计形式相比的先进之处。

OOP的好处,个人理解是:解放了程序员编程的负担。

13-2. 函数和方法的比较。函数和方法之间的区别是什么?

方法是类属性,但是函数不是,方法因类而存在。

13-3. 对类进行定制。写一个类,用来将浮点数值转换为金额。在本练习里,我们使用美国
货币,但读者也可以自选任意货币。
基本任务: 编写一个dollarize()函数,它以一个浮点数值作为输入,返回一个字符串形式的
金额数。比如说:
dollarize(1234567.8901) ==> ‘$1,234,567.89.
dollarize()返回的金额数里应该允许有逗号(比如1,000,000),和美元的货币符号。如果有负
号,它必须出现在美元符号的左边。完成这项工作后,你就可以把它转换成一个有用的类,名为
MoneyFmt。

def dollarize(fValue):
        strValue = str(fValue)
        isNegative = False
        if strValue[0] == "-":
            isNegative = True
            strValue = strValue[1:]
        strMoney = strValue.split(".")
        strTemp = []
        while (len(strMoney[0]) - 1) / 3:
            strTemp.append(strMoney[0][-3:])
            strMoney[0] = strMoney[0][:-3]
        strTemp.append(strMoney[0])
        strTemp.reverse()
        myDoller = ",".join(strTemp) + "." + strMoney[1]
        if isNegative:
            myDoller = "-" + myDoller
        return myDoller
class MoneyFmt(object):
    def __init__(self, value = 0.0):
        self.value = dollarize(value)
    def update(self, value = None):
        self.value = dollarize(value)
    def __repr__(self):
        return repr(self.value)
    def __str__(self):
        val = "$"
        if self.value[0] == "-":
            val = "-$" + self.value[1:]
        else:
            val += self.value
        return val
    def __nonzero__(self):
        return bool(self.value)

程序输出:

>>> cash = MoneyFmt(1234567.8901)
>>> cash
'1,234,567.8901'
>>> print cash
$1,234,567.8901
>>> cash = MoneyFmt(-1234567.8901)
>>> cash
'-1,234,567.8901'
>>> print cash
-$1,234,567.8901
13-4. 用户注册。建立一个用户数据库(包括登录名、密码和上次登录时间戳)类(参考练习7-5和9-12),来管理一个系统,该系统要求用户在登录后才能访问某些资源。这个数据库类对用户进行管理,并在实例化操作时加载之前保存的用户信息,提供访问函数来添加或更新数据库的信息。在数据修改后,数据库会在垃圾回收时将新信息保存到磁盘。

class PersonDataBase(object): def __init__(self): self.__personDataBase = [("user1",1),("user2",2)] def login(self, userName, userPasswd): if (userName, userPasswd) not in self.__personDataBase: print "sorry, error" return None else: print "ok" oneObj = PersonDataBase() oneObj.login("test",1) oneObj.login("user1",1)
没什么心思写这题,因为题目看不懂。。。就是不知道具体要写什么,是写复杂还是写简单,要达到什么效果等等。。

13-5. 几何. 创建一个由有序数值对(x, y) 组成的Point 类,它代表某个点的X 坐标和Y 坐标。X 坐标和Y 坐标在实例化时被传递给构造器,如果没有给出它们的值,则默认为坐标的原点。

class Point(object):
    def __init__(self, x, y):
        self.__x = x
        self.__y = y
    def get_xy(self):
        return (self.__x, self.__y)
    def set_xy(self, point):
        self.__x, self.__y = point
    def __str__(self):
        return "%d:%d" % (self.__x, self.__y)
    __repr__ = __str__
    point = property(get_xy, set_xy)

程序输出:

>>> p = Point(2,3)
>>> p.point = 5,6
>>> p
5:6
>>> p.point = 7,8
>>> print p
7:8

13-6. 几何. 创建一个直线/直线段类。除主要的数据属性:一对坐标值(参见上一个练习)外,
它还具有长度和斜线属性。你需要覆盖__repr__()方法(如果需要的话,还有__str__()方法),使得
代表那条直线(或直线段)的字符串表示形式是由一对元组构成的元组,即,((x1, y1), (x2, y2)).
总结:
__repr__     将直线的两个端点(始点和止点)显示成一对元组
length         返回直线段的长度 - 不要使用"len", 因为这样使人误解它是整数。
slope           返回此直线段的斜率(或在适当的时候返回None)

import math
class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def get_xy(self):
        return (self.x, self.y)
    def set_xy(self, point):
        self.x, self.y = point
    def __str__(self):
        return "%d:%d" % (self.x, self.y)
    __repr__ = __str__
    point = property(get_xy, set_xy)
class Line(object):
    def __init__(self, pointStart, pointEnd):
        self.pointStart = pointStart
        self.pointEnd = pointEnd
    def get_line(self):
        return (self.pointStart, self.pointEnd)
    def set_line(self, line):
        self.pointStart,self.pointEnd = line
    def __str__(self):
        return "%s-->%s" % (self.pointStart, self.pointEnd)
    def length(self):
        return math.sqrt((self.pointStart.x - self.pointEnd.x) ** 2 + (self.pointStart.y - self.pointEnd.y) ** 2)
    __repr__ = __str__
    line = property(get_line, set_line)
p1 = Point(2,3)
p2 = Point(4,5)
line1 = Line(p1, p2)
print line1.length()

程序输出:

>>> 
2.82842712475
斜率就不写了。这里有个特别注意到点是:Point数据不要写成私有的,否则调用起来特别难,除非你编写一个get函数来获取数据 。

13-7. 数据类。提供一个time 模块的接口,允许用户按照自己给定时间的格式,比如:
“MM/DD/YY,” “MM/DD/YYYY,” “DD/MM/YY,” “DD/MM/ YYYY,” “Mon DD, YYYY,” 或是标准
的Unix 日期格式:“Day Mon DD, HH:MM:SS YYYY” 来查看日期。你的类应该维护一个日期值,并
用给定的时间创建一个实例。如果没有给出时间值,程序执行时会默认采用当前的系统时间。还包
括另外一些方法:
update() 按给定时间或是默认的当前系统时间修改数据值
display() 以代表时间格式的字符串做参数,并按照给定时间的格式显示:
'MDY' ==> MM/DD/YY
'MDYY' ==> MM/DD/YYYY
'DMY' ==> DD/MM/YY
'DMYY' ==> DD/MM/YYYY
'MODYY' ==> Mon DD, YYYY

import time
class Date(object):
    def __init__(self, time = time.ctime()):
        self.time = time
    def choice(self, choiceTime):
        date = []
        date = self.time.split(" ")
        dateDict = {}
        dateDict["MMY"] = date[1] + "/" + date[2] + "/" + date[4][2:]
        dateDict["MDYY"] = date[1] + "/" + date[2] + "/" + date[4]
        dateDict["DMY"] = date[2] + "/" + date[1] + "/" + date[4][2:]
        dateDict["DMYY"] = date[2] + "/" + date[1] + "/" + date[4]
        dateDict["MODYY"] = date[1] + " " + date[2] + "," + date[4]
        return dateDict[choiceTime]
if __name__ == "__main__":
    date1 = Date()
    while True:
        print "'MMY'-->MM/DD/YY"
        print "'MDYY' ==> MM/DD/YYYY"
        print "'DMY' ==> DD/MM/YY"
        print "'DMYY' ==> DD/MM/YYYY"
        print "'MODYY' ==> Mon DD, YYYY"
        choiceTime = raw_input("please enter your choice(q to quit):")
        if choiceTime.lower() == "q":
            break
        print date1.choice(choiceTime)
程序输出:
>>> 
'MMY'-->MM/DD/YY
'MDYY' ==> MM/DD/YYYY
'DMY' ==> DD/MM/YY
'DMYY' ==> DD/MM/YYYY
'MODYY' ==> Mon DD, YYYY
please enter your choice(q to quit):MMY
Jun/18/13
'MMY'-->MM/DD/YY
'MDYY' ==> MM/DD/YYYY
'DMY' ==> DD/MM/YY
'DMYY' ==> DD/MM/YYYY
'MODYY' ==> Mon DD, YYYY
please enter your choice(q to quit):MDYY
Jun/18/2013
'MMY'-->MM/DD/YY
'MDYY' ==> MM/DD/YYYY
'DMY' ==> DD/MM/YY
'DMYY' ==> DD/MM/YYYY
'MODYY' ==> Mon DD, YYYY
please enter your choice(q to quit):DMY
18/Jun/13
'MMY'-->MM/DD/YY
'MDYY' ==> MM/DD/YYYY
'DMY' ==> DD/MM/YY
'DMYY' ==> DD/MM/YYYY
'MODYY' ==> Mon DD, YYYY
please enter your choice(q to quit):Q
13-8. 堆栈类
class Stack(list):
    def __init__(self, stack):
        super(Stack, self).__init__()
        self.stack = stack
    def push(self, oneElement):
        self.stack.append(oneElement)
    def pop(self):
        return self.stack.pop()
    def isEmpty(self):
        return (not len(self.stack))
    def peek(self):
        return self.stack[-1]
    def __str__(self):
        return str(self.stack)
    __repr__ = __str__
程序输出:
>>> stk = Stack([1,2,3])
>>> stk
[1, 2, 3]
>>> stk.push(4)
>>> stk
[1, 2, 3, 4]
>>> stk.pop()
4
>>> stk.pop()
3
>>> stk
[1, 2]
>>> stk.peek()
2
>>> stk
[1, 2]
>>> stk.isEmpty()
False
这个类实际上编写的并不完美,因为根本就没有继承list。不知道如何继承list来编写堆栈类。我记忆中之前写过,用到list的继承后,写的特别的轻松,但是忘记内容在哪里了。

13-9. 队列类

class Queue(object):
    def __init__(self, queue):
        self.queue = queue
    def enqueue(self, element):
        self.queue.append(element)
    def dequeue(self):
        element = self.queue[0]
        self.queue = self.queue[1:]
        return element
    def __str__(self):
        return str(self.queue)
    __repr__ = __str__
程序输出:
>>> que = Queue([1,2,3,4])
>>> que
[1, 2, 3, 4]
>>> que.enqueue(5)
>>> que.dequeue()
1
>>> que
[2, 3, 4, 5]
>>> que.dequeue()
2
>>> que
[3, 4, 5]
这里编写的也不太好,因为并没有进行判断是否存在异常的现象等。

13-10. 堆栈和队列。编写一个类,定义一个能够同时具有堆栈(FIFO)和队列(LIFO)操作行为的数据结构。这个类和Perl 语言中数组相像。需要实现四个方法:
shift()         返回并删除列表中的第一个元素,类似于前面的dequeue()函数。
unshift()      在列表的头部"压入"一个新元素
push()         在列表的尾部加上一个新元素,类似于前面的enqueue()和push()方法。
pop()         返回并删除列表中的最后一个元素,与前面的pop()方法完全一样。

class StackQueue(object):
    def __init__(self,StackQueue):
        self.StackQueue = StackQueue
    def isEmpty(self):
        return (not len(self.StackQueue))
    def shift(self):
        if self.isEmpty():
            print "empty, can not shift"
        else:
            element = self.StackQueue[0]
            self.StackQueue = self.StackQueue[1:]
            return element
    def unshift(self, element):
        self.StackQueue = [element] + self.StackQueue
    def push(self, element):
        self.StackQueue.append(element)
    def pop(self):
        self.StackQueue.pop()
    def __str__(self):
        return str(self.StackQueue)
    __repr__ = __str__
程序输出:
>>> stkque = StackQueue([1,2,3])
>>> stkque
[1, 2, 3]
>>> stkque.shift()
1
>>> stkque.unshift(6)
>>> stkque
[6, 2, 3]
>>> stkque.push(7)
>>> stkque
[6, 2, 3, 7]
>>> stkque.pop()
>>> stkque
[6, 2, 3]
后面程序越来越有点:变态。。。。。。要我用python做出一个QQ来吗?我当时看wxpython,头都看晕了。。

习题13.11,13.12,13.13先跳过(以后可能也不会回头做这三道题。。。)

13-14. DOS. 为DOS 机器编写一个UNIX 操作界面的shell。你向用户提供一个命令行,使得用户可以在那里输入Unix 命令,你可以对这些命令进行解释,并返回相应的输出,例如:“ls”命令调用“dir”来显示一个目录中的文件列表,“more”调用同名命令(分页显示一个文件),“cat” 调用 “type,” “cp” 调用“copy,” “mv” 调用 “ren,” “rm” 调用 “del,” 等.

import os
def cmdDir():
    dirName = os.getcwd()
    for i in os.listdir(dirName):
        print dirName + "\\" + i
def cmdmore(cmd):
    fileName = cmd.split(" ")[1]
    with open(fileName) as fobj:
        for line in fobj:
            print line
def cmdtype(cmd):
    fileName = cmd.split(" ")[1]
    type(fileName)
def cmdcopy(cmd):
    oldFile = cmd.split(" ")[1]
    newFile = cmd.split(" ")[2]
    with open(oldFile,"r") as foldObj:
        with open(newFile,"w") as fnewObj:
            for line in foldObj:
                fnewObj.write(line)
def cmdren(cmd):
    cmdcopy(cmd)
    delFile = cmd.split(" ")[1]
    os.remove(delFile)
def cmddel(cmd):
    delFile = cmd.split(" ")[1]
    os.remove(delFile)
def DOS():
    
    while True:
        cmd = raw_input("-->")
        if cmd.find("ls") != -1:
            cmdDir()
        elif cmd.find("more") != -1:
            cmdmore(cmd)
        elif cmd.find("cat") != -1:
            cmdtype(cmd)
        elif cmd.find("cp") != -1:
            cmdcopy(cmd)
        elif cmd.find("mv") != -1:
            cmdren(cmd)
        elif cmd.find("rm") != -1:
            cmddel(cmd)
        else:
            print "sorry, command is wrong.please enter:ls,more,cat,cp,mv or rm"
if __name__ == "__main__":
    DOS()

我唯一不理解的是cat这条命令,用type来表示,我如何type一个文件???

13-15. 授权。示例13.8 的执行结果表明我们的类CapOpen 能成功完成数据的写入操作。在我们的最后评论中,提到可以使用CapOpen() 或 open()来读取文件中的文本。为什么呢?这两者使用起来有什么差异吗?

实际上使用起来没什么差异,只是使用CapOpen()的时候,它重写了write的方法罢了。

13-16. 授权和函数编程。
(a) 请为示例13.8 中的CapOpen 类编写一个writelines()方法。这个新函数将可以一次读入多行文本,然后将文本数据转换成大写的形式,它与write()方法的区别和通常意思上的writelines()与write()方法之间的区别相似。注意:编写完这个方法后,writelines()将不再由文件对象"代理"。
(b) 在writelines()方法中添加一个参数,用这个参数来指明是否需要为每行文本加上一个换行符。此参数的默认值是False,表示不加换行符。

class CapOpen(object):
    def __init__(self, fn, mode = "r", buf = -1):
        self.file = open(fn, mode, buf)
    def __str__(self):
        return str(self.file)
    def __repr__(self):
        return repr(self.file)
    def write(self, line):
        self.file.write(line.upper())
    def writelines(self, lines, isNewLine = False):
        for line in lines:
            self.file.write(line)
            if not isNewLine:
                self.file.write("\n")
    def __getattr__(self, attr):
        return getattr(self.file, attr)
if __name__ == "__main__":
    fobj = CapOpen("data.txt")
    print fobj.read()
    fobj.close()

    fobj = CapOpen("data.txt","a+")
    fobj.write("newline\n")
    fobj.close()

    fobj = CapOpen("data.txt","a+")
    lines = ["a\n","new\n","line\n"]
    fobj.writelines(lines, True)

    print
    fobj = CapOpen("data.txt")
    print fobj.read()
    fobj.close()
程序输出:
>>> 
hello world
i love this world
and i love python
too

hello world
i love this world
and i love python
tooNEWLINE
a
new
line


13-17. 数值类型子类化。在示例13.3 中所看到的moneyfmt.py 脚本基础上修改它,使得它可以扩展Python 的浮点类型。请确保它支持所有操作,而且是不可变的。

class MoneyFmt(float):
    def __init__(self, value = 0.0):
        super(MoneyFmt, self).__init__()
        self.value = float(value)
    def update(self, value = None):
        self.value = float(value)
    def __repr__(self):
        return repr(self.value)
    def __str__(self):
        return "%f" % self.value
    def __nonzero__(self):
        return bool(self.value)
  #  def __getattr__(self, attr):
   #     return getattr(self.value, attr)
if __name__ == "__main__":
    fValue = MoneyFmt("123.456")
    print fValue
    fValue.update("1234")
    print fValue
    newfValue = MoneyFmt("111.222")
    print newfValue + fValue
程序输出:
>>> 
123.456000
1234.000000
234.678

这里采取的策略是:直接从float派生。经过测试,虽然有getattr,但是调用float的函数还是得显式进行调用。比如newfValue.__add__(fValue)

13-19. 映射类型子类化。假设在13.11.3 节中字典的子类,若将keys()方法重写为:
def keys(self):
return sorted(self.keys())
(a) 当方法keys()被调用,结果如何?
(b) 为什么会有这样的结果?如何使我们的原解决方案顺利工作?

class SortedKeyDict(dict):
    def keys(self):
        return sorted(self.keys())
d = SortedKeyDict((('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2)))
print d.keys()
结果非常明显:无穷递归。。。。。。

如果不调用父类的keys()方法的话,我不知道怎么改。。。。。。。。。


13-20. 类的定制。改进脚本time60.py,见13.13.2 节,示例13.3.
(a) 允许“空”实例化: 如果小时和分钟的值没有给出,默认为零小时、零分钟。
(b) 用零占位组成两位数的表示形式,因为当前的时间格式不符合要求。如下面的示例,wed
应该输出为“12:05.”
(c)除了用hours (hr) 和minutes (min)进行初始化外,还支持以下时间输入格式:
 一个由小时和分钟组成的元组(10, 30)
一个由小时和分钟组成的字典({'hr': 10, 'min': 30})
一个代表小时和分钟的字符串("10:30")
附加题: 允许不恰当的时间字符串表示形式,如 “12:5”.
(d) 我们是否需要实现__radd__()方法? 为什么? 如果不必实现此方法,那我们什么时候可
以或应该覆盖它?
(e) __repr__()函数的实现是有缺陷而且被误导的。我们只是重载了此函数,这样我们可以省
去使用print 语句的麻烦,使它在解释器中很好的显示出来。但是,这个违背了一个原则:对于可估
值的Python 表达式,repr()总是应该给出一个(有效的)字符串表示形式。12:05 本身不是一个合法
的Python 表达式,但Time60('12:05')是合法的。请实现它。
(f) 添加六十进制(基数是60)的运算功能。下面示例中的输出应该是19:15,而不是18:75:
>>> thu = Time60(10, 30)
>>> fri = Time60(8, 45)
>>> thu + fri
18:75

class Time60(object):
    def __init__(self, *args1):
        if type(args1[0]) is tuple:
            self.hr = args1[0][0]
            self.min = args1[0][1]
        elif type(args1[0]) is dict:
            self.hr = args1[0]["hr"]
            self.min = args1[0]["min"]
        elif type(args1[0]) is str:
            self.hr = int(args1[0].split(":")[0])
            self.min = int(args1[0].split(":")[1])
        elif type(args1) is tuple:
            self.hr = args1[0]
            self.min = args1[1]
    def __str__(self):
        return "%02d:%02d" % (self.hr, self.min)
    def __repr__(self):
        return repr("%02d:%02d" % (self.hr, self.min))
    def __add__(self, other):
        hour = self.hr + other.hr
        min = self.min + other.min
        if min >= 60:
            min -= 60
            hour += 1
        return self.__class__(hour, min)
    def __radd__(self, other):
        self.hr += other.hr
        self.min += other.min
        if self.min >= 60:
            self.min -= 60
            self.hr += 1
        return self
    def __iadd__(self, other):
        self.hr += other.hr
        self.min += other.min
        if self.min >= 60:
            self.min -= 60
            self.hr += 1
        return self
thu = Time60(10,30)
fri = Time60(8,35)
print thu + fri
thu = Time60((10,30))
fri = Time60({"hr":8,"min":35})
mon = Time60("10:30")
print thu + fri
print fri + mon
thu += fri
print thu
程序输出:
>>> 
19:05
19:05
19:05
19:05

粉丝  259
 
博文  203
 
码字总数  438425
评论  (9)
Ctrl+Enter 
   
红魔鬼
哥们太认真了!
红魔鬼
对于你提到的几个问题,探讨一下:


01
>>> class HideX(object):
02
def __init__(self, x):
03
self.__x = x
04
def get_x(self):
05
return ~self.__x
06
def set_x(self, x):
07
assert isinstance(x, int),"x must be an integer!"
08
self.__x = ~x
09
x = property(get_x, set_x)
10

11

12
>>> inst = HideX(20)
13
>>> print inst.x
14
-21
15
>>> inst.x = -21
16
>>> print inst.x
17
-21
18
>>> inst.x = 20
19
>>> print inst.x

我有个疑问是:为什么初始化为20的时候,会输出21呢???

这是显然的,inst = HideX(20),会调用__init__,将20赋给__x,当输出时,调用get_x,将20取反,显示-21。

其实你写的程序和书上的不一样,书上的__init__是self.x = x, 你写的是self.__x = x。按书上的写法,初始化时__init__会自动调用set_x,将20取反赋给__x(即__x为-21),输出时get_x再取反,故为20。


红魔鬼
下面的property装饰器方法,你写错了,怀疑你看的是中文版,缩进什么的都错了。应为:

class HideX(object):
def __init__(self, x):
self.x = x

def x():
def fget(self):
return ~self.__x

def fset(self, x):
assert isinstance(x, int), '"x" must be an integer!'
self.__x = ~x

return locals()

x = property(**x())
红魔鬼
class HideX(object):
......def __init__(self, x):
............self.x = x

.......def x():
............def fget(self):
..................return ~self.__x

............def fset(self, x):
..................assert isinstance(x, int), "'x' must be an int'
..................self.__x = ~x

............return locals()

........x = property(**x())

上面用点号代替缩进
红魔鬼
最后一行,x = property(**x()),x() 即会调用locals(), 返回函数x的字典,字典两个key是fget和fset。
**是关键字参数的用法。最后一行即等于x = property(fget=..., fset=...)
其余的,不难了。
fzyz_sb

引用来自“红魔鬼”的评论

对于你提到的几个问题,探讨一下:


01
>>> class HideX(object):
02
def __init__(self, x):
03
self.__x = x
04
def get_x(self):
05
return ~self.__x
06
def set_x(self, x):
07
assert isinstance(x, int),"x must be an integer!"
08
self.__x = ~x
09
x = property(get_x, set_x)
10

11

12
>>> inst = HideX(20)
13
>>> print inst.x
14
-21
15
>>> inst.x = -21
16
>>> print inst.x
17
-21
18
>>> inst.x = 20
19
>>> print inst.x

我有个疑问是:为什么初始化为20的时候,会输出21呢???

这是显然的,inst = HideX(20),会调用__init__,将20赋给__x,当输出时,调用get_x,将20取反,显示-21。

其实你写的程序和书上的不一样,书上的__init__是self.x = x, 你写的是self.__x = x。按书上的写法,初始化时__init__会自动调用set_x,将20取反赋给__x(即__x为-21),输出时get_x再取反,故为20。


确实,当时真蠢,谢谢啦。
fzyz_sb

引用来自“红魔鬼”的评论

class HideX(object):
......def __init__(self, x):
............self.x = x

.......def x():
............def fget(self):
..................return ~self.__x

............def fset(self, x):
..................assert isinstance(x, int), "'x' must be an int'
..................self.__x = ~x

............return locals()

........x = property(**x())

上面用点号代替缩进

我是直接边运行边写的,木有写成一个文件方式。
红魔鬼
即使在解释器里直接写,也是要考虑缩进的,不同的缩进会有不同的结果。

tianyi210
习题13-19中的无穷递归,就把def keys改为def skeys,最后打印的改成d.skeys()即可,就是名字覆盖了,不知道理解如何
顶部

猜你喜欢

转载自blog.csdn.net/qq_20113327/article/details/61203290