文章目录
魔法方法、属性和迭代器
在python中,有些使用了__name__
写法的名称,由这些名字所组成的名字组成的集合所包含的方法称为魔法(或特殊)方法。如果对象实现了这些方法中的某一个或者某些, 那么这个方法会在某些特殊的时候被python调用。
- 本章讨论了一些重要的魔法方法。另外还讲述了两个相关的主题:属性、迭代器
- 关于魔法方法,最重要的是init方法和一些处理对象访问的方法,这些方法允许你创建自己的序列或者映射
- 关于属性,旧版本中使用魔法属性,而新版本中则通过property函数
- 关于迭代器,使用iter来允许迭代器在for循环中使用
9.1 准备工作
很久以前,python的对象的工作方式就有了很大的改变。虽然对大多数刚开始使用python的人来说这些改变并不是很重要,但是一些特性(比如super和属性)不会再老式的类上起作用,需要metaclass=type放在模块的最开始或者子类化内建的类
- 以上问题在python3中不成为问题,因为所有的类都是新式的类了
- 如果不明确的指明超类的话,就会直接默认为object的子类
9.2 构造方法
构造方法
在python中创造一个构造方法很容易,如下:
1 2 3 4 |
class Foobar: def __init__(self,value): self.somevar=value |
析构方法
python中也有析构方法 del ,它在对象就要被垃圾回收之前调用,但是具体调用的时间是不可知的,读者应该尽力避免使用它
9.2.1 重写一般方法和特殊的构造方法
1 2 3 4 |
#子类使用如下方法表示 class b(a): pass |
- 重写——在方法继承的过程中,顺序类似于css的层叠
- 进行重写时,新的构造方法并不会默认的继承超类的方法
- 为了实现预期的效果,需要调用超类方法的未绑定版本或者使用 super 函数
9.2.2 调用未绑定的超类构造方法
历史遗留
使用未绑定的超类构造方法,主要是历史遗留问题
内容
1 2 3 4 5 6 7 8 |
class SonghBird(Bird): def __init__(self): bird.__init__(self) self.sound='Squawk' def sing(self): print bird.sound ... |
原因是在调用一个实例的方法时,该方法的 self 参数会被自动绑定到实例上,称为绑定方法。如果直接调用类的方法,就不会发生这样的绑定,这样就可以自由的提供需要的self 参数
9.2.3 使用 super 函数
使用
- super函数只能在新式类中出现
- 当前的类和对象可以作为 super 函数的参数使用,调用函数返回的任何方法都是调用超类的方法,而不是当前类的方法
- 在python3中, super 函数可以不带任何参数进行调用,功能仍然具有魔力
1 2 3 4 5 6 |
class SongBird(Bird): def __init__(self): super(SongBird,self).__init__() self.sound="Squark" ... |
相关知识
在我看来,super 函数比直接在超类中直接调用未绑定的方法更加直观,但是这并不是它的唯一优点。 super函数实际上非常智能,即使类已经继承了多个超类,他也只需要使用一次 super 函数,但要确保所有的超类继承方法都使用了 super 函数
在一些含糊的情况下(比如两个超类共同继承了一个超类)使用未绑定的超类构造方法非常别扭,然而新式类和 super 函数可以自动处理,具体的实现原理可以不用了解
实际上, super 函数返回的是 super 对象,它负责进行方法的解析,当对其特性进行解析时,它会查找所有的超类以及超类的超类,直到找到所需的方法为止,或者引发一个AttributeError错误
9.3 成员访问
规则
- 规则(propotcol)这个词在python中经常使用,用来描述管理某种形式的行为的规范。
- 它说明了应该实现何种方法和这些方法应该做什么
- python的多态性由于是基于对象的行为的,于是只简单地要求对象遵守几个给定的规则:比如为了成为一个序列,对象只需要遵守序列的规则
9.3.1 基本的序列和映射规则
序列(sequence)和映射(mapping)是对象的集合,为了实现他们的规则,就需要使用一些魔法方法:
- len(self)
- 这个方法返回集合中所含项目(元素/键-值对)的数量
- 如果len返回0,并且没有实现重写该行为的nonzero,对象会被当做bool值中的假值进行处理
- getitem(self,index/key)
- setitem(self,index/key,value)
- 只能为可修改的对象定义这个方法
- delitem(self,key)
- 删除部分项
对这些方法的附加要求如下
- 对于一个序列来说,如果index是负整数则倒序计数
- 如果键的类型,会引发 TypeError
- 如果索引超出了范围会引发 IndexError
- index允许在分片中使用非整形限制,只要想处理基本序列规则之外的事情,那么这个规则尤其有用
9.3.2 子类化列表,字典和字符串
实现python官方规范推荐实施的特殊方法和普通方法,是一件繁重的劳动,很难以做好.如果想要懒惰的实现,继承是一个好办法
- 标准库有三个关于序列和映射的规则(UserList,UserString和UserDict)可以立即使用的实现
- 在新版本的python中,可以子类化内建类型
9.4 更多魔力
魔法名称的用途很多,更多内容参考 <<python参考手册>>
9.5 属性
访问器方法
访问器是一个简单的方法,它能够使用getHeight,setHeight这样的名字来得到或者重新绑定一些特性,甚至可以是类的私有属性
1 2 3 4 5 6 7 8 9 |
class Recentage: def __init__(self): self.width=0 self.height=0 def setSize(self,size): self.width,self.height=size def getSize(self): return self.width,self.height |
这样的实现有缺陷,如果有一天类要改变,可能所有相关的代码都要重写,但python能隐藏访问器方法,让所有特性看起来一样,这些通过访问器定义的特性被称之为属性
9.5.1 property函数
1 2 3 4 5 6 7 8 9 10 11 |
#如果已经定义了上一节的类 class Recentage: def __init__(self): self.width=0 self.height=0 def setSize(self,size): self.width,self.height=size def getSize(self): return self.width,self.height size=property(getSize,setSize) |
在这个新版的类中,property函数创建了一个属性,其中访问器函数被用作参数
property函数参数
实际上,property函数可以用1-4个参数调用,如果没有参数,产生的属性既不可读也不可写,一个取值方法调用则为可读的,第三个参数(可选)是一个用于删除参数的方法(它不要参数),第四个参数是一个文档字符串
工作原理
- property并不是真正的函数,它其实是拥有很多特殊方法的类
- 涉及的方法有get,set,delete,这三个特殊方法在一起构成了描述符规则,其中任何一个方法的对象就叫做描述符
9.5.2 静态方法和类成员方法
- 静态方法和类成员方法分别在被创建时装入staticmethod类型和classmethod类型的对象中.
- 静态方法的定义没有self函数,且能够被类本身直接调用.他在被调用时,需要名为cls的类似于self的参数
- 类成员方法可以直接用类的具体对象调用,但cls参数是自动绑定到类的
1 2 3 4 5 6 7 8 9 |
class MyClass: def smeth(): print("this is a static method") smeth=staticmethod(smeth) def cmeth(cls): print('this is a class method of',cls) cmeth=classmethod(cmeth) |
装饰器
手动包装和替换的方法看起来有些单调.在python2.4中,为这样的包装方法引入了一个叫做装饰器(decorator)的语法糖: 它能够对任何可调用的对象进行包装,既可以用于方法,也可以用于函数.
装饰器使用
- 使用装饰器操作符@,在方法或者函数的上方将装饰器列出,从而制定一个或者更多的装饰器
- 多个装饰器在应用时和指定时的顺序是相反的
1 2 3 4 5 6 7 8 9 |
class MyClass: @staticmethod def smeth(): print("this is a static method") @classmethod def cmeth(cls): print('this is a class method of',cls) |
9.5.3 getattr,setattr和它们的朋友们
在python2中拦截(intercept)对象所有的特性访问是可能的,这样可以使用旧式类实现属性
为了在访问特性的时候可以执行代码,必须使用一些魔法方法
- getattribute(self,name)
- 当特性name被访问时自动被调用,只能在新式类中使用
- getattr(self,name)
- 当特性name被访问但对象没有特定特性时使用
- setattr(self,name,value)
- 当试图给name赋值时被自动调用
- delattr(self,name)
- 当试图删除特性name时被调用
尽管和使用property函数相比有点复杂,而且在某些方面效率更低,但是这些特殊方法很强大,因为可以对处理很多属性的方法进行再编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Recentage: def __init__(self): self.width=0 self.height=0 def __setattr__(self,name,value): if name=='size': self.width,self.width=size else: self.__dict__[name]=value def __getattr__(self,name): if name=='size': return self.width,self.height else: raise AttributeError |
一些细节和注意
- setattr当特性不是size时也会调用,所以必须把两方面都考虑到
- dict包含所有实例的属性,为了避免setattr被重复调用引起死循环而设置
- getattr只在普通的特性没有找到时调用. 如果希望类和hasattr,getattr这样的内建函数一起工作,这个方法就非常重要
- 因为getattribute拦截所有特性的访问,也拦截对dict的访问,因此访问getattribute中self相关特性时,使用超类getattribute方法(使用super函数)是唯一安全的途径
9.6 迭代器
本节只讨论一个特殊方法iter,这个方法是迭代器规则的基础
9.6.1 迭代器规则
- 迭代的意思是重复做一些事很多次
- 只要该对象实现了iter方法,就可对它进行迭代
- iter方法会返回一个迭代器(iterator)
- 所谓迭代器就是具有next方法的对象
- 如果next方法被调用,但是迭代器没有值可以返回,就会引发一个StopIteration异常
- python3中,迭代器应该实现next方法,而不是next,于是it.next()被替换为next(it)
为什么使用迭代器而不是列表?
因为迭代器可以实现类似惰性求值的效果,而列表会一次性计算出所有值,消耗太大
可迭代对象(iterable)和迭代器(iterator)
实现iter的是可迭代对象,实现next的是迭代器
1 2 3 4 5 6 7 8 9 10 |
class Fibs: def __init__(self): self.a=0 self.b=1 def __next__(self): self.a,self.b=self.b,self.b+self.a return self.a def __iter__(self): return self |
9.6.2 从迭代器得到序列
- 除了在迭代器和可迭代对象上进行迭代外,还可以将他们转化为序列
- 在大部分能使用序列的地方,都可以使用可迭代对象
9.7 生成器
- 生成器是python新引入的概念,由于历史原因,它也叫简单生成器
- 生成器是概念更加高级一些的语法糖
- 生成器是一种用普通的语法定义的生成器
9.7.1 创建生成器
1 2 3 4 5 |
def flatten(nested): for sublist in nested: for element in sublist: yield element |
yield与return的差别在于每次产生一个值,然后函数被冻结在那个时刻,等待被唤醒然后继续执行
循环生成器
生成器推导式和列表推导式的工作方式类似,只不过返回的是生成器,并且类似haskell的列表是惰性产生
- 一般严格求值使用列表推导式比较好
- 惰性求值使用生成器推导式比较好
更妙的是生成器推导式可以直接在支持可迭代对象的当前圆括号内使用
9.7.2 递归生成器
如果要处理任意层的嵌套,可以使用递归生成器
1 2 3 4 5 6 7 8 |
def flatten(nested): try: for sublist in nested: for element in flatten(sublist): yield element except TypeError: yield nested |
注意
- 不应该在这个函数中对非原子对象(例如字符串)进行迭代,首先他们可能和列表本身类型相同,不会引发TypeError,其次可能导致无穷递归
- 为了防止这一现象,可以在生成器开头使用try/except语句进行检查
9.7.3 通用生成器
生成器由两部分构成,生成器的函数和生成器的迭代器,生成器的函数是用def语句定义的,包含yield的部分,生成器的迭代器是这个函数返回的部分
9.7.4 生成器方法
生成器的新特征是在开始运行后为生成器提供值的能力
– 外部作用域访问生成器的send方法,使用一个参数将任意对象作为要向外界发送的消息
– 在内部则挂起生成器,yield现在作为表达式而非语句使用,当生成器重新运行的时候,yield方法返回一个值,也就是通过send发送的值.如果next被使用,则返回None
– 使用send方法只有在生成器挂起之后才有意义
– 如果对刚启动的生成器使用send方法,可以将None作为参数调用
1 2 3 4 5 6 7 8 9 10 11 |
def repeater(value): while True: new=(yield value) if new is not None:value=new >>> r=repeater(42) >>> next(r) 42 >>> r.send("Hello world") "Hello world" |
其他方法
- throw 使用异常类型调用,还有可选的值和可回溯对象,用于在生成器内引发异常
- close 不需要参数,用于停止生成器
(close引发GeneratorExit异常,捕捉后必须重新引发\引发另一个异常\直接返回,close调用后在生成则会RuntimeError)
9.7.5 模拟生成器
可以使用列表L.append()来模拟非惰性生成器