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

魔法方法、属性和迭代器

在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:魔法方法、属性和迭代器

时间: 2024-10-15 21:08:48

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

python基础教程_学习笔记9:抽象

抽象 懒惰即美德. 抽象和结构 抽象可以节省大量工作,实际上它的作用还要更大,它是使得计算机程序可以让人读懂的关键. 创建函数 函数可以调用(可能包含参数,也就是放在圆括号中的值),它执行某种行为并且返回一个值.一般来说,内建的callable函数可以用来判断函数是否可调用: >>> import math >>> y=1 >>> x=math.sqrt >>> callable(x) True >>> callab

python基础教程_学习笔记7:条件、循环、其它语句

条件.循环.其它语句 print和import 随着更加深入地学习python,可能会出现这种感觉:有些自以为已经掌握的知识点,还隐藏着一些让人惊讶的特性. 使用逗号输出 打印多个表达式,只要将这些表达式用逗号隔开即可: >>> print "age:",28 age: 28 参数之间都插入了一个空格符. 如果在结尾加上逗号,那么接下来的语句会与前一条语句在同一行打印: print "Hello,", print "World!"

python基础教程_学习笔记15:标准库:一些最爱——fileinput

标准库:一些最爱 fileinput 重要的函数 函数 描述 input([files[,inplace[,backup]]) 便于遍历多个输入流中的行 filename() 返回当前文件的名称 lineno() 返回当前(累计)的名称 filelineno() 返回当前文件的行数 isfirstline() 检查当前行是否是文件的第一行 isstdin() 检查最后一行是否来自sys.stdin nextfile() 关闭当前文件,移动到下一个文件 close() 关闭序列 fileinput

python基础教程_学习笔记17:标准库:一些最爱——time

标准库:一些最爱 time time模块所包含的函数能够实现以下功能: 获取当前时间.操作系统时间和日期.从字符串读取时间以及格式化时间为字符串. 日期可以用实数(从"新纪元"的1月1日0点开始计算到现在的秒数,新纪元是一个与平台相关的年份,对unix来说是1970年),或者是包含有9个整数的元组. 日期元组的字段含义 如元组: (2008,1,21,12,2,56,0,21,0) 表示2008年1月21日12时2分56秒,星期一,且是当年的第21天(无夏令时). 索引 字段 值 0

python基础教程_学习笔记14:标准库:一些最爱——re

标准库:一些最爱 re re模块包含对正则表达式的支持,因为曾经系统学习过正则表达式,所以基础内容略过,直接看python对于正则表达式的支持. 正则表达式的学习,见<Mastering Regular Expressions>(精通正则表达式) re模块的内容 最重要的一些函数 函数 描述 compile(pattern[,flags]) 根据包含正则表达式的字符串创建模式对象 search(pattern,string[,flags]) 在字符串中寻找模式 match(pattern,st

python基础教程_学习笔记2:序列-2

序列-2 通用序列操作 序列相加 通过加号对列表进行连接操作: 列表 >>> [1,3,4]+[2,5,8] [1, 3, 4, 2, 5, 8] 字符串 >>> '134'+'258' '134258' 元组 >>> (1,2,3)+(2,5,8) (1, 2, 3, 2, 5, 8) 元素数据类型不同的列表 >>> [[1,3],[3,9]]+[[2,2],'abc'] [[1, 3], [3, 9], [2, 2], 'abc'

python基础教程_学习笔记13:标准库:一些最爱——sys

标准库:一些最爱 sys sys这个模块让你能够访问与python解释器联系紧密的变量和函数. sys模块中一些重要的函数和变量 函数/变量 描述 argv 命令行参数,包括脚本名称 exit([arg]) 退出当前程序,可选参数为给定的返回值或者错误信息 modules 映射模块名字到载入模块的字典 path 查找模块所在目录的目录名列表 platform 类似sunos5或者win32的平台标识符 stdin 标准输入流--一个类文件对象 stdout 标准输出流--一个类文件对象 stde

python基础教程_学习笔记23:图形用户界面

图形用户界面 丰富的平台 在编写Python GUI程序前,需要决定使用哪个GUI平台. 简单来说,平台是图形组件的一个特定集合,可以通过叫做GUI工具包的给定Python模块进行访问. 工具包 描述 Tkinter 使用Tk平台.很容易得到.半标准. wxpython 基于wxWindows.跨平台越来越流行. PythonWin 只能在Windows上使用.使用了本机的Windows GUI功能. JavaSwing 只能用于Jython.使用本机的Java GUI. PyGTK 使用GTK

python基础教程_学习笔记3:元组

元组 元组不能修改:(可能你已经注意到了:字符串也不能修改.) 创建元组的语法很简单:如果用逗号分隔了一些值,那么你就自动创建了元组. >>> 1,3,'ab' (1, 3, 'ab') 元组也是(大部分时候是)通过圆括号括起来的. >>> (1,3,'13') (1, 3, '13') 空元组可以用没有内容的两个圆括号来表示. 如何实现包括一个值的元组呢? >>> (5) 5 >>> ('ab') 'ab' >>>