((Python基础教程))学习笔记 | 第09章 | 魔法方法、属性和迭代器
((Python基础教程))学习笔记 | 第09章 | 魔法方法、属性和迭代器
这一章,有点抽象,看着有点蛋疼!
双下划线__future__或单下划线有特殊含义,在Python中,这些名字的集合称为魔法方法:最重要的是__init__和一些处理访问对象的方法,这些方法允许你创建自己的序列或者是映射.
------
准备工作:
将__metaclass__=type放在模块的最开始位置,以确保类时最新式的。考虑下面两个类
class NewStyle(object): more_code_here class OldStyle: more_code_here如果文件以__metclass__=type开始,那么这个类都是新式类
------
构造方法:
和普通方法的不同在于:当一个对象被创建后,会立即调用构造方法。因此,之前章节的
>>>f = FooBar() >>>f.init()等同于
>>>f = FooBar() >>> class FooBar: def __init__(self): self.var = 42 >>> f = FooBar() >>> f.var 42#如果有默认参数呢?
>>> class Foobar: def __init__(self,value=42): self.var = value >>> f = Foobar() #无参的话,使用默认值 >>> f.var 42 >>> f1 = Foobar(44) #有参的话,使用新参数 >>> f1.var 44在Python中__init__是使用最多的一个.
Python中有个魔法方法__del__,析构方法,她在对象要被垃圾回收之前调用.但是发生调用的具体时间是不可知的。
所以建立尽力避免使用__del__函数.
------
重写一般方法和特殊构造方法:
>>> class A: ... def hello(self): ... print 'Hello,World!' ... >>> class B(A): pass ... >>> class C(B): pass ...#看看他们的工作机制
#c是C类的实例,当c调用hello(),首先找自己类有没有,没有话,去超类B中找,如果还没有话,
就去超类的超类去找。还没有的话,就报错。
>>> c = C() >>> c.hello() Hello,World!如果重写B类,hello的方法呢?
>>> class A: ... def hello(self): ... print Hello,I am A! ... >>> class B(A): ... def hello(self): ... print Hello,I am B! ... >>> b = B() >>> b.hello() Hello,I am B!重写是继承机制中的一个重要内容,对于构造方法尤其重要。构造方法用来初始化新创建对象的状态,大多数子类不仅要拥有自己的初始化代码,还要拥有超类的初始化代码。虽然重写的机制对于所有方法来说都是一样的,但是当处理构造方法比重写普通方法时,更可能遇到特别的问题:如果一个类的构造方法被重写,那么就需要调用超类的构造方法,否则对象不会被正确初始化。看下面的例子
class Bird: def __init__(self): self.hungry = True def eat(self): if self.hungry: print Ahaha... self.hungry = False else: print No, Thanks!该类定义鸟的基本功能吃,吃饱了就不再吃
输出结果:
>>> b = Bird() >>> b.eat() Ahaha... >>> b.eat() No, Thanks! 下面一个子类SingBird, class SingBird(Bird): def __init__(self): self.sound = 'squawk' def sing(self): print self.sound输出结果:
>>> s = SingBird() >>> s.sing() squawkSingBird是Bird的子类,但如果调用Bird类的eat()方法时,
>>> s.eat() Traceback (most recent call last): File代码错误很清晰,SingBird中初始化代码被重写,但没有任何初始化hungry的代码, line 1, in s.eat() File D:LearnPythonPerson.py, line 42, in eat if self.hungry: AttributeError: SingBird instance has no attribute 'hungry'
------
调用未绑定的超类构造方法:
class SingBird(Bird): def __init__(self): Bird.__init__(self) #增加这行代码就搞定 self.sound = 'squawk' def sing(self): print self.sound >>> sb = SingBird() >>> sb.sing() squawk >>> sb.eat() Ahaha... >>> sb.eat() No, Thanks!------
使用super函数:
__metaclass__=type class Bird: def __init__(self): self.hungry = True def eat(self): if self.hungry: print Ahaha... self.hungry = False else: print No, Thanks! class SingBird(Bird): def __init__(self): super(SingBird,self).__init__() self.sound = 'squawk' def sing(self): print self.soundNote:
1. __metaclass__=type 必不可少,否则报错如下:
>>> sb = SingBird() Traceback (most recent call last): File2. super(SingBird,self).__init__() 多了这么一句, line 1, in sb = SingBird() File D:LearnPythonPerson.py, line 51, in __init__ super(SingBird,self).__init__() TypeError: must be type, not classobj
输出结果:
>>> sb = SingBird() >>> sb.sing() squawk >>> sb.eat() Ahaha... >>> sb.eat() No, Thanks!------
基本的序列和映射规则:
序列和映射是对象的集合,为了实现他们的基本行为,如果对象是不可变的,那么就需要两个魔法方法,如果对象时可变的,那么就需要四个魔法方法
__len__(self):返回集合中所含项目的数量
__getitem__(self,key):返回与所给的键对应的值
__setitem__(self,key,value):按一定的方法存储和key相关的value
__delitem__(self,key):删除对象相关的键
实践一下,创建一个无穷序列
def checkIndex(key): if not isinstance(key,(int,long)): raise TypeError if key<0: raise IndexError class ArithmeticSequence: def __init__(self,start=0,step=1): self.start = start self.step = step self.changed = {} def __getitem__(self,key): checkIndex(key) try: return self.changed[key] except KeyError: return self.start + key*self.step def __setitem__(self,key,value): checkIndex(key) self.changed[key] = value输出结果
>>> s[100] 201 >>> s = ArithmeticSequence(1,2) >>> s[4] 9 >>> s[10] 21 >>> del s[4] Traceback (most recent call last): File------, line 1, in del s[4] AttributeError: ArithmeticSequence instance has no attribute '__delitem__' >>> s['four'] Traceback (most recent call last): File , line 1, in s['four'] File D:LearnPythonPerson.py, line 71, in __getitem__ checkIndex(key) File D:LearnPythonPerson.py, line 62, in checkIndex raise TypeError TypeError >>> s[-4] Traceback (most recent call last): File , line 1, in s[-4] File D:LearnPythonPerson.py, line 71, in __getitem__ checkIndex(key) File D:LearnPythonPerson.py, line 64, in checkIndex raise IndexError IndexError
子类化列表,字典和字符串
三个关于序列和映射规则(UserList,UserString,UserDict),如果希望实现一个和内建列表行为相似的序列,可以使用子类list,看看下面的例子,带有访问计数的列表
class CounterList(list): def __init__(self,*args): super(CounterList,self).__init__(*args) self.counter = 0 def __getitem__(self,index): self.counter +=1 return super(CounterList,self).__getitem__(index)#下面是她如何使用的一些例子
>>> c = CounterList(range(10)) >>> c [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> c.reverse() >>> c [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] >>> del c[3:6] >>> c [9, 8, 7, 3, 2, 1, 0] >>> c.counter 0 >>> c[4]+c[2] 9 >>> c.counter 2
#其他跟list性能一样,但有个counter特性,每次执行加法后会自增。
------
属性:
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 >>> r= Rectangle() >>> r.width = 10 >>> r.height= 5 >>> r.getSize() (10, 5) >>> r.setSize((150,100)) >>> r.width 150------
property函数:
__metaclass__=type 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)在这个新版的Rectangle中,property函数创建了一个属性,其中访问器函数被用做参数(先是取值,然后是赋值),这个属性命为size,这样一来,就不用担心是如何实现的,可以用同样的方式处理width,height和size.
>>> r = Rectangle() >>> r.width = 10 >>> r.height= 20 >>> r.size (10, 20) >>> r.size = 100,200 >>> r.width 100property函数可以用0,1,2,3或4个参数来调用。如果没有参数,产生的属性即不可读,也不可写。如果只使用一个参数调用,产生的属性是只读的第3个参数。名字分别叫:fget,fset,fdel,doc__
------
静态方法和类成员方法
静态方法和类成员方法分别在创建时被装入Staticmethod类型和Classmethod类型的对象中。
静态方法的定义没有self参数,且可能被类本身直接调用。
类方法在定义时需要名为cls的类似于self的参数,类成员方法可以直接用类的具体对象调用。但cls参数是自动被绑定类的。看下例子:
class MyClass: def smeth(): print 'This is a stacie method' smeth = staticmethod(smeth) def cmeth(cls): print 'This is a class method of', cls cmeth = classmethod(cmeth)#用@来替代
__metaclass__ = type class MyClass: @staticmethod def smeth(): print 'This is a stacie method' @classmethod def cmeth(cls): print 'This is a class method of', cls#定义好了方法后,可以这样调用。
>>> MyClass.smeth() This is a stacie method >>> MyClass.cmeth() This is a class method of------
__getattr__,__setattr__
为了访问特性的时候可以执行代码,必须使用一些魔法方法。下面四种方法提供了需要的功能。
__getattrbute__(self,name): 当特性name被访问时,自动被调用
__getattr__(self,name):当特性name被访问,且对象没有相应的特性时被自动调用
__setattr__(self,name,value):当试图给特性name赋值时会被自动调用
__delattr__(self,name): 当试图删除特性name时被自动调用。
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: raise AttributeError------
迭代器:
主要讨论特殊的方法:__iter__这个迭代器规则的基础.__iter__方法返回一个迭代器,所谓迭代器就是具有next方法的对象。如果next被调用,却没有值可以返回,则会返回StopIteration的异常.为什么要用迭代器而不用列表呢?
如果值很多,列表一次性获得,会占用太多的内存。而迭代则可以一个一个获取。还有为什么要用迭代器的原因:
迭代器更通用,更简单,更优雅。
class Fibs: def __init__(self): self.a = 0 self.b = 1 def next(self): self.a,self.b = self.b,self.a + self.b return self.a def __iter__(self): return self首先是实现了__iter__方法,这个方法实际上返回迭代器本身。很多情况下,__iter__会放到for循环中使用的对象中.首先产生一个Fibs对象
>>> fibs = Fibs()
其次可在for循环中使用该对象-比如去查找在斐波那契数列中比1000大的数中的最小的数:
>>> for f in fibs: if f>1000: print f break 1597Note: 内建函数iter可以从可迭代的对象中获得迭代器.
>>> it = iter([1,2,3]) >>> it.next() 1 >>> it.next() 2除此之外,它也可以从函数或者其他可调用对象中获取可迭代对象.
------
从迭代器中得到序列:
除了再迭代器和可迭代对象上进行迭代外,还能把它们转换为序列。在大部分能使用序列的情况下,能使用迭代器替换。一个很有用的例子是使用list构造方法显式地将迭代器转化为列表
class TestIter: value = 0 def next(self): self.value +=1 if self.value > 10 : raise StopIteration return self.value def __iter__(self):输出结果:
>>> ti = TestIter() >>> ti <__main__.TestIter instance at 0x0000000002A81A08> >>> list(ti) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]------
生成器: 是Python新引入的概念,由于历史原因,它也叫简单生成器。它和迭代器可能是近几年来引入的最强大的两个特性。生成器可以帮助程序员写出非常优雅的代码,当然编写任何程序也不可以不使用。生成器是一种用普通的函数语法定义的迭代器。
------
创建生成器:
nested = [[1,2],[3,4],[5]]如果像这么个例子,怎么样用将数字一一打印出来。
def flatten(nested): for sublist in nested: for element in sublist: yield element#如何包含yield的语句,称为生成器
>>> nested = [[1],[2,3],[4,5,6]] >>> flatten(nested)------>>> for num in flatten(nested): print num 1 2 3 4 5 6
递归生成器:
上一个例子创建的生成器只能处理两层嵌套,使用了两个for循环。如果要处理任意层的嵌套怎么办?就应该更灵活,现在就到了递归生成器登场的时候了。
def flatten(nested): try: for sublist in nested: for element in flatten(sublist): yield element except TypeError: yield nested两种情况: 基本情况和需要递归的情况
1. 如果只是元素,函数被告知展开一个元素,这种情况下,for循环会引发一个TypeError异常,生成器会产生一个元素.
2. 如果是一个列表,那么就要进行特殊处理。程序必须遍历所有子列表,并对它们调用flatten,然后使用另一个for循环来产生被展开的子列表中的所有元素,很神奇吧。
>>> nested = [[[[1,2],3],4],5]
>>> list(flatten(nested))
[1, 2, 3, 4, 5]
如果是字符串对象,那么它就是一个序列,不会引发TypeError,如果你不想对这样的对象进行迭代。为了处理这种情况,则必须在生成器的开始处添加一个检查语句。试着将传入的对象和一个字符串拼接,看看会不会出现TypeError,这是检查一个对象是不是类似于字符串的最简单,最快速的方法。下面加入检查语句的生成器。
>>> nested=['a',[[[1,2],3],4],5]
>>> list(flatten(nested))
['a', 1, 2, 3, 4, 5]
------
通用生成器:
生成器是一个包含yield关键字的函数。当它被调用时,在函数体中的代码不会被执行,而会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,知道遇到一个yield或者return语句。yield意味着应该生成一个值。return语句意味着生成器要停止执行。换句话说,生成器由两部分组成:生成器的函数和生成器的迭代器。生成器的函数是用def语句定义,包含yield部分,生成器的迭代器是这个函数返回的部分。
>>> def simple_generator(): yield 1 >>> simple_generator>>> simple_generator() >>>
------
生成器方法:
>>> def repeater(value): while True: new = (yield value) if new is not None: value = new输出结果:
>>> r = repeater(42) >>> r.next() 42 >>> r.next() 42 >>> r.send('Hello,World!') 'Hello,World!'#next()方法,send()方法,throw()方法,close()方法
------
模拟生成器:
如何使用普通的函数模拟生成器
首先将下面语句放在程序的开始处
result = []
然后将下面这种形式的代码:
yield some_expression
用下面的语句替换:
result.append(some_expression)
最后在函数的末尾,添加下面的语句:
return result
下面是flatten生成器用普通的函数重写的版本
def flatten(nested): result = [] try: try: nested + '' except TypeError: pass else: raise TypeError for sublist in nested: for element in flatten(sublist): result.append(element) except TypeError: result.append(nested) return result输出结果:
>>> n = [[[[[['HaHa...'],1],2],3],4],5] >>> flatten(n) ['HaHa...', 1, 2, 3, 4, 5]------
iter(obj) 从一个可迭代的对象得到迭代器
property(fget,fset,fdel,doc) 返回一个属性,所有参数都是可选的
super(class,obj) 返回一个类的超类的绑定实例
评论关闭