Skip to content

Commit

Permalink
update post
Browse files Browse the repository at this point in the history
  • Loading branch information
skyline75489 committed Jul 12, 2014
1 parent 40cbe39 commit 7c1c3ea
Showing 1 changed file with 148 additions and 24 deletions.
172 changes: 148 additions & 24 deletions docs/python-magic-methods-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ Python包含了一系列的魔法方法,用于实现对象之间直接比较
常见算数操作符
===============

现在,我们来看看常见的二元操作符(和一些函数),像+,-,*之类的,它们很容易从字面意思理解。
现在,我们来看看常见的二元操作符(和一些函数),像+,-,\*之类的,它们很容易从字面意思理解。


- `__add__(self, other)`

Expand Down Expand Up @@ -435,31 +436,31 @@ Python也有一系列的魔法方法用于实现类似 `float()` 的内建类型

- `__str__(self)`

定义对类的实例调用 `str()` 时的行为。
定义对类的实例调用 `str()` 时的行为。

- `__repr__(self)`

定义对类的实例调用 `repr()` 时的行为。 `str()` 和 `repr()` 最主要的差别在于“目标用户”。 `repr()` 的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的Python代码),而 `str()` 则产生人类可读的输出。
定义对类的实例调用 `repr()` 时的行为。 `str()` 和 `repr()` 最主要的差别在于“目标用户”。 `repr()` 的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的Python代码),而 `str()` 则产生人类可读的输出。

- `__unicode__(self)`

定义对类的实例调用 `unicode()` 时的行为。 `unicode()` 和 `str()` 很像,只是它返回unicode字符串。注意,如果调用者试图调用 `str()` 而你的类只实现了 `__unicode__()` ,那么类将不能正常工作。所有你应该总是定义 `__str__()` ,以防有些人没有闲情雅致来使用unicode。
定义对类的实例调用 `unicode()` 时的行为。 `unicode()` 和 `str()` 很像,只是它返回unicode字符串。注意,如果调用者试图调用 `str()` 而你的类只实现了 `__unicode__()` ,那么类将不能正常工作。所有你应该总是定义 `__str__()` ,以防有些人没有闲情雅致来使用unicode。

- `__format__(self)`

定义当类的实例用于新式字符串格式化时的行为,例如, `"Hello, 0:abc!".format(a)` 会导致调用 `a.__format__("abc")` 。当定义你自己的数值类型或字符串类型时,你可能想提供某些特殊的格式化选项,这种情况下这个魔法方法会非常有用。
定义当类的实例用于新式字符串格式化时的行为,例如, `"Hello, 0:abc!".format(a)` 会导致调用 `a.__format__("abc")` 。当定义你自己的数值类型或字符串类型时,你可能想提供某些特殊的格式化选项,这种情况下这个魔法方法会非常有用。

- `__hash__(self)`

定义对类的实例调用 `hash()` 时的行为。它必须返回一个整数,其结果会被用于字典中键的快速比较。同时注意一点,实现这个魔法方法通常也需要实现 `__eq__` ,并且遵守如下的规则: `a == b` 意味着 `hash(a) == hash(b)`。
定义对类的实例调用 `hash()` 时的行为。它必须返回一个整数,其结果会被用于字典中键的快速比较。同时注意一点,实现这个魔法方法通常也需要实现 `__eq__` ,并且遵守如下的规则: `a == b` 意味着 `hash(a) == hash(b)`。

- `__nonzero__(self)`

定义对类的实例调用 `bool()` 时的行为,根据你自己对类的设计,针对不同的实例,这个魔法方法应该相应地返回True或False。
定义对类的实例调用 `bool()` 时的行为,根据你自己对类的设计,针对不同的实例,这个魔法方法应该相应地返回True或False。

- `__dir__(self)`

定义对类的实例调用 `dir()` 时的行为,这个方法应该向调用者返回一个属性列表。一般来说,没必要自己实现 `__dir__` 。但是如果你重定义了 `__getattr__` 或者 `__getattribute__`(下个部分会介绍),乃至使用动态生成的属性,以实现类的交互式使用,那么这个魔法方法是必不可少的。
定义对类的实例调用 `dir()` 时的行为,这个方法应该向调用者返回一个属性列表。一般来说,没必要自己实现 `__dir__` 。但是如果你重定义了 `__getattr__` 或者 `__getattribute__` (下个部分会介绍),乃至使用动态生成的属性,以实现类的交互式使用,那么这个魔法方法是必不可少的。



Expand Down Expand Up @@ -640,11 +641,11 @@ Python也有一系列的魔法方法用于实现类似 `float()` 的内建类型

- `__instancecheck__(self, instance)`

检查一个实例是否是你定义的类的一个实例(例如 `isinstance(instance, class)` )。
检查一个实例是否是你定义的类的一个实例(例如 `isinstance(instance, class)` )。

- `__subclasscheck__(self, subclass)`

检查一个类是否是你定义的类的子类(例如 `issubclass(subclass, class)` )。
检查一个类是否是你定义的类的子类(例如 `issubclass(subclass, class)` )。


这几个魔法方法的适用范围看起来有些窄,事实也正是如此。我不会在反射魔法方法上花费太多时间,因为相比其他魔法方法它们显得不是很重要。但是它们展示了在Python中进行面向对象编程(或者总体上使用Python进行编程)时很重要的一点:不管做什么事情,都会有一个简单方法,不管它常用不常用。这些魔法方法可能看起来没那么有用,但是当你真正需要用到它们的时候,你会感到很幸运,因为它们还在那儿(也因为你阅读了这本指南!)
Expand All @@ -665,9 +666,9 @@ Python中一个特殊的魔法方法允许你自己类的对象表现得像是

- `__call__(self, [args...])`

允许类的一个实例像函数那样被调用。本质上这代表了 `x()` 和 `x.__call__()` 是相同的。注意 `__call__` 可以有多个参数,这代表你可以像定义其他任何函数一样,定义 `__call__` ,喜欢用多少参数就用多少。
允许类的一个实例像函数那样被调用。本质上这代表了 `x()` 和 `x.__call__()` 是相同的。注意 `__call__` 可以有多个参数,这代表你可以像定义其他任何函数一样,定义 `__call__` ,喜欢用多少参数就用多少。

`__call__` 在某些需要经常改变状态的类的实例中显得特别有用。“调用”这个实例来改变它的状态,是一种更加符合直觉,也更加优雅的方法。一个表示平面上实体的类是一个不错的例子::
`__call__` 在某些需要经常改变状态的类的实例中显得特别有用。“调用”这个实例来改变它的状态,是一种更加符合直觉,也更加优雅的方法。一个表示平面上实体的类是一个不错的例子::
class Entity:
'''表示一个实体的类,调用它的实例
Expand All @@ -693,13 +694,13 @@ Python中一个特殊的魔法方法允许你自己类的对象表现得像是

当对象使用 `with` 声明创建时,上下文管理器允许类做一些设置和清理工作。上下文管理器的行为由下面两个魔法方法所定义:

- `__enter__(self)
- `__enter__(self)`

定义使用 `with` 声明创建的语句块最开始上下文管理器应该做些什么。注意 `__enter__` 的返回值会赋给 `with` 声明的目标,也就是 `as` 之后的东西。
定义使用 `with` 声明创建的语句块最开始上下文管理器应该做些什么。注意 `__enter__` 的返回值会赋给 `with` 声明的目标,也就是 `as` 之后的东西。

- `__exit__(self, exception_type, exception_value, traceback)
- `__exit__(self, exception_type, exception_value, traceback)`

定义当 `with` 声明语句块执行完毕(或终止)时上下文管理器的行为。它可以用来处理异常,进行清理,或者做其他应该在语句块结束之后立刻执行的工作。如果语句块顺利执行, `exception_type` , `exception_value` 和 `traceback` 会是 `None` 。否则,你可以选择处理这个异常或者让用户来处理。如果你想处理异常,确保 `__exit__` 在完成工作之后返回 `True` 。如果你不想处理异常,那就让它发生吧。
定义当 `with` 声明语句块执行完毕(或终止)时上下文管理器的行为。它可以用来处理异常,进行清理,或者做其他应该在语句块结束之后立刻执行的工作。如果语句块顺利执行, `exception_type` , `exception_value` 和 `traceback` 会是 `None` 。否则,你可以选择处理这个异常或者让用户来处理。如果你想处理异常,确保 `__exit__` 在完成工作之后返回 `True` 。如果你不想处理异常,那就让它发生吧。



Expand Down Expand Up @@ -753,15 +754,15 @@ Python中一个特殊的魔法方法允许你自己类的对象表现得像是

- `__get__(self, instance, owner)`

定义当试图取出描述符的值时的行为。 `instance` 是拥有者类的实例, `owner` 是拥有者类本身。
定义当试图取出描述符的值时的行为。 `instance` 是拥有者类的实例, `owner` 是拥有者类本身。

- `__set__(self, instance, owner)`

定义当描述符的值改变时的行为。 `instance` 是拥有者类的实例, `value` 是要赋给描述符的值。
定义当描述符的值改变时的行为。 `instance` 是拥有者类的实例, `value` 是要赋给描述符的值。

- `__delete__(self, instance, owner)`

定义当描述符的值被删除时的行为。 `instance` 是拥有者类的实例
定义当描述符的值被删除时的行为。 `instance` 是拥有者类的实例


现在,来看一个描述符的有效应用:单位转换::
Expand Down Expand Up @@ -798,20 +799,143 @@ Python中一个特殊的魔法方法允许你自己类的对象表现得像是

- `__copy__(self)`

定义对类的实例使用 `copy.copy()` 时的行为。 `copy.copy()` 返回一个对象的浅拷贝,这意味着拷贝出的实例是全新的,然而里面的数据全都是引用的。也就是说,对象本身是拷贝的,但是它的数据还是引用的(所以浅拷贝中的数据更改会影响原对象)。
定义对类的实例使用 `copy.copy()` 时的行为。 `copy.copy()` 返回一个对象的浅拷贝,这意味着拷贝出的实例是全新的,然而里面的数据全都是引用的。也就是说,对象本身是拷贝的,但是它的数据还是引用的(所以浅拷贝中的数据更改会影响原对象)。

- `__deepcopy__(self, memodict=)
- `__deepcopy__(self, memodict=)`

定义对类的实例使用 `copy.deepcopy()` 时的行为。 `copy.deepcopy()` 返回一个对象的深拷贝,这个对象和它的数据全都被拷贝了一份。 `memodict` 是一个先前拷贝对象的缓存,它优化了拷贝过程,而且可以防止拷贝递归数据结构时产生无限递归。当你想深拷贝一个单独的属性时,在那个属性上调用 `copy.deepcopy()` ,使用 `memodict` 作为第一个参数。
定义对类的实例使用 `copy.deepcopy()` 时的行为。 `copy.deepcopy()` 返回一个对象的深拷贝,这个对象和它的数据全都被拷贝了一份。 `memodict` 是一个先前拷贝对象的缓存,它优化了拷贝过程,而且可以防止拷贝递归数据结构时产生无限递归。当你想深拷贝一个单独的属性时,在那个属性上调用 `copy.deepcopy()` ,使用 `memodict` 作为第一个参数。

这些魔法方法有什么用武之地呢?像往常一样,当你需要比默认行为更加精确的控制时。例如,如果你想拷贝一个对象,其中存储了一个字典作为缓存(可能会很大),拷贝缓存可能是没有意义的。如果这个缓存可以在内存中被不同实例共享,那么它就应该被共享。

Pickling
---------

如果你和其他的Python爱好者共事过,很可能你已经听说过Pickling了。Pickling是Python的数据结构的
如果你和其他的Python爱好者共事过,很可能你已经听说过Pickling了。Pickling是Python数据结构的序列化过程,当你想存储一个对象稍后再取出读取时,Pickling会显得十分有用。然而它同样也是担忧和混淆的主要来源。

Pickling是如此的重要,以至于它不仅仅有自己的模块( `pickle` ),还有自己的协议和魔法方法。首先,我们先来简要的介绍一下如何pickle已存在的对象类型(如果你已经知道了,大可跳过这部分内容)。


Pickling : 小试牛刀
'''''''''''''''''''''

我们一起来pickle吧。假设你有一个字典,你想存储它,稍后再取出来。你可以把它的内容写入一个文件,小心翼翼地确保使用了正确地格式,要把它读取出来,你可以使用 `exec()` 或处理文件输入。但是这种方法并不可靠:如果你使用纯文本来存储重要数据,数据很容易以多种方式被破坏或者修改,导致你的程序崩溃,更糟糕的情况下,还可能在你的计算机上运行恶意代码。因此,我们要pickle它::

import pickle
data = {'foo': [1,2,3],
'bar': ('Hello', 'world!'),
'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # 将pickle后的数据写入jar文件
jar.close()

过了几个小时,我们想把它取出来,我们只需要反pickle它::

import pickle
pkl_file = open('data.pkl', 'rb') # 与pickle后的数据连接
data = pickle.load(pkl_file) # 把它加载进一个变量
print data
pkl_file.close()
将会发生什么?正如你期待的,它就是我们之前的 `data` 。

现在,还需要谨慎地说一句: pickle并不完美。Pickle文件很容易因为事故或被故意的破坏掉。Pickling或许比纯文本文件安全一些,但是依然有可能被用来运行恶意代码。而且它还不支持跨Python版本,所以不要指望分发pickle对象之后所有人都能正确地读取。然而不管怎么样,它依然是一个强有力的工具,可以用于缓存和其他类型的持久化工作。


Pickle你的对象
'''''''''''''''

Pickle不仅仅可以用于内建类型,任何遵守pickle协议的类都可以被pickle。Pickle协议有四个可选方法,可以让类自定义它们的行为(这和C语言扩展略有不同,那不在我们的讨论范围之内)。

- `__getinitargs__(self)`

如果你想让你的类在反pickle时调用 `__init__` ,你可以定义 `__getinitargs__(self)` ,它会返回一个参数元组,这个元组会传递给 `__init__` 。注意,这个方法只能用于旧式类。

- `__getnewargs__(self)`

对新式类来说,你可以通过这个方法改变类在反pickle时传递给 `__new__` 的参数。这个方法应该返回一个参数元组。

- `__getstate__(self)`

你可以自定义对象被pickle时被存储的状态,而不使用对象的 `__dict__` 属性。 这个状态在对象被反pickle时会被 `__setstate__` 使用。

- `__setstate__(self)`

当一个对象被反pickle时,如果定义了 `__setstate__` ,对象的状态会传递给这个魔法方法,而不是直接应用到对象的 `__dict__` 属性。这个魔法方法和 `__getstate__` 相互依存:当这两个方法都被定义时,你可以在Pickle时使用任何方法保存对象的任何状态。

- `__reduce__(self)`

当定义扩展类型时(也就是使用Python的C语言API实现的类型),如果你想pickle它们,你必须告诉Python如何pickle它们。 `__reduce__` 被定义之后,当对象被Pickle时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。这个元组包含2到5个元素,其中包括:一个可调用的对象,用于重建对象时调用;一个参数元素,供那个可调用对象使用;被传递给 `__setstate__` 的状态(可选);一个产生被pickle的列表元素的迭代器(可选);一个产生被pickle的字典元素的迭代器(可选);

- `__reduce_ex__(self)`

`__reduce_ex__` 的存在是为了兼容性。如果它被定义,在pickle时 `__reduce_ex__` 会代替 `__reduce__` 被调用。 `__reduce__` 也可以被定义,用于不支持 `__reduce_ex__` 的旧版pickle的API调用。


一个例子
'''''''''

我们的例子是 `Slate` ,它会记住它的值曾经是什么,以及那些值是什么时候赋给它的。然而
每次被pickle时它都会变成空白,因为当前的值不会被存储::

import time
class Slate:
'''存储一个字符串和一个变更日志的类
每次被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_change(self):
print 'Changelog for Slate object:'
for k,v in self.history.items():
print '%s\t %s' % (k,v)
def __getstate__(self):
# 故意不返回self.value或self.last_change
# 我们想在反pickle时得到一个空白的slate
return self.history
def __setstate__(self):
# 使self.history = slate,last_change
# 和value为未定义
self.history = state
self.value, self.last_change = None, None
总结
------

这本指南的目标是使所有阅读它的人都能有所收获,无论他们有没有使用Python或者进行面向对象编程的经验。如果你刚刚开始学习Python,你会得到宝贵的基础知识,了解如何写出具有丰富特性的,优雅而且易用的类。如果你是中级的Python程序员,你或许能掌握一些新的概念和技巧,以及一些可以减少代码行数的好办法。如果你是专家级别的Python爱好者,你又重新复习了一遍某些可能已经忘掉的知识,也可能顺便了解了一些新技巧。无论你的水平怎样,我希望这趟遨游Python特殊方法的旅行,真的对你产生了魔法般的效果(实在忍不住不说最后这个双关)。

附录1:如何调用魔法方法
-------------------------

一些魔法方法直接和内建函数对应,这种情况下,如何调用它们是显而易见的。然而,另外的情况下,调用魔法方法的途径并不是那么明显。这个附录旨在展示那些不那么明显的调用魔法方法的语法。


附录2:Python 3中的变化
------------------------

在这里,我们记录了几个在对象模型方面Python 3 和 Python 2.x之间的主要区别。

- Python 3中string和unicode的区别不复存在,因此 `__unicode__` 被取消了, `__bytes__` 加入进来(与Python 2.7 中的 `__str__` 和 `__unicode__` 行为类似),用于新的创建字节数组的内建方法。

- Python 3中默认除法变成了 true 除法,因此 `__div__` 被取消了。

- `__coerce__` 被取消了,因为和其他魔法方法有功能上的重复,以及本身行为令人迷惑。

- `__cmp__` 被取消了,因为和其他魔法方法有功能上的重复。

未完待续...
- `__nonzero__` 被重命名成 `__bool__` 。

0 comments on commit 7c1c3ea

Please sign in to comment.