《Python基础教程》学习笔记 Chp9 魔法方法、属性和迭代器

在Python中,有的名称会在前面和后面加上两个下划线(例如future),这种瓶邪表示名字有特殊含义,所以绝不要在自己的程序中使用这种名字。在Python中,有这些名字组成的集合所包含的方法称为魔法(或特殊)方法。如果对象实现了这些方法中的一个,那么这个方法在特殊的情况下(确切地说是根据名字)被Python调用。而几乎没有直接调用它们的必要。

1.构造方法
首先要讨论的第一个魔法方法是构造方法。在Python中创建一个构造方法很容易,只要把init方法的名字从简单的init修改为魔法版本_init_即可,而且也可以给构造方法传递参数。例:

>>> class FooBar:
...     def __init__(self):
...         self.somevar = 42
...         
>>> f = FooBar()
>>> f.somevar
42
>>> class FooBar:
...     def __init__(self, value = 42):
...         self.somevar = value
...         
>>> f1 = FooBar()
>>> f1.somevar
42
>>> f2 = FooBar('argument ')
>>> f2.somevar
'argument '

注:Python中有一个魔法方法叫做del,也就是析构方法。它在对象就要被垃圾回收之前调用。但是发生的具体时间是不可知的,所以应尽力避免使用del函数。
重写一般方法和特殊的构造方法
重写一般方法。例:

>>> class A:
...     def hello(self):
...         print "Hello, I'm A."
...         
>>> class B(A):
...     def hello(self):
...         print "Hello, I'm B."
...         
>>> a = A()
>>> a.hello()
Hello, I'm A.
>>> b = B()
>>> b.hello()
Hello, I'm B.

重写特殊的构造方法。例:

>>> class A:
...     def __init__(self):
...         print "Hello, I'm A."
...         
>>> class B(A):
...     def __init__(self):
...         print "Hello, I'm B."
...         
>>> a = A()
Hello, I'm A.
>>> b = B()
Hello, I'm B.

调用未绑定的超类构造方法
结合例子来了解更清楚。例:

>>> class Bird:
...     def __init__(self):
...         self.hungry = True
...     def eat(self):
...         if self.hungry:
...             print 'aaaaa.......'
...             self.hungry = False
...         else:
...             print 'No. Thanks !'
...             
>>> class SongBird(Bird):
...     def __init__(self):
...         Bird.__init__(self)     #这一句是关键代码
...         self.sound = 'jojlfa'
...     def sing(self):
...         print self.sound
...         
>>> sb = SongBird()
>>> sb.sing()
jojlfa
>>> sb.eat()
aaaaa.......
>>> sb.eat()
No. Thanks !

分析说明:如果没有添加上例中的关键性的那句代码,由于SongBird重写的构造函数,并且没有任何关于初始化hungry特性的代码,所以调用eat方法的时候就会产生异常。在调用一个实例的方法时,该方法的self参数会被自动绑定到实例上(这称为绑定方法)。但是如果直接调用类的方法(例如Bird.init),那么就没有实例会被绑定。这样就可以自由地提供需要的self参数。这样的方法称为未绑定(unbound)方法,也就是标题中所提到的。通过将当前的实例作为self参数提供给未绑定的方法,SongBird就能够使用超类构造方法的所有实现,也就是属性hungry被被设置。

使用super函数
如果不想使用上述的方法,可以使用super函数。将当前的类和对象作为super函数的参数使用。例:

>>> __metaclass__ = type      #super函数指在新式类中起作用
>>> class Bird:
...     def __init__(self):
...         self.hungry = True
...     def eat(self):
...         if self.hungry:
...             print 'aaaaa.......'
...             self.hungry = False
...         else:
...             print 'No. Thanks !'
...             
>>> class SongBird(Bird):
...     def __init__(self):
...         super(SongBird, self).__init__()
...         self.sound = 'jojlfa'
...     def sing(self):
...         print self.sound
...         
>>> sb = SongBird()
>>> sb.sing()
jojlfa
>>> sb.eat()
aaaaa.......
>>> sb.eat()
No. Thanks !

2.成员访问
规则(protocol)这个词在Python中经常使用,用来描述管理某种形式的行为的规则。规则说明了应该实现何种方法和这些方法应该做什么。

基本的序列和映射规则
序列和映射是对象的集合。为了实现他们基本的行为(规则),如果对象是不可变的,那么就需要使用两个魔法方法,如果是可变的则需要使用4个。
_len_(self):这个方法应该返回集合中所含项目的数量。对于序列来说,这就是元素的个数;对于映射来说,则是键-值对的数量。如果len返回0(并且没有实现重写该行为的nonzero),对象会被当做一个布尔变量中的假值(空的列表、元组、字符串和字典也一样)进行处理。
_getitem_(slef, key):这个方法返回与所给键对应的值。对于一个序列,键应该就是一个0~n-1的整数(或者想后面所说的负数),n是序列的长度;对于映射来说,可以使用任何种类的键。
_setitem_(self, key, value):这个方法应该按一定的方式存储和key相关的value,该值随后可以使用getitem_来获取。当然,只能为可以修改的对象定义这个方法。
_delitem_(slef, key):这个方法在对一部分对象使用del语句时被调用,同事必须删除和键相关的键。这个方法也是为可以修改的对象定义的(并不是删除全部的对象,而是删除一些需要移除的元素)。

创建一个无穷序列,例:

>>> def checkIndex(key):
...     """Check whether the given key is legal ?
...     If it's legal , it's must be a non-negative integer. 
...     If it's not a integer , it will cause TypeError.
...     If it's a negative number , it will cause IndexError.
...     (Because sequence has infinite length.)
...     """
...     if not isinstance(key, (int, long)): 
...         raise TypeError
...     if key < 0:
...         raise IndexError
...     
>>> class ArithmeticSequence:
...     def __init__(self, start = 0, step = 1):
...         '''init the arithmetic sequence
...         start ----- the first value in sequence
...         step  ----- the difference between two adjacent numbers
...         change----- the dictionary of the values modified by users
...         '''
...         self.start = start
...         self.step = step
...         self.change = {}        
...     def __getitem__(self, key):
...         '''
...         Get an item from th arithmentic sequence
...         '''
...         checkIndex(key)
...         try :
...             return self.change[key]
...         except KeyError:
...             print 'KeyError !'
...             return self.start + key * self.step
...     def __setitem__(self, key, value):
...         '''
...         Modify an item in arithmetic sequece
...         '''
...         checkIndex(key)
...         self.change[key] = value
...         
>>> s = ArithmeticSequence(1,2)
>>> s[4]
KeyError !
9
>>> s[4] = 2
>>> s[4]
2
>>> s['four']
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 15, in __getitem__
  File "<interactive input>", line 9, in checkIndexs
TypeError
>>> s[-4]
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 15, in __getitem__
  File "<interactive input>", line 11, in checkIndex
IndexError

分析:这里实现的是一个算数序列。取值的时候会自动调用getitem方法,赋值的时候会自动调用setitem方法。如果索引不符合规范,就会引发异常。更详尽的说明请参考:http://www.cnblogs.com/livingintruth/p/3601861.html

子类化列表,字典和字符串
如果希望实现一个和内建列表行为相似的序列,可以子类化list来实现。例:

>>> class CounterList(list):
...     def __init__(self, *args):
...         super(CounterList, self).__init__(*args)
...         self.count = 0
...     def __getitem__(self, index):
...         self.count += 1
...         return super(CounterList, self).__getitem__(index)
...     
>>> cl = CounterList(range(10))
>>> cl
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> cl.reverse()
>>> cl
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> del cl[3:6]
>>> cl
[9, 8, 7, 3, 2, 1, 0]
>>> cl.count
0
>>> cl[4] + cl[2]
9
>>> cl.count
2

分析:CounterList继承了超类list,很多没有重写的方法都能直接使用(例如del、reverse、index等)。

3.属性
关于Python类属性有一篇帖子介绍的很详细:
https://segmentfault.com/a/1190000002671941

property函数
通过例子来了解,例:

>>> class Rectangle:
...     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)
...     
>>> r = Rectangle()
>>> r.width = 10
>>> r.height = 5
>>> r.size
(10, 5)
>>> r.size = (150, 100)
>>> r.width
10

分析:property函数不是正真的函数,它是其实例拥有很多特殊方法的类,也正是那些方法完成了所有的工作。涉及的方法是getsetdelete。这三个方法合在一起,就定义了描述符规则。实现了其中任何一个方法的对象就叫描述符(descriptor)。描述符的特殊之处在于它们是如何访问的,如果该特性被绑定到了实现了get方法的对象上,那么就会调用get方法(结果值也会被返回),而不是简单地返回对象。

例:

>>> __metaclass__ = type
>>> class Person:
...     def __init__(self, first_name, last_name):
...         self.first_name = first_name
...         self.last_name = last_name
...     @property
...     def full_name(self):
...         return '%s %s' %(self.first_name, self.last_name)
...     
>>> person = Person('Mike', 'Driscoll')
>>> person.full_name
'Mike Driscoll'
>>> person.first_name
'Mike'
>>> person.last_name
'Driscoll'

使用属性函数的最简单的方法之一是将它作为一个方法的装饰器来使用,如上例所示。
更具体的参考:
http://www.cnblogs.com/livingintruth/p/3601861.html
http://python.jobbole.com/80955/

静态方法和类成员方法
静态方法和类成员方法分别在创建时分别装入staticmethod类型和classmethod类型的对象中。静态方法的定义没有self参数,且能够被类本身直接调用。类方法在定义时需要名为cls的类似于self的参数,类成员方法可以直接用类的具体对象调用。当cls参数是自动被绑定到类的。
例:

class MyClass:
...     def smeth():
...         print 'this is a static method !'
...     smeth = staticmethod(smeth)
...     def cmeth(cls):
...         print 'this is a classs method of ', cls
...     cmeth = classmethod(cmeth)
...     
>>> MyClass.smeth()
this is a static method !
>>> MyClass.cmeth()
this is a classs method of  __main__.MyClass

上例中的包装方法引入了一个叫做装饰器(decorator)的新语法(它能对任何可调用的对象进行包装,既能够用于方法也能用于函数)。使用@操作符,在方法(或函数)的商法将装饰器列出,从而指定一个或者更多的装饰器(多个装饰器在应用时的顺序于指定顺序相反)。
上例等价于:

>>> class MyClass:
...     @staticmethod
...     def smeth():
...         print 'this is a static method !'
...     @classmethod
...     def cmeth(cls):
...         print 'this is a classs method of ', cls
...         
>>> MyClass.smeth()
this is a static method !
>>> MyClass.cmeth()
this is a classs method of  __main__.MyClass

_getattr__setattr_和它的朋友们
拦截(intercept)对象的所有特性访问是可能的,这样可以用旧式类实现属性(因为property方法不能使用,如前面例子所示)。为了在访问特性的时候可以执行代码,必须使用一些魔法方法。下面的4种方法提供了需要的功能(在旧式类中只需要3个)。
_getattribute_(self, name):当特性name被访问时自动被调用(只能在新式类中使用)
_getattr_(self, name):当特性name被访问且对象没有响应的特性时被自动调用
_setattr_(self, name,value):当试图给特性name赋值时会被自动调用
_delattr_(self, name):当试图删除特性name时被自动调用
尽管和使用property函数相比有点复杂(而且在某些方面效率更低),但是这些特殊的方法是很强大的,因为可以对处理很多属性的方法进行再编码。
例:

>>> class Rectangle:
...     def __init__(self):
...         self.width = 0
...         self.height = 0
...     def __setattr__(self, name, value):
...         if name == 'size':
...             self.width, self.height = value
...         else :
...             self.__dict__[name] = value
...     def __getattr__(self, name):
...         if name == 'size':
...             return self.width, self.height
...         else:
...             print 'Attribute Error !'
...             raise AttributeError
...         
>>> r = Rectangle()
>>> r.__setattr__('size', (15, 20))
>>> r.size
(15, 20)
>>> r.width
15
>>> r.height
20
>>> r.weight = 156
>>> r.__dict__
{'width': 15, 'weight': 156, 'height': 20}
>>> r.size = (56, 28)
>>> r.__dict__
{'width': 56, 'weight': 156, 'height': 28}
>>> r.volumn
Attribute Error !
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 15, in __getattr__
AttributeError

分析:上例中的setattr方法在所涉及的特性不是size的时候也会被调用。getattr在给定的特性找不到时会引发AttributeError异常。

4.迭代器
所谓的迭代器就是具有next方法(这个方法在调用时不需要任何参数)的对象。在调用next方法是,迭代器会返回它的下一个值。如果next方法被调用,但是迭代器没有值可以返回,就会引发一个StopIteration异常。
注意:迭代器规则在Python3.0中有一些变化。在新的规则中,迭代器对象应该实现next方法,而不是next。而新的内建函数next可以用于访问这个方法。换句话说,next(it)等同于3.0之前版本中的it.next()。
例:使用迭代器的斐波那契数列

>>> class Fibs:
...     def __init__(self):
...         self.a = 0
...         self.b = 1
...     def next(self):
...         self.a, self.b = self.b, self.a + self.b
...         print self.a, self.b
...         return self.a
...     def __iter__(self):
...         return self
...     
>>> fibs =  Fibs()
>>> for f in fibs:
...     if f > 100:
...         print 'get numbe is : ', f
...         break
...     
1 1
1 2
2 3
3 5
5 8
8 13
13 21
21 34
34 55
55 89
89 144
144 233
get numbe is :  144

注意:迭代器实现了iter方法,这个方法实际上返回迭代器本身。在很多情况下,如上例,iter方法会放到其他的会在for循环中使用的对象中。这样一来,程序就能返回所需的迭代器。此外,推荐使迭代器实现它自己的iter方法,然后就能直接在for循环中使用迭代器本身了。正是的说法是:一个实现了_iter_方法的对象是可迭代的,一个实现了next方法的对象则是迭代器

从迭代器得到序列
在大部分能使用序列的情况下(除了在索引或者分片等操作中),都能使用迭代器(或者可迭代对象替换)。
例:使用list构造方法显示地将迭代器转化为列表

>>> class TestIteraor:
...     value = 0
...     def next(self):
...         self.value += 1
...         if self.value > 10 : raise StopIteration
...         return self.value
...     def __iter__(self):
...         return self
...     
>>> ti = TestIteraor()
>>> list(ti)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

5.生成器
创建生成器
创建一个转开嵌套列表的函数,例:

>>> def flatten(nested):
...     for sublist in nested:
...         for element in sublist:
...             yield element
...             
>>> nested = [[1,2], [3,4], [5]]
>>> for num in flatten(nested):
...     print num
...     
1
2
3
4
5
>>> list(flatten(nested))
[1, 2, 3, 4, 5]

任何包含yield语句的函数称为生成器。除了名字不同以外,它的行为和普通的函数也有很大的区别。这就在于它不是想return那样返回值,而是每次产生多个值。每次产生一个值(使用yield语句),函数就会冻结:即函数停在那点等待被重新唤醒。函数被重新唤醒后就从停止的那点开始执行。

递归生成器
当要处理嵌套循环时,使用递归生成器能更好地解决问题。例:

>>> def flatten(nested):
...     try:
...         #no need to iterate object like string
...         try :
...             nested + ''
...         except TypeError :
...             pass
...         else :
...             raise TypeError
...         for sublist in nested :
...             for element in flatten(sublist) :
...                 yield element
...     except TypeError :
...         yield nested    
...         
>>> nested = [[1,2], [3,4], [5], 'fjolaj', 'are']
>>> for num in flatten(nested):
...     print num
...     
1
2
3
4
5
fjolaj
are
>>> list(flatten(nested))
[1, 2, 3, 4, 5, 'fjolaj', 'are']

分析说明:上例是将字符串的对象当成院子值,所以将传入的对象和一个字符串拼接,用来减产一个对象是不是类似于字符串的对象。如果表达式nested + ”引发了一个TypeError,它就会忽略;然而如果没有引发TypeError,那么内层try语句中的else子句就会引发一个T它自己的TypeError异常。

通用生成器
生成器是一个包含yield关键字的函数,当它被调用时,在函数体中的代码不会被执行,而会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句。yield语句意味着应该生成一个值。return语句意味着生成器要停止执行。
例:

>>> def y_yield(n):
...     print 'beginning .....'
...     while n > 0:
...         print 'before yield .....'
...         yield n
...         n -= 1
...         print 'after yield .....'
...         
>>> yy = y_yield(3)
>>> yy.next()
beginning .....
before yield .....
3
>>> yy.next()
after yield .....
before yield .....
2
>>> yy.next()
after yield .....
before yield .....
1
>>> 
>>> 
>>> yy.next()
after yield .....
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
StopIteration

详细介绍请参考:
http://wiki.jikexueyuan.com/project/start-learning-python/215.html

生成器方法
生成器的新特征(在Python2.5中引入)是在开始运行后为生成器提供值的能力。表现为生成器和“外部世界”进行交流的渠道,要注意下面两点:
1.外部作用域访问生成器的send方法,就像访问next方法一样,只不过前者使用一个参数(要发送的“消息”——任意对象)
2.在内部则挂起生成器,yield现在作为表达式而不是语句使用,换句话说,当生成器重新运行的是时候,yield方法返回一个值,也就是外部通过send方法发送的值。如果next方法被使用,那么yield方法返回None
例:

>>> def repeater(value):
...     while True:
...         new = (yield value)
...         if new is not None :
...             value = new
...             
>>> r = repeater(4)
>>> r.next()
4
>>> r.send('hello')
'hello'
>>> a = repeater(4)
>>> a.send('hell')
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator

注:使用send方法只有在生成器挂起之后才有意义,也就是说在yield函数至少被执行一次之后,如果不是这样,就会已发异常,如上所示。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值