打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
第九章:魔法方法、属性和迭代器
在Python中,有的名称会在前面和后面都加上两个下划线,这种写法很特别。由这些名字组成的集合所包含的方法称为魔法(或特殊)方法。
本章会详细讨论一些重要的魔法方法,(最重要的是__init__方法和一些处理对象访问的方法,这些方法允许你创建自己的序列或者是映射)。

一、准备工作
考虑下面两个类


在这两个类中,NewStyle是新式的类,OldStyle是旧式的类。如果文件以__metaclass__=type开始,那么两个类都是新式类。
注:在Python3.0中没有“旧式”的类,也不需要显式地子类化object或者将元类设置为type,所有的类都会隐式地成为object的子类——如果没有明确超类的话,就会直接子类化;否则会间接子类化。

二、构造方法
在Python中创建一个构造方法很容易。只要把init方法的名字从简单的init修改为魔法版本__init__即可:

如果给构造方法传几个参数的话

参数可选

注:在Python中有一个魔法方法叫__del__,也就是析构方法。它在对象就要被垃圾回收之前调用。但发生调用的具体时间是不可知的。所以建议尽力避免使用__del__函数。

1、重写一般方法和特殊的构造方法
每个类都可能有一个或者多个超类,它们在从超类那里继承行为方式。如果一个方法在B类的一个实例中被调用(或一个属性被访问),但在B类中没有找到该方法,那么就会去它的超类A里面找。


A类定义了一个叫做hello的方法,被B类继承。

因为B类没有定义自己的hello方法,所以当hello被调用时,原始的信息就被打印出来。

在子类中增加功能的最基本的方式就是增加方法。但是也可以重写一些超类的方法来自定义继承的行为。B类也能重写这个方法。

使用这个定义,b.hello()能产生一个不同的结果

重写是继承机制中的一个重要内容,对于构造方法尤其重要。构造方法用来初始化新创建对象的状态,大多数子类不仅要拥有自己的初始化代码,还要拥有超类的初始化代码。

如果一个类的构造方法被重写,那么就需要调用超类(所继承的类)的构造方法,否则对象可能不会被正确的初始化。

考虑一下Bird类:



就像上面看到的,鸟吃过了之后就不再饥饿。现在考虑子类SongBird,它添加了唱歌的行为

SongBird类和Bird类一样容易使用:

因为SongBird是Bird的一个子类,它继承了eat方法,但如果调用了eat方法,就会产生问题

错误:在SongBird中,构造方法被重写,但新的构造方法没有任何关于初始化hungry特性的代码。为了达到预期的效果,SongBird的构造方法必须用其超类Bird的构造方法来确保进行基本的初始化。

2、调用为绑定的超类构造方法


通过将当前的实例作为self参数提供给未绑定方法,SongBird就能够使用其超类构造方法的所有实现,也就是说属性hungry能被设置。

3、使用super函数
当前的类和对象可以作为super函数的参数使用,调用函数返回的对象的任何方法都是调用超类的方法,而不是当前类的方法。那么就可以不用在SongBird的构造方法中使用Bird,而直接使用super(SongBird,self)。除此之外,__init__方法能以一个普通的方式被调用。
注:在Python3.0中,super函数可以不用任何参数进行调用,功能依然具有“魔力”

下面的例子是对bird例子的更新

这个新式的版本的运行结果和旧版本的一样:


三、成员访问

3.1基本的序列和映射规则
序列和映射是对象的集合。为了实现它们基本的行为(规则),如果对象是不可变的,那么就需要使用两个魔法方法,如果是可变的则需要使用4个。

__len_(self):这个方法应该返回集合中所含项目的数量。对于序列来说,这就是元素的个数;对于映射来说,则是键值对的数量。如果__len__返回0,对象会被当做一个布尔变量中的假值进行处理。
__getitem__(self,key):这个方法返回与所给键对应的值。对于一个序列,键应该是0~n-1的整数,n是序列的长度;对于映射来时,可以使用任何种类的键。
__setitem__(self,key,value):这个方法应该按一定的方式存储和key相关的value,该值随后可使用__getitem__来获取。当然,只能为可以修改的对象定义这个方法。
__delitem__(self,key):这个方法也是可修改的对象定义的(并不是删除全部的对象,而只删除一些需要移除的元素)。

对这些方法的附加要求:
对于一个序列来讲,如果键是负整数,那么要从末尾开始计数。换句话说就是x[-n]和x[len(x)-n]是一样的;
如果键是不合适的类型,会引发一个TypeError异常;
如果序列的索引是正确的类型,但超出了范围,应该引发一个Indexerror异常。

创建一个无穷序列:

下面是如何使用这个类的例子:

注意,没有实现__del__方法的原因是我希望删除元素是非法的:

这个类没有__len__方法,因为它是无限长的。
如果使用了一个非法类型的索引,就会引发TypeError异常,如果索引的类型是正确的但超出了范围,则会引发IndexError异常:


索引检查是通过用户自定义的fcheckIndex函数实现的。

3.2子类化列表,字典和字符串
如果希望实现一个和内建列表行为相似的序列,可以使用子类list
注:当子类化一个内建类型——比如list的时候,也就间接地将object子类化了,因此的类就自动成为新式类,这就意味着可以使用像super函数这样的特性了。

例子——带有访问计数的列表

CounterList类严重依赖于它的子类化超类(list)的行为。CounterList类没有重写任何的方法(和append、extend、index一样)都能被直接使用。在两个被重写的方法中,super方法被用来调用相应的超类的方法,只在_init_中添加了所需的初始化counter特性的行为,并在__getitem__中更新了counter特性。

注:重写__getitem__并非获取用户访问的完全之策,因为还有其他访问列表内容的途径,比如通过pop方法。


正如看到的,CounterList在很多方面和列表的作用一样,但它有一个counter特性(被初始化为0),每次列表元素被访问时,它都会自增。

四、更多魔力
魔法名称的用途有很多——目前演示的只有所有用途中的一小部分。大部分的特殊方法都是为高级的用法准备的。

五、属性
访问器是一个简单的方法,它能够使用getHeight、setHeight这样的名字得到或者重绑定一些特性。

下面的例子演示如何使用这个类

上面的例子中,getSize和setSize方法一个名为size的假想特性的访问器方法,size是width和height构成的元组。

5.1、property函数

这里property函数创建了一个属性,其中访问器函数被用作参数(先取值,然后赋值),这个属性命为size。


5.2、静态方法和类成员方法
静态方法的定义没有self参数,且能够被类本身直接调用。类方法在定义时需要名为cls的类似于self的参数,类成员方法可以直接用类的具体对象调用。但cls参数是自动被绑定到类的

手动包装和替换方法的技术看起来有点单调。为这样的包装方法引入了一个叫做装饰器的新语法。使用@操作符,在方法(或函数)的上方将装饰器列出,从而指定一个或者更多的装饰器。

定义了这些方法后,就可以像下面的例子那样使用

静态方法和类成员方法在Python中并不是向来都很重要,主要的原因是大部分情况下可以使用函数或者绑定方法代替。

5.3、__getattr__、__setattr__和它的朋友们

__getattribute__(self,name):当特性name被访问时自动被调用(只能在新式类中使用)
__getattr__(self,name):当特性name被访问且对象没有相应的特性时被自动调用
__setattr__(self,name,value):当试图给特性name赋值时会被自动调用
__delattr__(self,name):当试图删除特性name时被自动调用

使用特殊方法的Rectangle的例子


六、迭代器
 6.1、迭代器规则
迭代的意思是重复做一些事很多次——就像循环中做的那样。
为什么使用迭代而不使用列表。如果有可以一个接一个地计算值的函数,那么在使用时可能是计算一个值时获取一个值——而不是通过列表一次性获取所有值。如果有很多值,列表就会占用太多内存。

注:正式的说法是,一个实现了__iter__方法的对象是可迭代的,一个实现了next方法的对象则是迭代器。

因为设置了break,所以循环在这里停止了,否则循环会一直继续下去

提示:内建函数iter可以从可迭代的对象中获得迭代器

6.2、从迭代器得到序列
除了在迭代器和可迭代对象上进行迭代外,还能把它们转换为序列。


七、生成器
生成器是一种用普通的函数语法定义的迭代器。
7.1、创建生成器

换句话说就是一个列表的列表。函数应该按顺序打印出列表中的数字。

接下来可以通过在生成器上迭代来使用所有的值

或者


7.2、递归生成器


这样如果nested是一个类似于字符串的对象,那么它就是一个序列,不会引发TypeError,但是你不想对这样的对象进行迭代。
下面是加入了检查语句的生成器

如果表达式nested+''引发了一个TypeError,它就会被忽略。然而如果没有引发TypeError,那么内层try语句中的else子句就会引发一个它自己的TypeError异常。这就会按照原来的样子生成类似于字符串的对象。


7.3、通用生成器
生成器是由两部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义的,包含yield的部分,生成器的迭代器是这个函数返回的部分。按一种不是很准确的说法,两个实体经常被当做一个,合起来叫做生成器。


7.4、生成器方法
生成器的新属性是在开始运行后为生成器提供值的能力。表现为生成器和“外部世界”进行交流的渠道。

外部作用域访问生成器的send方法,就像访问next方法一样,只不过前者使用一个参数。

在内部则挂起生成器,yield现在作为表达式而不是语句使用,换句话说,当生成器重新运行的时候,yield方法返回一个值,也就是外部通过send方法发送的值。如果next方法被使用,那么yield方法返回 None。

使用方法如下:


注意看yield表达式周围的括号使用。虽然并未严格要求,但在使用返回值的时候,安全起见还是要闭合yield表达式。

生成器还有其他两个方法:
throw方法(使用异常类型调用,还有可选的值以及回溯对象)用于在生成器内引发一个异常(在yield表达式中)
close方法(调用时不用参数)用于停止生成器。

7.5、模拟生成器
下面是flatten生成器用普通的函数重写的版本


八、把皇后问题
本节会介绍如何使用生成器解决经典的编程问题
8.1、生成器和回溯
生成器是逐渐产生结果的复杂递归算法的理想实现工具。没有生成器的话,算法就需要一个作为额外参数传递的半成品方案,这样递归调用就可以在这个方案上建立起来。如果使用生成器,那么所有的递归调用只要创建自己的yield部分。

8.2、问题
这是一个深受喜爱的计算机科学谜题:有一个棋盘和8个要放到上面的皇后。唯一的要求是皇后之间不能形成威胁。也就是说,必须把它们放置成每个皇后都不能吃掉其他皇后的状态。皇后要如何放置?

8.3、状态表示
为了表示一个可能的解决方案(或者方案的一部分),可以使用元组(或者列表)。每个元组中元素都指示相应行的皇后的位置(也就是列)。

8.4、寻找冲突
首先从一些简单的抽象开始。为了找到一种没有冲突的设置,首先必须定义冲突是什么,为什么不把它定义成一个函数。
已知的皇后的位置被传递给conflict函数(以状态元组的形式),然后由函数判断下一个的皇后位置会不会有新的冲突。

参数nextX代表下一个皇后的水平位置(x坐标或列),nextY代表垂直位置(y坐标或行)。这个函数对前面的每个皇后的位置做一个简单的检查,如果下一个皇后和前面的皇后有同样的水平位置,或者是在一条对角线上,就会发生冲突,接着返回True。如果没有这样的冲突发生,那么返回False。

8.5、基本情况
八皇后问题的实现虽然有点不太好实现,但如果使用了生成器就没什么难的了。



8.6、需要递归的情况



如果用8个皇后做参数来运行queens,会看到很多解决方案闪过。

8.7、打包
清理输出总是一个好习惯,因为这样很容易发现错误

注意prettyprint中创建了一个小的助手函数。是因为我们假设在外面的任何地方都不会用到它。


九、小结

旧式类和新式类:Python中类的工作方式正在发生变化。3.0版本以前的Python内有两种类,旧式类已经过时,新式类在2.2版本中被引入,它提供了一些新的特征(比如使用super函数和property函数,而旧式类就不能)。为了创建新式类,必须直接或者间接子类化object,或设置__metaclass__属性也可以。

魔法方法:在Python中有一些特殊的方法(名字是以双下划线开始和结束的)。这些方法和函数只有很小的不同,但其中的大部分方法在某些情况下被Python自动调用。

构造方法:这是面向对象的语言共有的,可能要为自己写的每个类实现构造方法。构造方法被命名为__init__并且在对象被创建后立即自动调用。

重写:一个类能通过实现方法来重写它的超类中定义的这些方法和属性。如果新方法要调用重写版本的方法,可以从超类(旧式类)直接调用未绑定的版本或者使用super函数(新式类)

序列和映射:创建自己的序列或者映射需要实现所有的序列或是映射规则的方法,包括__getitem__和__setitem__这样的特殊方法。通过子类化list(或者UserList)和dict(或者UserDict)能节省很多工作。

迭代器:迭代器是带有next方法的简单对象。迭代器能在一系列的值上进行迭代。当没有值可以迭代时,next方法就会引发StopIteration异常。

生成器:生成器函数(或者方法)是包含了关键字yield的函数(或方法)。当被调用时,生成器函数返回一个生成器(一种特殊的迭代器)。可以使用send、throw和close方法让活动生成器和外界交互。

八皇后问题:八皇后问题在计算机科学领域内无人不知,使用生成器可以很轻松的解决这个问题。问题描述的是如何在棋盘上放置8个皇后,使其不会互相攻击。










































本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
一名神级程序员花了半个月:把这本书的全部重点都整理好了!超长
适合Java开发者学习的Python入门教程
Python高级用法总结—(列表推导式,迭代器,生成器,装饰器)
迭代、可迭代对象、迭代器
你知道Python迭代器和生成器的区别联系吗?五分钟带你叱咤江湖
Python中迭代器和生成器的区别?
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服