Python学习笔记_第九章:魔法方法、属性和迭代器

魔法方法:名字中以双下划线为开头和结尾(__* __)的方法组成的集合中所包含的方法

准备工作

Alex Martelli所著的《Python技术手册》的第8章有关于旧式类和新式类区别的深入探讨
可以对自己类的作用域中的__metaclass __变量赋值来为这个类设定元类,元类是其他类(或类型)的类——这是个更高级别的主题(可参考:http://python.org/2.2/descrintro.html)。
Python3.0中没有旧式类

构造方法

构造方法:__init __为名字的魔法方法。
析构方法:__del __为名字的方法,他在对象将要被垃圾回收之前调用,但发生的具体时间不可知,所以不建议用。

方法重写

Python支持在子类中重写方法(覆盖)——不同于C++中的重写虚拟函数,这好像是C++中的重定义,调用超类中被覆盖的同名方法可以调用超类方法的未绑定版本或者使用super函数。

调用未绑定的父类构造方法

class Brid:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print 'Aaah...'
            self.hungry = False
        else:
            print 'No Thanks!'
class SongBird(Brid):
    def __init__(self):
        Brid.__init__(self)
        self.sound = 'gagaga'
    def sing(self):
        print self.sound
sb = SongBird()
sb.sing()
sb.eat()
sb.eat()

out:
gagaga
Aaah...
No Thanks!

上面通过调用未绑定的父类构造方法初始化了从父类中继承的特性
调用一个实例方法时,该方法的self参数会被自动绑定到实例上,但如果直接通过类名调用方法的话,就没有实例被绑定。这样可以像前面代码一样自由地指定self参数了,这样的方法称为未绑定方法。

使用super函数

__metaclass__ = type
class Brid:
    def __init__(self):
        self.hungry = True
    def eat(self):
        if self.hungry:
            print 'Aaah...'
            self.hungry = False
        else:
            print 'No Thanks!'
class SongBird(Brid):
    def __init__(self):
        super(SongBird, self).__init__()
        self.sound = 'gagaga'
    def sing(self):
        print self.sound
sb = SongBird()
sb.sing()
sb.eat()
sb.eat()

out:
gagaga
Aaah...
No Thanks!

上面代码为用super调用父类构造方法的例子,在Python3.0中,super函数可以不带任何参数进行调用,功能仍具魔力
super函数是很智能的,即使类继承了多个超类,也只需要使用一次super函数(要确保所有超类的构造方法都使用了super函数)

成员访问

本节介绍常用魔法方法的集合,它可以创建行为类似于序列或映射的对象,使对象拥有类似序列或映像的成员访问规则。
规则这个词在Python中会经常使用,用来描述管理某种形式的行为的规则,这与第7章中提到的接口概念有点类似。规则说明了应该实现何种方法以及方法应该做什么。
其他语言中对象可能被要求属于某一个类,或者被要求实现某个接口,但Python中只是简单地要求它遵守几个给定的规则(实现某些魔法方法)。

基本的序列和映射规则

对象不可变时实现前两个方法即可,对象可变时额外需要实现后两个方法

  • __len __(self)
  • __getitem __(self, key)
  • __setitem __(self, key, value)
  • __delitem __(self, key)

以上方法中,如果键类型不合适会引发TypeError异常,类型合适但超出了范围会引发IndexError异常。
Python语言规范上明确指出索引必须是整数,所以序列对象的类进行索引检查过程中要利用isinstance方法进行类型检查,遵守标准是使用类型检查(很少的)正当理由之一。
分片操作也是可以模拟的。当对支持__getitem __方法的实例进行分片操作时,分片对象作为键提供。
分片兑现在Python库参考(http://python.org/doc/lib)的2.1节中slice函数部分有介绍。
Python2.5有一个更加专门的做法叫做__index __,它允许你在分片中使用非整型限制,只要你想处理基本序列之外的事情,那么这个方法尤其有用。

子类化列表,字典和字符串

Python参考手册的3.4.5节(http://www.python.org/doc/ref/sequence-types.html)可以找到官方推荐的其他有关序列和映射的其他特殊方法和普通方法,实现这些方法(可以让自己的对象具有多态性)是很难做好的事,所以对于特定应用可以选择性实现一些方法。
继承可以解决上面的问题,标准库中有3个关于序列和映射规则(UserList、UserString和UserDict)可以继承。
当子类化一个内建类型之后相当于间接子类化了object类,自动成为了新式类,可以使用像super这样的特性了。

更多魔力

关于更多特殊函数可以参考《Python参考手册》中的3.4
(http://www.python.org/doc/ref/specialnames.html).

属性

Python能隐藏访问器方法,让所用特性看起来一样。这些通过访问器定义的特性称为属性
实际上Python中有两种创建属性的机制,下面讨论新式类中欧冠的property函数

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)
rec = Rectangle()
rec.size = 12, 3
print rec.width
print rec.height
print rec.size

out:
12
3
(12, 3)

property函数的作用是封装访问器函数为属性,使它能像普通特性一样对待
取决于Python版本,对属性访问结果可能不同,在有些版本中虽然属性的取值部分可以正常工作但赋值部分就不一定了。
property函数也可以用0,1,3,4个参数调用

  • 0个参数:产生的属性不可读也不可写
  • 1个参数(一个取值方法):产生的属性只读
  • 第三个参数可选:一个删除特性的方法,该方法不要参数
  • 第四个参数可选:一个文档字符串
  • 4个参数分别叫做:fget,fset,fdel,doc

如果希望一个属性时只写的并且需要一个文档字符串,可以通过关键字参数实现。

静态方法和类成员方法

静态方法和类成员方法在创建时分别被装入staticmethod类型和classmethod类型的对象中,实现方法和新式属性的实现方法类似。
静态方法的定义中没有self参数,且能被类本身直接调用;类成员方法定义中有一个类似self的cls参数(但cls参数是自动绑定到类的),可以用类的具体对象调用。

getattrsetattr和它的朋友们

拦截对象的所有特性访问是可能的,所以可以用旧式类实现属性

# _*_ coding: utf-8 _*_
class Rectangle:
    def __init__(self):
        self.width = 0
        self.height = 0
    def __getattr__(self, item):
        if item == 'size':
            return self.width, self.height
        else:
            raise AttributeError
    def __setattr__(self, key, value):
        if key == 'size':
            self.width, self.height = value
        else:
            self.__dict__[key] = value
            #为了避免__setattr__被再次调用,__dict__被用来代替普通的特性赋值操作
rec = Rectangle()
rec.size = (12, 3)
print rec.height
print rec.width
print rec.size

out:
3
12
(12, 3)

就像死循环陷阱和__setattr __有关系一样,在新式类中getattribute拦截所有特性的访问,同样也拦截对__dict __的访问,所以,访问__getattribute __中与self有关的特性时,使用超类的__getattribute __方法是唯一安全的路径。

迭代器

特殊方法__iter __是迭代器规则的基础

迭代器规则

__iter __方法会返回一个迭代器,我们可以迭代所有实现了__iter __方法的对象,所谓的迭代器就是具有next方法的对象。
在Python3.0中迭代器对象应该实现__next __方法,而不是next方法,而新的内建函数next可以访问这个方法,即next(it)等同于3.0之前的it.next()。
下面是斐波那契数列的实现

# _*_ coding: utf-8 _*_
class fib:
    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
f = fib()
for i in f:
    if i > 1000:
        print i
        break

out:
1597

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

>>> it = iter([1, 2, 3, 4])
>>> it.next()
1
...
>>> it.next()
4
>>> it.next()

Traceback (most recent call last):
  File "", line 1, in 
    it.next()
StopIteration

如上,当迭代完还进行迭代就会引发StopIteration异常。

从迭代器到序列

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

生成器

生成器是一种用普通函数的语法定义的迭代器

>>> def flatten(nested):
    for sublist in nested:
        for element in sublist:
            yield element

>>> nested = [[1, 2, [3, 4], 5], [6, 7, 8, [9, 10]]]
>>> for num in flatten(nested):
    print num

1
2
[3, 4]
5
6
7
8
[9, 10]

这里的yield是新知识,任何包含yield语句的函数称为生成器。
循环生成器:生成器推导式和列表推导试的工作方式类似,只不过返回的不是列表而是生成器,和列表推导式不同的是普通圆括号的使用方式,生成器推导式可以将迭代对象打包,与之相比,列表推导式会一次性生成一个列表,丧失迭代的优势。

>>> g = (i + 2)**2 for i in range(2, 27)
SyntaxError: invalid syntax
>>> g = ((i + 2)**2 for i in range(2, 27))
>>> g.next()
16

生成器推导式可以不用外面的括号,直接在圆括号内使用

>>> sum(i for i in range(2, 10))
44

递归生成器

在生成器的例子flatten(nested)中并没有完全解决问题,要解决上面三层嵌套列表问题,我们也许可以考虑再加一层for循环,但对于层数不确定的列表,就需要递归来解决问题了。

>>> def flatten(nested):
    try:
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError:
        yield nested

>>> nested = [[1, 2, [3, 4], 5], [6, 7, 8, [9, 10]]]
>>> for num in flatten(nested):
    print num
1
2
3
4
5
6
7
8
9
10

这么做还有一个问题,如果nested是一个类似于字符串的对象,那么它就是一个序列,不会引发TypeError,但又不想对这样的对象进行迭代(对字符串迭代会引起无穷递归,因为字符串的第一个元素是另一个长度为1的字符串,而长度为1的字符串的第一个元素就是字符串本身)
解决这个问题可以通过把传入对象和一个空字符串拼接,通过检查是否出现TypeError来判断是否是字符串

通用生成器

return语句只有在生成器中使用才能进行无参数调用。
生成器由两部分组成:生成器函数和生成器迭代器。

生成器方法

>>> 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')
'hello'
>>> r.next()
'hello'

以上,yield被挂起后调用send时yield会返回send的参数,程序中yield返回值通过new传给了value
生成器还有其他两个方法
throw:引发异常
close:停止生成器
有关更多生成器方法的信息以及如何将生成器转换为简单的协同程序的方法可以参见PEP342:http://www.python.org/dev/peps/pep-0342/

模拟生成器

在旧版本的Python中不可以用生成器,但是可以用普通的函数来模拟。

# _*_ coding: utf-8 _*_
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
nested = ['string', 'hello', [1, 2, 3, [4, 5]]]
value = flatten(nested)
for va in value:
    print va

out:
string
hello
1
2
3
4
5

总结:

  • 构造方法,析构方法。
  • super:访问超类方法的魔法方法(也可以访问超类的非绑定方法)。
  • 成员访问规则方法:__len __(self)、__getitem __(self, key)、__setitem __(self, key, value)、__delitem __(self, key)。
  • property:将访问器方法构造为属性。
  • 静态方法和类成员方法
  • 特性访问拦截器方法(用于在旧式类中实现属性):__getattribute __、__getattr __、__setattr __、__delattr __
  • __iter __、next:关于迭代器的方法。
  • yield:关于生成器的关键字。

你可能感兴趣的:(Python学习笔记_第九章:魔法方法、属性和迭代器)