魔法方法、属性和迭代器
在python中,有的名称会在前面和后面各加上两个下划线,这种写法很特别。它表示名字有特殊含义,所以绝不要在自己的程序中使用这种名字。在python中,由这些名字组成的集合所包含的方法叫做魔法(或称特殊)方法。如果对象实现了这些方法中的某一个,那么这个方法会在特殊的情况下被python调用,而几乎没有直接调用它们的必要。
准备工作
为了确保类是新型的,应该把赋值语句__metaclass__=type放在你的模块的最开始,或者(直接或间接)子类化内建类(实际上是类型)object(或其他一些新式类)。
构造方法
构造方法和其他普通方法不同的地方在于,当一个对象被创建后,会立即调用构造方法。
在python中创建一个构造方法很容易。只要把init方法的名字从简单的init修改为魔法版本__init__即可。
在python所有的魔法方法中,__init__是使用最多的一个。
重写一般方法和特殊的构造方法
如果一个方法在B类的一个实例中被调用(或一个属性被访问),但在B类中没有找到该方法,那么会去它的超类A里面找。
>>> class A:
def hello(self):
print "Hello,I‘m A."
>>> class B(A):
pass
>>> a=A()
>>> b=B()
>>> a.hello()
Hello,I‘m A.
>>> b.hello()
Hello,I‘m A.
在子类中增加功能的最基本方式是增加方法。也可以重写一些超类的方法来自定义继承的行为。
>>> class A:
def hello(self):
print "Hello,I‘m A."
>>> class B(A):
def hello(self):
print "Hello,I‘m B."
>>> a=A()
>>> b=B()
>>> a.hello()
Hello,I‘m A.
>>> b.hello()
Hello,I‘m B.
重写是继承机制中的一个重要内容,对于构造方法尤其重要。构造方法用来初始化新创建对象的状态,大部分子类不仅要拥有自己的初始化代码,还要拥有超类的初始化代码。虽然重写的机制对于所有方法来说都是一样的,但是当处理构造方法比重写普通方法时,更可能遇到特别的问题:
如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则对象可能不能被正确地初始化。
父类:
>>> class Bird:
def __init__(self):
self.hungry=True
def eat(self):
if self.hungry:
print "Aaaaah"
self.hungry=False
else:
print "No,thanks!"
>>> b=Bird()
>>> b.eat()
Aaaaah
>>> b.eat()
No,thanks!
子类:
>>> class SongBird(Bird):
def __init__(self):
self.sound=‘Squawk!‘
def sing(self):
print self.sound
>>> sb=SongBird()
>>> sb.sing()
Squawk!
但如果调用eat方法,就会产生一个错误:
>>> sb.eat()
Traceback (most recent call last):
File "<pyshell#52>", line 1, in <module>
sb.eat()
File "<pyshell#39>", line 5, in eat
if self.hungry:
AttributeError: SongBird instance has no attribute ‘hungry‘
原因:在SongBird中,构造方法被重写,但新的构造方法没有任何关于初始化hungry特性的代码。为了达到预期效果,SongBird的构造方法必须调用其超类Bird的构造方法来确保基本的初始化。
有两种方法可以达到这个目的:
调用超类方法的未绑定版本;
使用super函数;
调用未绑定的超类构造方法
上一节提出的问题的解决方法:
>>> class SongBird(Bird):
def __init__(self):
Bird.__init__(self)
self.sound=‘Squawk!‘
def sing(self):
print self.sound
>>> sb=SongBird()
>>> sb.eat()
Aaaaah
>>> sb.eat()
No,thanks!
在调用一个实例的方法时,该方法的self参数会被自动绑定到实例上(这称为绑定方法)。如果直接调用类的方法(比如Bird.__init__)就没有实例会被绑定。这样就可以自由地提供需要的self参数。这样的方法称为未绑定方法。
使用super函数
如果不想坚守旧版python阵营的话,那么就应该使用super函数。它只能在新式类中使用,但不管怎样,都应该使用新式类。
当前的类和对象可以作为super函数的参数使用,调用函数返回的对象的任何方法都是调用超类的方法,而不是当前类的方法。那么就可以不用在SongBird的构造方法中使用Bird,而直接使用super(SongBird,self)。除此之外,__init__方法能以一个普通的绑定方法被调用。
对Bird例子的更新:
>>> __metaclass__=type
>>> class Bird:
def __init__(self):
self.hungry=True
def eat(self):
if self.hungry:
print "Aaaaah"
self.hungry=False
else:
print "No,thanks!"
>>> class SongBird(Bird):
def __init__(self):
super(SongBird,self).__init__()
self.sound=‘Squawk!‘
def sing(self):
print self.sound
>>> sb=SongBird()
>>> sb.sing()
Squawk!
>>> sb.eat()
Aaaaah
>>> sb.eat()
No,thanks!
成员访问
尽管__init__是目前为止提到的最重要的特殊方法,但还有一些其他的方法提供的作用也很重要。
基本的序列和映射的规则很简单,但如果要实现它们全部功能就需要实现很多魔法函数。
基本的序列和映射规则
序列和映射是对象的集合。为了实现它们基本的行为(规则),如果对象是不可变的,那么就需要使用两个魔法方法,如果是可变的,则需要使用4个。
__len__(self):这个方法应该返回集合中所含项目的数量。
对于序列来说,这是元素的个数;
对于映射来说,是键值对的数量。
如果__len__返回0(并且没有实现重写该行为的__nozero__),对象会被当作一个布尔变量中的假值(空的列表,元组,字符串和字典也一样)进行处理。
__getitem__(self.key):这个方法返回与所给键对于的值。
对于一个序列,键应该是一个0~n-1的整数,n是序列的长度;
对于映射来说,可以使用任何种类的键。
__setitem__(self,key,value):这个方法应该按一定的方式存储和key相关的value,该值随后可使用__getitem__来获取。当然,只能为可以修改的对象定义这个方法。
__delitem__(self,key):这个方法在对一部分对象使用del语句时被调用,同时必须删除和元素相关的键。这个方法也是为可修改的对象定义的(并不是删除全部的对象,而只删除一些需要移除的元素)。
属性
访问器是一个简单的方法,它能够使用getHeight、setHeight这样的名字来得到或重绑定一些特性。
python能隐藏访问器方法,让所有特性看起来一样,这些通过访问器定义的特性被称为属性。
property函数
实际上,property函数可以用0、1、2、3或者4个参数来调用。如果没有参数,产生的属性既不可读,也不可写。如果只使用一个参数调用(一个取值方法),产生的属性是只读的。第3个参数(可选)是一个用于删除特性的方法。第4个参数是一个文档字符串。property的4个参数分别被叫做fget、fset、fdel和doc。
实际上,property函数不是一个真正的函数——它是其实例拥有很多特殊方法的类。
静态方法和类成员方法
静态方法和类成员方法分别在创建时被装入Staticmethod类型和Classmethod类型的对象中。静态方法的定义没有self参数,且能够被类本身直接调用。
类方法在定义时需要名为cls的类似于self的参数,类成员方法可以直接用类的具体对象调用。但cls参数是自动被绑定到类的。
装饰器,使用@操作符,在方法(或函数)的上方将装饰器列出,从而指定一个或者更多的装饰器(多个装饰器在应用时的顺序与指定的顺序相反)。
静态方法和类成员方法在python中并不是向来都很重要,主要的原因是大部分情况下可以使用函数或绑定方法代替。
__getattr_、__setattr__和它的朋友们
拦截对象的所有特性访问是可能的,这样可以用旧式类实现属性。为了在访问特性的时候可以执行代码,必须使用一些魔法方法。
__getattribute__(self , name):当特性name被访问时自动被调用(只能在新式类中使用)。
__getattr__(self , name):当特性name被访问且对象没有相应的特性时被自动调用。
__setattr__(self , name , value):当试图给特性name赋值时会被自动调用。
__delattr__(self , name):当试图删除特性name时被自动调用。
__setattr__方法在所涉及到的特性不是size时会被调用。因此,这个方法必须把两方面都考虑进去:如果属性是size,那么就像前面那样执行操作,否则就要使用特殊方法__dict__,该特殊方法包含一个字典,字典里面是所有实例的属性。为了避免__setattr__方法被再次调用,__dict__方法被用来代替普通的特性赋值操作。
__getattr__方法只在普通的特性没有被找到的时候调用,这就是说如果给定的名字不是size,这个特性不存在,这个方法会引发一个AttributeError异常。如果希望类和hasattr或者getattr这样的内建函数一起正确地工作,__getattr__方法就很重要。如果使用的是size属性,那么就会使用在前面的实现中找到的表达式。
迭代器
迭代器规则
迭代的意思是重复做一些事很多次。到目前为止只是在for循环中对序列或字典进行迭代,但实际上也能对其他的对象进行迭代:实现__iter__方法的对象。
__iter__方法返回一个迭代器,所谓的迭代器就是具有next方法的对象。在调用next方法时,迭代器会返回它的下一个值。如果next方法被调用,但迭代器没有值可以返回,就会引发一个StopIteration异常。
迭代规则的关键是什么?为什么不使用列表?因为列表的杀伤力太大。如果有可以一个接一个地计算值的函数,那么在使用时可能是计算一个值时获得一个值——而不是通过列表一次性获取所有值。如果有很多值,列表就会占用太多的内存。但还有其他理由:使用迭代器更通用、更简单、更优雅。
从迭代器得到序列
除了在迭代器和可迭代对象上进行迭代外,还能把它们转换为序列。在大部分能使用序列的情况下,能使用迭代器替换。
生成器
生成器可以帮助读者写出非常优雅的代码,当然,编写任何程序时不使用生成器也是可以的。
生成器是一种用普通的函数语法定义的迭代器。
创建生成器
任何包含yield语句的函数称为生成器。除了名字不同以外,它的行为和普通的函数也有很大的差别。这就在于它不是像return那样那样返回值,而是每次产生多个值。每次产生一个值(使用yield语句),函数就会被冻结:即函数停在那点等待被激活。函数被激活后就从停止的那点开始执行。
递归生成器
当需要处理任意层循环时,可以使用递归方式生成器;
通用生成器
生成器是一个包含yield关键字的函数。当它被调用时,在函数体中的代码不会被执行,而会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句。yield语句意味着应该生成一个值。return语句意味着生成器要停止执行(不再生成任何东西,return语句只有在一个生成器中使用时才能进行无参数调用)。
换句话说,生成器是由两部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义的,包含yield的部分,生成器的迭代器是这个函数返回的部分。
生成器的函数返回的迭代器可以像其他的迭代器那样使用。
生成器方法
生成器的新属性是在开始运行后为生成器提供值的能力。
模拟生成器
略;
八皇后问题
生成器和回溯
生成器是逐渐产生结果的复杂递归算法的理想实现工具。没有生成器的话,算法就需要一个额外参数传递的半成品方案,这样递归调用就可以在这个方案上建立起来。如果使用生成器,那么所有的递归调用只要创建自己的yield部分。
在一些应用程序中,答案必须做很多次选择才能得出。并且程序不只是在一个层面上而必须在递归的每个层面上做出选择。
问题
八皇后问题,略;
python基础教程_学习笔记11:魔法方法、属性和迭代器