介绍
本指南是数月博客的总结。主题是魔术方法。
什么是魔术方法呢?它们是面向对象Python语言中的一切。它们是你可以自定义并添加“魔法”到类中的特殊方法。它们被双下划线环绕(比如__init__或__lt__)。它们的文档也不像它所需要的那么齐备。Python的所有魔术方法都在Python文档的同一区域,但它们的使用分散,组织松散。而且文档的这部分区域中几乎没有一个示例(这很有可能是设计好的,因为在语法参考里它们都很详尽,但伴随的是枯燥的语法描述等等)。
因此,为了解决Python文档中我认为的缺陷,我想提供一些更简单直白的表述——示例驱动型的Python魔术方法文档。我从每周的博客开始,现在我已经完成了,并把它们合到了一起。
我希望你能喜欢它。把它作为一个教程、复习或参考使用;它希望能成为一个Python魔术方法用户友好的指导。
构造与初始化
我们每个知道的最基本的“魔法”方法是__init__。一种让我们在初始化一个类时定义一些行为。然而当我执行 x = SomeClass(), __init__ 不是第一个被执行的。事实上,第一被执行的的方法是__new__,它会创建一个实例,然后在构造器创建时传递一些参数。在一个object的生命周期的另一端的方法是__del__。让我们仔细看看这3个“魔法”方法:
- __new__(cls, […)
- __new__ 是一个类的初始化过程中第一个被执行的方法。它创建了类,然后把一些参数传递给__init__。__new__ 很少被使用,特别是当我们用一些不可变类型的子类时(像tuple ,string),我不想关心__new__的太多的细节,因为那是没有用的。但它有它存在的意义。更多详细的请看 in the Python docs.
- __init__(self, […)
- 类的构造器,当初始构造方法被执行(例如,我们执行 x = SomeClass(10,’foo’)),__init__ 就会获得 10 和 ‘foo’ 作为参数。__init__ 在python类的定义中经常被使用
- __del__(self)
- 若果 __new__ 和 __init__ 形成一个类的构造函数,__del__ 是就是析构函数。它不实现语句 del x 的行为(这样代码就不会转换为 x.__del__())。它定义了一个被垃圾回收的行为。它在类消除的时后需要做一些额外的行为时是非常有用的,就像 sockets 和 file 类。注意,当编译器还在运行,如果类还存活着,这里不能确保__del__一定会被执行。所以__del__ 不能替代一些良好的编程习惯(比如连接用完了将其关掉),事实上__del__很少被使用,因为它的调用是非常不稳定的;请谨慎使用!
把他们合起来后,这里就是一个 __init__ 和 __del__ 使用的例子:
1 2 3 4 5 6 7 8 9 10 |
from os.path import joinclass FileObject: '''Wrapper for file objects to make sure the file gets closed on deletion.''' def __init__(self, filepath='~', filename='sample.txt'): # open a file filename in filepath in read and write mode self.file = open(join(filepath, filename), 'r+') def __del__(self): self.file.close() del self.file |
定义自己的类中的操作
我们使用Python的“魔法”方法最大得优势之一是它提供了一种简单的方法去定义类的行为,比如 built-in 类型。这就意味着你可以避免丑陋的,违反直觉的,非标准化的基本操作方法。在一些语言中,他们通常这样写:
1 2 |
if instance.equals(other_instance): # do something |
当让Python中也可以这么做,但是这增加了混乱和不必要的冗余。不同的类库中的相同的方法可能会用不同名字,使得使用者做了太多不必要的操作。相比之下“魔法”方法是强大的,我们可以使用它定义一个方法代替上面的例子(__eq__ , 在这个例子中):
1 2 |
if instance == other_instance: #do something |
这是“魔法”方法强大用途的一部分。他们绝大部分让我们定义操作的意义,以至于我们可以使用他们在我们自己的类中就像使用built in 类型。
魔法比较方法
python拥有大量用于实现对象与对象之间比较的魔法方法,这些对象使用运算符进行直观比较而不是难看的方法调用。同时它也提供了一种方法去重载python默认的对象比较行为(比较引用)。这里有一个这些方法和它们做了什么事情的列表:
- __cmp__(self, other)
- __cmp__ 是比较方法里面最基本的的魔法方法。实际上它实现了所有的比较运算符(如<, ==, !=)的行为,但也许不是你想要的行为(例如,一个实例是否和另一个实例相等应该由某个条件来决定,一个实例是否大于另一个实例应该由其他的条件来决定)。当self < other时__cmp__应该返回一个负整数,当self == other时返回0,self > other时返回正整数。通常来说最好是定义每个你需要的比较方法而不是一次性定义所有的比较方法,但是__cmp__是一个消除重复性的良途,并且当你有很多比较方法需要用相类似的条件去实现的时候这能让代码变得清晰。
- __eq__(self, other)
- 定义相等符号的行为,==
- __ne__(self,other)
- 定义不等符号的行为,!=
- __lt__(self,other)
- 定义小于符号的行为,<
- __gt__(self,other)
- 定义大于符号的行为,>
- __le__(self,other)
- 定义小于等于符号的行为,<=
- __ge__(self,other)
- 定义大于等于符号的行为,>=
例如,假设一个类是一个单词模型。我们可能要按字典比较单词(按字母),这是比较字符串默认行为,但我们也可能需要基于其他一些标准来做比较,比如按长度、或字节数量等。在下面的例子中,我们将比较长度。下面是实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Word(str): '''Class for words, defining comparison based on word length.''' def __new__(cls, word): # Note that we have to use __new__. This is because str is an immutable # type, so we have to initialize it early (at creation) if ' ' in word: print "Value contains spaces. Truncating to first space." word = word[:word.index(' ')] # Word is now all chars before first space return str.__new__(cls, word) def __gt__(self, other): return len(self) > len(other) def __lt__(self, other): return len(self) < len(other) def __ge__(self, other): return len(self) >= len(other) def __le__(self, other): return len(self) <= len(other) |
现在,我们可以创建两个单词(通过使用Word(‘foo’)和Word(‘bar’))然后依据长度比较它们。但要注意,我们没有定义__eq__和__ne__,因为这样会导致其他一些奇怪的行为(尤其是Word(‘foo’)==Word(‘bar’)会判定为true),它不是基于长度相等意义上的测量,所以我们回归到字符平等意义上的实现。
现在需要留心啦——为达到预期的比较效果你不需要为每个比较定义魔术方法。如果你只定义__eq__以及其他的(比如__gt__,__lt__等),标准库已经在functools模块里为我们提供了一个类修饰器,它可以定义所有的富特性比较方法。这个特性只有在Python 2.7中才是可用的,但如果你碰巧的话这可以节省大量的时间和精力。你可以通过将@total_ordering放置在类定义前面来使用它。
数值魔术方法
就如同你可以通过定义比较操作来比较你自己的类实例一样,你也可以自己定义数学运算符号的行为。好吧,先系紧你的裤腰带,深呼吸……,这些操作可多着呢。由于文章组织需要,我把这些数学“魔术方法”分为5类:单目运算操作,一般数学运算操作,满足交换律的数学运算(后面会有更多介绍),参数赋值操作和类型转换操作:
单目运算符操作与函数:
单目运算符或单目运算函数只有一个操作数: 比如取负(-2),绝对值操作等。
- __pos__(self)
- 实现一个取正数的操作(比如 +some_object ,python调用__pos__函数)
- __neg__(self)
- 实现一个取负数的操作(比如 -some_object )
- __abs__(self)
- 实现一个内建的abs()函数的行为
- __invert__(self)
- 实现一个取反操作符(~操作符)的行为。想要了解这个操作的解释,参考the Wikipedia article on bitwise operations.
- __round__(self, n)
- 实现一个内建的round()函数的行为。 n 是待取整的十进制数.
- __floor__(self)
- 实现math.floor()的函数行为,比如, 把数字下取整到最近的整数.
- __ceil__(self)
- 实现math.ceil()的函数行为,比如, 把数字上取整到最近的整数.
- __trunc__(self)
- 实现math.trunc()的函数行为,比如, 把数字截断而得到整数.
-
一般算数运算
好吧,现在我们开始介绍双目运算操作或函数,比如 +, -, * 等等. 这些很容易自解释.
- __add__(self, other)
- 实现一个加法.
- __sub__(self, other)
- 实现一个减法.
- __mul__(self, other)
- 实现一个乘法.
- __floordiv__(self, other)
- 实现一个“//”操作符产生的整除操作()
- __div__(self, other)
- 实现一个“/”操作符代表的除法操作.
- __truediv__(self, other)
- 实现真实除法,注意,只有当你from __future__ import division时才会有效
- __mod__(self, other)实现一个“%”操作符代表的取模操作.
- __divmod__(self, other)
- 实现一个内建函数divmod()
- __pow__
- 实现一个指数操作(“**”操作符)的行为
- __lshift__(self, other)
- 实现一个位左移操作(<<)的功能
- __rshift__(self, other)
- 实现一个位右移操作(>>)的功能.
- __and__(self, other)
- 实现一个按位进行与操作(&)的行为.
- __or__(self, other)实现一个按位进行或操作(|)的行为.
- __xor__(self, other)
- 实现一个异或操作(^)的行为
反射算术运算符
你相信我说我能用一位来表示反射运算吗?可能有人会认为表示一个反射运算是大的吓人的“外国概念”,反射实际上它是非常简单的。看下面的例子:
1 |
some_object + other |
1 2 3 4 5 6 7 8 9 10 |
from os.path import joinclass FileObject: '''Wrapper for file objects to make sure the file gets closed on deletion.''' def __init__(self, filepath='~', filename='sample.txt'): # open a file filename in filepath in read and write mode self.file = open(join(filepath, filename), 'r+') def __del__(self): self.file.close() del self.file |
定义自己的类中的操作
我们使用Python的“魔法”方法最大得优势之一是它提供了一种简单的方法去定义类的行为,比如 built-in 类型。这就意味着你可以避免丑陋的,违反直觉的,非标准化的基本操作方法。在一些语言中,他们通常这样写:
1 2 |
if instance.equals(other_instance): # do something |
当让Python中也可以这么做,但是这增加了混乱和不必要的冗余。不同的类库中的相同的方法可能会用不同名字,使得使用者做了太多不必要的操作。相比之下“魔法”方法是强大的,我们可以使用它定义一个方法代替上面的例子(__eq__ , 在这个例子中):
1 2 |
if instance == other_instance: #do something |
这是“魔法”方法强大用途的一部分。他们绝大部分让我们定义操作的意义,以至于我们可以使用他们在我们自己的类中就像使用built in 类型。
魔法比较方法
python拥有大量用于实现对象与对象之间比较的魔法方法,这些对象使用运算符进行直观比较而不是难看的方法调用。同时它也提供了一种方法去重载python默认的对象比较行为(比较引用)。这里有一个这些方法和它们做了什么事情的列表:
- __cmp__(self, other)
- __cmp__ 是比较方法里面最基本的的魔法方法。实际上它实现了所有的比较运算符(如<, ==, !=)的行为,但也许不是你想要的行为(例如,一个实例是否和另一个实例相等应该由某个条件来决定,一个实例是否大于另一个实例应该由其他的条件来决定)。当self < other时__cmp__应该返回一个负整数,当self == other时返回0,self > other时返回正整数。通常来说最好是定义每个你需要的比较方法而不是一次性定义所有的比较方法,但是__cmp__是一个消除重复性的良途,并且当你有很多比较方法需要用相类似的条件去实现的时候这能让代码变得清晰。
- __eq__(self, other)
- 定义相等符号的行为,==
- __ne__(self,other)
- 定义不等符号的行为,!=
- __lt__(self,other)
- 定义小于符号的行为,<
- __gt__(self,other)
- 定义大于符号的行为,>
- __le__(self,other)
- 定义小于等于符号的行为,<=
- __ge__(self,other)
- 定义大于等于符号的行为,>=
例如,假设一个类是一个单词模型。我们可能要按字典比较单词(按字母),这是比较字符串默认行为,但我们也可能需要基于其他一些标准来做比较,比如按长度、或字节数量等。在下面的例子中,我们将比较长度。下面是实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Word(str): '''Class for words, defining comparison based on word length.''' def __new__(cls, word): # Note that we have to use __new__. This is because str is an immutable # type, so we have to initialize it early (at creation) if ' ' in word: print "Value contains spaces. Truncating to first space." word = word[:word.index(' ')] # Word is now all chars before first space return str.__new__(cls, word) def __gt__(self, other): return len(self) > len(other) def __lt__(self, other): return len(self) < len(other) def __ge__(self, other): return len(self) >= len(other) def __le__(self, other): return len(self) <= len(other) |
现在,我们可以创建两个单词(通过使用Word(‘foo’)和Word(‘bar’))然后依据长度比较它们。但要注意,我们没有定义__eq__和__ne__,因为这样会导致其他一些奇怪的行为(尤其是Word(‘foo’)==Word(‘bar’)会判定为true),它不是基于长度相等意义上的测量,所以我们回归到字符平等意义上的实现。
现在需要留心啦——为达到预期的比较效果你不需要为每个比较定义魔术方法。如果你只定义__eq__以及其他的(比如__gt__,__lt__等),标准库已经在functools模块里为我们提供了一个类修饰器,它可以定义所有的富特性比较方法。这个特性只有在Python 2.7中才是可用的,但如果你碰巧的话这可以节省大量的时间和精力。你可以通过将@total_ordering放置在类定义前面来使用它。
数值魔术方法
就如同你可以通过定义比较操作来比较你自己的类实例一样,你也可以自己定义数学运算符号的行为。好吧,先系紧你的裤腰带,深呼吸……,这些操作可多着呢。由于文章组织需要,我把这些数学“魔术方法”分为5类:单目运算操作,一般数学运算操作,满足交换律的数学运算(后面会有更多介绍),参数赋值操作和类型转换操作:
单目运算符操作与函数:
单目运算符或单目运算函数只有一个操作数: 比如取负(-2),绝对值操作等。
- __pos__(self)
- 实现一个取正数的操作(比如 +some_object ,python调用__pos__函数)
- __neg__(self)
- 实现一个取负数的操作(比如 -some_object )
- __abs__(self)
- 实现一个内建的abs()函数的行为
- __invert__(self)
- 实现一个取反操作符(~操作符)的行为。想要了解这个操作的解释,参考the Wikipedia article on bitwise operations.
- __round__(self, n)
- 实现一个内建的round()函数的行为。 n 是待取整的十进制数.
- __floor__(self)
- 实现math.floor()的函数行为,比如, 把数字下取整到最近的整数.
- __ceil__(self)
- 实现math.ceil()的函数行为,比如, 把数字上取整到最近的整数.
- __trunc__(self)
- 实现math.trunc()的函数行为,比如, 把数字截断而得到整数.
-
一般算数运算
好吧,现在我们开始介绍双目运算操作或函数,比如 +, -, * 等等. 这些很容易自解释.
- __add__(self, other)
- 实现一个加法.
- __sub__(self, other)
- 实现一个减法.
- __mul__(self, other)
- 实现一个乘法.
- __floordiv__(self, other)
- 实现一个“//”操作符产生的整除操作()
- __div__(self, other)
- 实现一个“/”操作符代表的除法操作.
- __truediv__(self, other)
- 实现真实除法,注意,只有当你from __future__ import division时才会有效
- __mod__(self, other)实现一个“%”操作符代表的取模操作.
- __divmod__(self, other)
- 实现一个内建函数divmod()
- __pow__
- 实现一个指数操作(“**”操作符)的行为
- __lshift__(self, other)
- 实现一个位左移操作(<<)的功能
- __rshift__(self, other)
- 实现一个位右移操作(>>)的功能.
- __and__(self, other)
- 实现一个按位进行与操作(&)的行为.
- __or__(self, other)实现一个按位进行或操作(|)的行为.
- __xor__(self, other)
- 实现一个异或操作(^)的行为
反射算术运算符
你相信我说我能用一位来表示反射运算吗?可能有人会认为表示一个反射运算是大的吓人的“外国概念”,反射实际上它是非常简单的。看下面的例子:
1 |
some_object + other |
1 2 |
if instance == other_instance |