前后都使用双下划线,由这些名字组成的集合所包含的方法称为魔法(特殊)方法,如果对象实现了这些方法中的某一个,那么这个方法会在特殊的情况下被Python调用,而几乎没有直接调用他们的必要
本章内容:
魔法方法(最重要的是__init__方法和一些处理对象访问的方法,这些方法允许你创建自己的序列或者映射)
属性(在以前的版本中通过魔法方法来处理,现在则通过property函数)
迭代器(使用魔法方法__iter__来允许迭代器在for循环中使用)
为了确保类是新型的,应该把赋值语句__metaclass__=type放在模块的最开始
或者(直接或间接)子类化内建类(类型)object(或其他一些新式类)
class NewStyle(object):
more_code_here
class OldStyle:
more_code_here
如果没有兼容之前旧版Python的需要,建议将所有类都写为新式类,并使用super函数
在Python3.0中没有旧式类,也不需要显示子类化object或者将元素设置为type,所有类都会隐式地成为object子类---如果没有明确超类的话,会直接子类化,否则会间接子类化
构造方法和其他普通方法不同的地方在于,当一个对象被创建后,会立即调用构造方法
>>> f = FooBar()
>>> f.init()
构造方法可以简化
>>> f = FooBar()
在Python中构建一个构造方法,只要把init方法的名字从简单的init修改为__init__
class FooBar:
def __init__(self):
self.somevar = 42
>>> f = FooBar()
>>> f.somevar
42
还可以有多个参数,且参数可选
__del__析构方法,在对象就要被垃圾回收之前调用,但发生调用的具体时间是不可知的,应尽量避免使用。
如果一个方法在B类的一个实例中被调用(或一个属性被访问),但在B类中没有找到该方法,那么就会去它的超类A里面找
构造方法用来初始化新创建对象的状态,如果一个类的构造方法被重写,那么就需要调用超类的构造方法
考虑Bird类
Class Bird:
Def __init__(self):
Self.hungry = True
def eat(self):
if self.hungry
print ‘Aaaah...’
self.hungry = False
else :
print ‘No, thanks’
这个类定义所有的鸟都具有的一些最基本的能力,吃
>>> b = Bird()
>>> b.eat()
Aaaah...
>>> b.eat()
No, thanks
现在考虑子类SongBird,它添加了唱歌的行为
class SongBird(Bird):
def __init__(self):
self.sound = ‘Squawk!’
def sing(self):
print self.sound
SongBird和Bird类一样使用
>>> sb =SongBird()
>>> sb.sing()
Squawk
SongBird是Bird子类,继承了eat方法,但是调用eat方法就会产生一个问题:
SongBird没有hungry属性,因为构造方法被重写,新的构造方法没有任何关于初始化hungry特性的代码,因此,SongBird的构造方法必须调用其超类Bird的构造方法来确保进行基本的初始化,两种方法实现:调用超类构造方法的未绑定版本,或者使用super函数
class SongBird(Bird):
def __init__(self):
Bird.__init__(self)
self.sound = ‘Squawk!’
def sing(self):
print self.sound
在调用一个实例的方法时,该方法的self参数会被自动绑定到实例上(绑定方法),但是如果直接调用类的方法,那么就没有实例被绑定,这样可以自由提供需要的self参数(成为未绑定方法)
Super函数只能在新式类中起作用
当前类和对象可以作为super函数的参数使用,调用函数返回的对象的任何方法都是调用超类的方法,而不是当前类的方法,那么可以直接使用super(SongBird, self)
__init__方法能以一个普通的(绑定)方式被调用
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry
print ‘Aaaah...’
self.hungry = False
else :
print ‘No, thanks’
class SongBird(Bird):
def __init__(self):
Supe(SongBird, self).__init__()
self.sound = ‘Squawk!’
def sing(self):
print self.sound
魔法方法集合,它可以创建行为类似于序列或映射的对象
规则,是管理某种形式的行为的规则,说明了应该实现何种方法和这些方法应该做什么。
因为Python中的多态是基于对象的行为的(而不是基于祖先,例如他所属的类或超类),在其他语言中对象可能被要求属于某一个类,或者被要求实现某个接口,但Python中只是简单地要求它遵循几个给定的规则,因此成为了一个序列,需要的只是遵循序列的规则
序列和映射是对象的集合,为了实现它们基本的行为(规则),如果对象是不可变的,那么就需要使用两个魔法方法,如果是可变的则需要使用4个
__len__(self):这个方法应该返回集合中所含项目的数量,对于序列就是元素个数;对于映射,则是键-值对数量,如果__len__返回0(并且没有实现重写该行行为的__nonzero__),对象会被当作一个布尔变量中的假值(空的列表,元组,字符串和字典也一样)进行处理
__getitem__(self, key):这个方法返回与所给键对应的值,对于一个序列,键应该是一个0~n-1的整数(或者负数),n是序列长度,对于映射来说,可以使用任何种类的键
__setitem__(self, key, value):这个方法应该按一定的方式存储和key相关的value,该值随后可使用__getitem__来获取,当然只能为可以修改的对象定义这个方法
__delitem__(self, key):这个方法在对一部分对象使用del语句时被调用,同时必须删除和元素相关的键,这个方法也是位可修改的对象定义的(并不是删除全部的对象,而只删除一些需要移除的元素)
对这些方法的附加要求:
对于一个序列来说,如果键是负整数,那么要从末尾开始计数,x[-n]和x[len(x)-n]是一样的
如果键是不合适的类型(例如对序列使用字符串作为键),会引发一个TypeError异常
如果序列的索引是正确的类型,但超出了范围,应引发一个IndexError异常
继承,3个关于序列和映射规则(UserList,UserString,UserDict)可以立即使用的实现
如果希望实现一个和内建列表行为相似的序列,可以使用子类list
当子类化一个内建类型--比如list的时候,也就间接地将object子类化了,因此类就自动成为新式类,这就意味着可以使用像super函数这样的特性
带有访问计数的列表
class CounterList(list):
def __init__(self, *args):
super(CounterList, self).__init__(*args)
self.counter = 0
def __getitem__(self, index):
self.counter += 1
return super(CounterList, self).__getitem__(index)
CounterList类严重依赖于它的子类化超类(list)行为,CounterList类没有重写任何的方法(和append,extend,index一样)都能被直接使用,在两个被重写的方法中,super方法被用来调用相应的超类的方法,只在__init__中添加了所需的初始化counter特性的行为,并在__getitem__中更新了counter特性
重写__getitem__并非获取用户访问的万全之策,因为还有其他访问列表内容的途径,比如通过pop方法
>>> c1 = CounterList(range(10))
>>> c1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> c1.reverse()
>>> c1 [9, 8, 7, 6, 5, 4, 3, 2, 1,0]
>>> del c1[3:6]
>>> c1 [9, 8, 7, 3, 2, 1, 0]
>>> c1.counter 0
>>> c1[4] + c1[2] 9
>>> c1.counter 2
CounterList在很多方面和列表的作用一样,但它有一个counter特性(被初始化为0),每次列表元素被访问时,它都会自增,所以在执行加法c1[4] + c1[2]后,这个值自增两次,变为2
《Python参考手册》3.4节
访问器是一个简单的方法,它能够使用getHeight, setHeight来得到或者重绑定一些特性(可能是类的私有属性)
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self, size):
self.width, self.height = size
def getSize(self):
return self.width, self.height
使用
>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.getSize() (10, 5)
>>> r.setSize((150, 100))
>>> r.width 150
getSize和setSize方法一个名为size的假想特性的访问器方法,size是由width和height构成的元组
Python能隐藏访问器方法,让所有特性看起来一样,这些通过访问器定义的特性称为属性。
在新式类中使用property函数
__metaclass__ = type
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self, size):
self.width, self.height = size
def getSize(self):
return self.width, self.height
size = property(getSize, setSize)
Property函数创建了一个属性,其中访问器函数被用作参数(先是取值,然后赋值),这个属性命为size,这样一来就不再需要担心怎么实现,可以用同样的方法处理width,height,size
>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.size
(10, 5)
>>> r.size = 150, 100
>>> r.width
150
size特性仍然取决于getSize和setSize中的计算
Property函数可以用0,1,2,3或者4个参数来调用,如果没有参数,产生的属性既不可读也不可写。如果只使用一个参数调用(一个取值方法),产生的属性是只读的第3个参数(可选)是一个用于删除特性的方法(它不要参数),第4个参数(可选)是一个文档字符串
Property的四个参数分别被叫做fget,fset,fdel和doc--,如果想要一个属性是只写的,并且又一个文档字符串,能使用他们作为关键字参数
静态方法和类成员方法在创建时分别被装入Staticmethod类型和Classmethod类型的对象中,
静态方法的定义没有self参数,且能够被类本身直接调用,类方法在定义时需要名为cls的类似于self的参数,类成员方法可以直接用类的具体对象调用,但cls参数是自动被绑定到类的
__metaclass__ = type
class MyClass:
def smeth():
print ‘This is a static method’
smeth = staticmethod(smeth)
def cmeth(cls):
print ‘This is a class method of’, cls
cmeth = classmethod(cmeth)
如上为手动包装和替换的技术
在Python2.4中,为这样的包装方法引入了一个叫做装饰器的新语法(能够对任何可调用的对象进行包装,既能够用于方法也能够用于函数),使用@操作符,在方法(或函数)上方将装饰器列出,从而指定一个或更多的装饰器(多个装饰器子啊应用时的顺序与指定顺序相反)。
__metaclass__ = type
class MyClass:
@staticmethod
def smeth():
print ‘This is a static method’
@classmethod
def cmeth(cls):
print ‘This is a class method of’, cls
定义这些方法以后,可以如下使用
>>> MyClass.smeth()
This is a static method
>>> MyClass.cmeth()
This is a class method of
静态方法和类成员方法在Python中并不是想了都很重要,主要原因是大部分情况下可以使用函数或者绑定方法代替。
拦截(intercept)对象的所有特性访问是可能的,这样可以用旧式类实现属性(因为property方法不能使用),为了在访问特性的时候可以执行代码,必须使用一些魔法方法(旧式类中只需后3个)
__getattribute__(self, name):当特性name被访问时自动被调用(只能在新式类中使用)
__getattr__(self, name):当特性name被访问且对象没有相应的特性时被自动调用
__setattr__(self, name, value):当试图给特性name赋值时会被自动调用
__delattr__(self, name):当试图删除特性name时被自动调用
尽管和使用property函数相比有点复杂(而且某些方面效率更低),但这些特殊方法是很强大的,因为可以对处理很多属性的方法进行再编码
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def __setattr__(self, name, value):
If name == ‘size’:
self.width, self.height = size
else:
Self.__dict__[name] = value
def __getattr__(self, name):
If name == ‘size’:
return self.width, self.height
else:
raise AttributeError
__setattr__方法在所涉及到的特性不是size时也会被调用,因此,这个方法必须把两方面都考虑进去,如果属性是size,那么就像前面那样执行操作,否则就要使用特殊方法__dict__,该特殊方法包含一个字典,字典里面是所有实例的属性,为了避免__setattr__方法被再次调用(会使程序陷入死循环),__dict__方法被用来代替普通的特性赋值操作
__getattr__方法只在普通的特性没有被找到时调用,这就是说如果给定的名字不是size,这个特性不存在,这个方法会引发一个AttributeError异常,如果希望类和hasattr或者是getattr这样的内建函数一起正确地工作,__getattr__方法就很重要,如果使用的是size属性,那么就会使用在前面的实现中找到的表达式
就像死循环陷阱和__setattr__有关系一样,还有一个陷阱和__getattribute__有关系,因为__getattribute__拦截所有特性的访问(在新式类中),也拦截对__dict__的访问,
访问__getattribute__中与self相关的特性时,使用超类的__getattribute__方法(使用super函数)是唯一安全的途径