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

版权声明:本文为博主原创文章,未经博主同意不得转载。

https://blog.csdn.net/signjing/article/details/31417309

魔法方法、属性和迭代器

在python中,有的名称会在前面和后面各加上两个下划线,这种写法非常特别。

它表示名字有特殊含义。所以绝不要在自己的程序中使用这种名字。在python中,由这些名字组成的集合所包括的方法叫做魔法(或称特殊)方法。假设对象实现了这些方法中的某一个,那么这种方法会在特殊的情况下被python调用,而差点儿没有直接调用它们的必要。

准备工作

为了确保类是新型的,应该把赋值语句__metaclass__=type放在你的模块的最開始,或者(直接或间接)子类化内建类(实际上是类型)object(或其它一些新式类)。

构造方法

构造方法和其它普通方法不同的地方在于。当一个对象被创建后,会马上调用构造方法。

 

在python中创建一个构造方法非常easy。仅仅要把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语句),函数就会被冻结:即函数停在那点等待被激活。函数被激活后就从停止的那点開始运行。

递归生成器

当须要处理随意层循环时,能够使用递归方式生成器;

通用生成器

生成器是一个包括yieldkeyword的函数。当它被调用时。在函数体中的代码不会被运行。而会返回一个迭代器。每次请求一个值,就会运行生成器中的代码。直到遇到一个yield或者return语句。

yield语句意味着应该生成一个值。

return语句意味着生成器要停止运行(不再生成不论什么东西,return语句仅仅有在一个生成器中使用时才干进行无參数调用)。

 

换句话说,生成器是由两部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义的,包括yield的部分,生成器的迭代器是这个函数返回的部分。

 

生成器的函数返回的迭代器能够像其它的迭代器那样使用。

生成器方法

生成器的新属性是在開始运行后为生成器提供值的能力。

模拟生成器

略;

八皇后问题

生成器和回溯

生成器是逐渐产生结果的复杂递归算法的理想实现工具。

没有生成器的话。算法就须要一个额外參数传递的半成品方案,这样递归调用就能够在这个方法上建立起来。假设使用生成器。那么全部的递归调用仅仅要创建自己的yield部分。

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

问题

八皇后问题,略。

上一篇:python基础教程_学习笔记10:异常


下一篇:python基础教程_学习笔记18:标准库:一些最爱——shelve