频道栏目
首页 > 资讯 > Python > 正文

python基础教程_学习笔记11:魔法方法、属性和迭代器

14-06-17        来源:[db:作者]  
收藏   我要投稿

魔法方法、属性和迭代器

在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 "", line 1, in

sb.eat()

File "", 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部分。

在一些应用程序中,答案必须做很多次选择才能得出。并且程序不只是在一个层面上而必须在递归的每个层面上做出选择。

问题

八皇后问题,略;

相关TAG标签
上一篇:算法7-1:图论简介
下一篇:hdu2665-Kth number
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站