7.反射
你也可以通过定义神奇方法来控制如何反射使用内建函数isinstance()和issubclass()的行为。这些神奇方法是:
__instancecheck__(self, instance)
检查一个实例是否是你定义类中的一个实例(比如,isinstance(instance, class))
__subclasscheck__(self, subclass)
检查一个类是否是你定义类的子类(比如,issubclass(subclass, class))
这对于神奇方法的用例情况来说可能较小,可它的确是真的。我并不想花费太多的时间在反射方法上面,因为他们不是那么地重要。不过它们反映了在Python中关于面对对象编程一些重要的东西,而且在Python中的普遍:总是在找一种简单的方式来做某些事情,即使它能被用到的不多。这些神奇方法似乎看上去不那么有用,但当你需要使用它们的时候你会感激它们的存在(和你阅读的这本指南!)。
8.可调用对象
正如你可能已经知道,在Python中函数是第一类对象。这就意味着它们可以被传递到函数和方法,就像是任何类型的对象。这真是一种难以置信强大的特性。
这是Python中一个特别的神奇方法,它允许你的类实例像函数。所以你可以“调用”它们,把他们当做参数传递给函数等等。这是另一个强大又便利的特性让Python的编程变得更可爱了。
__call__(self, [args...])
允许类实例像函数一样被调用。本质上,这意味着x()等价于x.__call__()。注意,__call__需要的参数数目是可变的,也就是说可以对任何函数按你的喜好定义参数的数目定义__call__
__call__可能对于那些经常改变状态的实例来说是极其有用的。“调用”实例是一种顺应直觉且优雅的方式来改变对象的状态。下面一个例子是一个类表示一个实体在一个平面上的位置:
1 2 3 4 5 6 7 8 9 10 11 12 | class Entity: '''描述实体的类,被调用的时候更新实体的位置'''
def __init__(self, size, x, y): self.x, self.y = x, y self.size = size
def __call__(self, x, y): '''改变实体的位置''' self.x, self.y = x, y
#省略... |
9.上下文管理
在Python2.5里引入了一个新关键字(with)使得一个新方法得到了代码复用。上下文管理这个概念在Python中早已不是新鲜事了(之前它作为库的一部分被实现过),但直到PEP343(http://www.python.org/dev/peps/pep-0343/)才作为第一个类语言结构取得了重要地位而被接受。你有可能早就已经见识过with声明:
1 2 | with open('foo.txt') as bar: # 对bar执行某些动作 |
上下文管理允许对对象进行设置和清理动作,用with声明进行已经封装的操作。上下文操作的行为取决于2个神奇方法:
__enter__(self)
定义块用with声明创建出来时上下文管理应该在块开始做什么。注意,__enter__的返回值必须绑定with声明的目标,或是as后面的名称。
__exit__(self, exception_type, exception_value, traceback)
定义在块执行(或终止)之后上下文管理应该做什么。它可以用来处理异常,进行清理,或行动处于块之后某些总是被立即处理的事。如果块执行成功的话,excepteion_type,exception_value,和traceback将会置None。否则,你可以选择去处理异常,或者让用户自己去处理。如果你想处理,确保在全部都完成之后__exit__会返回True。如果你不想让上下文管理处理异常,那就让它发生好了。
__enter__和__exit__对那些已有良好定义和对设置,清理行为有共同行为的特殊类是有用。你也可以使用这些方法去创建封装其他对象通用的上下文管理。看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Closer: '''用with声明一个上下文管理用一个close方法自动关闭一个对象'''
def __init__(self, obj): self.obj = obj
def __enter__(self): return self.obj #绑定目标
def __exit__(self, exception_type, exception_val, trace): try: self.obj.close() except AttributeError: #obj不具备close print 'Not closable.' return True #成功处理异常 |
以下是一个对于Closer实际应用的一个例子,使用一个FTP连接进行的演示(一个可关闭的套接字):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> from magicmethods import Closer >>> from ftplib import :;; >>> with Closer(FTP('ftp.somsite.com')) as conn: ... conn.dir() ... # 省略的输出 >>> conn.dir() # 一个很长的AttributeError消息, 不能关闭使用的一个连接 >>> with Closer(int(5)) as i: ... i += 1 ... Not closeable. >>> i 6 |
瞧见我们如何漂亮地封装处理正确或不正确的用例了吗?那就是上下文管理和神奇方法的威力。
10.构建描述符对象
描述符可以改变其他对象,也可以是访问类中任一的getting,setting,deleting。描述符不意味着孤立;相反,它们意味着会被它们的所有者类控制。当建立面向对象数据库或那些拥有相互依赖的属性的类时,描述符是有用的。当描述符在几个不同单元或描述计算属性时显得更为有用。
作为一个描述符,一个类必须至少实现__get__,__set__,和__delete__中的一个。让我们快点看一下这些神奇方法吧:
__get__(self, instance, owner)
当描述符的值被取回时定义其行为。instance是owner对象的一个实例,owner是所有类。
__set__(self, instance, value)
当描述符的值被改变时定义其行为。instance是owner对象的一个实例,value是设置的描述符的值
__delete__(self, instance)
当描述符的值被删除时定义其行为。instance是owner对象的一个实例。
现在,有一个有用的描述符应用例子:单位转换策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Meter(object): '''米描述符'''
def __init__(self, value=0.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value)
class Foot(object): '''英尺描述符'''
def __get__(self, instance, owner): return instance.meter * 3.2808 def __set__(self, instance, value): instance.meter = float(value) / 3.2808
class Distance(object): '''表示距离的类,控制2个描述符:feet和meters''' meter = Meter() foot = Foot() |
11.Pickling你的对象
假如你花时间和其他Pythonistas打交道,那么你至少有可能听到过Pickling这个词。Pickling是一种对Python数据结构的序列化过程。如果你需要存储一个对象,之后再取回它(通常是为了缓存)那么它就显得格外地有用了。同时,它也是产生忧虑和困惑的主要来源。
Pickling是那么地重要以至于它不仅有自己专属的模块(pickle),还有自己的protocol和神奇方法与其相伴。但首先用简要的文字来解释下如何pickle已经存在的类型(如果你已经懂了可以随意跳过这部分内容)
Pickling:盐水中的快速浸泡
让我们跳入pickling。话说你有一个词典你想要保存它并在稍后取回。你可以把它的内容写到一个文件中去,需要非常小心地确保你写了正确的语法,然后用exec()或处理文件的输入取回写入的内容。但这是不稳定的:如果你你在纯文本中保存重要的数据,它有可能被几种方法改变,导致你的程序crash或在你的计算机上运行了恶意代码而出错。于是,我们准备pickle它:
1 2 3 4 5 6 7 8 | import pickle
data = {'foo': [1,2,3], 'bar': ('Hello','world!'), 'baz': True} jar = open('data.pk1', 'wb') pickle.dump(data, jar) #把pickled数据写入jar文件 jar.close() |
好了现在,已经过去了几个小时。我们希望拿回数据,而我们需要做的事仅仅是unpickle它:
1 2 3 4 5 6 | import pickle
pk1_file = open('data.pk1','rb') #连接pickled数据 data = pickle.load(pk1_file) #把数据load到一个变量中去 print data pk1_file.close() |
发生了什么事?正如你的预期,我们获得了data。
现在,我要给你一些忠告:pickling并非完美。Pickle文件很容易因意外或出于故意行为而被损毁。Pickling可能比起使用纯文本文件安全些,但它仍旧有可能会被用来跑恶意代码。还有因为Python版本的不兼容问题,所以不要期望发布Pickled对象,也不要期望人们能够打开它们。但是,它依然是一个强大的缓存工具和其他常见序列化任务。
Pickling你自定义的对象
Pickling不仅可用在内建类型上,还可以用于遵守pickle协议的任何类。pickle协议有4个可选方法用于定制Python对象如何运行(这跟C扩展有点不同,但那不在我们讨论的范围内):
__getinitargs__(self)
如果你想当你的类unpickled时调用__init__,那你可以定义__getinitargs__,该方法应该返回一个元组的参数,然后你可以把他传递给__init__。注意,该方法仅适用于旧式类。
__getnewargs__(self)
对于新式类,你可以影响有哪些参数会被传递到__new__进行unpickling。该方法同样应该返回一个元组参数,然后能传递给__new__
__getstate__(self)
代替对象的__dict__属性被保存。当对象pickled,你可返回一个自定义的状态被保存。当对象unpickled时,这个状态将会被__setstate__使用。
__setstate__(self, state)
对象unpickled时,如果__setstate__定义对象状态会传递来代替直接用对象的__dict__属性。这正好跟__getstate__手牵手:当二者都被定义了,你可以描述对象的pickled状态,任何你想要的。
一个例子:
我们的例子是Slate类,它会记忆它曾经的值和已经写入的值。然而,当这特殊的slate每一次pickle都会被清空:当前值不会被保存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import time
class Slate: '''存储一个字符串和一个变更log,当Pickle时会忘记它的值'''
def __init__(self, value): self.value = value self.last_change = time.asctime() self.history = {}
def change(self, new_value): # 改变值,提交最后的值到历史记录 self.history[self.last_change] = self.value self.value = new_value self.last_change = time.asctime()
def print_changes(self): print 'Changelog for Slate object:' for k, v in self.history.items(): print '%st %s' % (k, v)
def __getstate__(self): # 故意不返回self.value 或 self.last_change. # 当unpickle,我们希望有一块空白的"slate" return self.history
def __setstate__(self, state): # 让 self.history = state 和 last_change和 value被定义 self.history = state self.value, self.last_change = None, None |
12.总结
这份指南的目标就是任何人读一读它,不管读者们是否具备Python或面对对象的编程经验。如果你正准备学习Python,那你已经获得了编写功能丰富,优雅,易用的类的宝贵知识。如果你是一名中级Python程序员,你有可能已经拾起了一些新概念和策略和一些好的方法来减少你和你的用户编写的代码量。如果你是一名Pythonista专家,你可能已经回顾了某些你可能已经被你遗忘的知识点,或着你又学习到了一些新技巧。不管你的的经验等级,我希望这次Python神奇方法的旅程达到了真正神奇的效果。(我无法控制自己在最后不用个双关语)
附录:如果调用神奇方法
Python中的一些神奇方法直接映射到内建函数;在这种情况下,调用它们的方法是相当明显的。然而,在其他情况下,那些调用方法就不这么明显了。本附录致力于揭开能够引导神奇方法被调用的非明显语法。
神奇方法 | 调用方法 | 说明 |
__new__(cls [,...]) | instance = MyClass(arg1, arg2) | __new__ 在创建实例的时候被调用 |
__init__(self [,...]) | instance = MyClass(arg1, arg2) | __init__ 在创建实例的时候被调用 |
__cmp__(self, other) | self == other, self > other, 等 | 在比较的时候调用 |
__pos__(self) | +self | 一元加运算符 |
__neg__(self) | -self | 一元减运算符 |
__invert__(self) | ~self | 取反运算符 |
__index__(self) | x[self] | 对象被作为索引使用的时候 |
__nonzero__(self) | bool(self) | 对象的布尔值 |
__getattr__(self, name) | self.name # name不存在 | 访问一个不存在的属性时 |
__setattr__(self, name, val) | self.name = val | 对一个属性赋值时 |
__delattr__(self, name) | del self.name | 删除一个属性时 |
__getattribute(self, name) | self.name | 访问任何属性时 |
__getitem__(self, key) | self[key] | 使用索引访问元素时 |
__setitem__(self, key, val) | self[key] = val | 对某个索引值赋值时 |
__delitem__(self, key) | del self[key] | 删除某个索引值时 |
__iter__(self) | for x in self | 迭代时 |
__contains__(self, value) | value in self, value not in self | 使用 in 操作测试关系时 |
__concat__(self, value) | self + other | 连接两个对象时 |
__call__(self [,...]) | self(args) | “调用”对象时 |
__enter__(self) | with self as x: | with语句上下文管理 |
__exit__(self, exc, val, trace) | with self as x: | with语句上下文管理 |
__getstate__(self) | pickle.dump(pkl_file, self) | 序列化 |
__setstate__(self) | data = pickle.load(pkl_file) | 序列化 |