本指南是数月博客的总结。主题是魔术方法。
什么是魔术方法呢?它们是面向对象Python语言中的一切。它们是你可以自定义并添加“魔法”到类中的特殊方法。它们被双下划线环绕(比如__init__或__lt__)。它们的文档也不像它所需要的那么齐备。Python的所有魔术方法都在Python文档的同一区域,但它们的使用分散,组织松散。而且文档的这部分区域中几乎没有一个示例(这很有可能是设计好的,因为在语法参考里它们都很详尽,但伴随的是枯燥的语法描述等等)。
因此,为了解决Python文档中我认为的缺陷,我想提供一些更简单直白的表述——示例驱动型的Python魔术方法文档。我从每周的博客开始,现在我已经完成了,并把它们合到了一起。
我希望你能喜欢它。把它作为一个教程、复习或参考使用;它希望能成为一个Python魔术方法用户友好的指导。
构造与初始化
我们每个知道的最基本的“魔法”方法是__init__。一种让我们在初始化一个类时定义一些行为。然而当我执行 x = SomeClass(), __init__ 不是第一个被执行的。事实上,第一被执行的的方法是__new__,它会创建一个实例,然后在构造器创建时传递一些参数。在一个object的生命周期的另一端的方法是__del__。让我们仔细看看这3个“魔法”方法:
把他们合起来后,这里就是一个 __init__ 和 __del__ 使用的例子:
| 12345678910 | 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 类型。这就意味着你可以避免丑陋的,违反直觉的,非标准化的基本操作方法。在一些语言中,他们通常这样写:
| 12 | if instance.equals(other_instance): # do something |
当让Python中也可以这么做,但是这增加了混乱和不必要的冗余。不同的类库中的相同的方法可能会用不同名字,使得使用者做了太多不必要的操作。相比之下“魔法”方法是强大的,我们可以使用它定义一个方法代替上面的例子(__eq__ , 在这个例子中):
| 12 | if instance == other_instance: #do something |
这是“魔法”方法强大用途的一部分。他们绝大部分让我们定义操作的意义,以至于我们可以使用他们在我们自己的类中就像使用built in 类型。
python拥有大量用于实现对象与对象之间比较的魔法方法,这些对象使用运算符进行直观比较而不是难看的方法调用。同时它也提供了一种方法去重载python默认的对象比较行为(比较引用)。这里有一个这些方法和它们做了什么事情的列表:
例如,假设一个类是一个单词模型。我们可能要按字典比较单词(按字母),这是比较字符串默认行为,但我们也可能需要基于其他一些标准来做比较,比如按长度、或字节数量等。在下面的例子中,我们将比较长度。下面是实现:
| 12345678910111213141516171819 | 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),绝对值操作等。
好吧,现在我们开始介绍双目运算操作或函数,比如 +, -, * 等等. 这些很容易自解释.
你相信我说我能用一位来表示反射运算吗?可能有人会认为表示一个反射运算是大的吓人的“外国概念”,反射实际上它是非常简单的。看下面的例子:
| 1 | some_object + other |
| 12345678910 | 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 类型。这就意味着你可以避免丑陋的,违反直觉的,非标准化的基本操作方法。在一些语言中,他们通常这样写:
| 12 | if instance.equals(other_instance): # do something |
当让Python中也可以这么做,但是这增加了混乱和不必要的冗余。不同的类库中的相同的方法可能会用不同名字,使得使用者做了太多不必要的操作。相比之下“魔法”方法是强大的,我们可以使用它定义一个方法代替上面的例子(__eq__ , 在这个例子中):
| 12 | if instance == other_instance: #do something |
这是“魔法”方法强大用途的一部分。他们绝大部分让我们定义操作的意义,以至于我们可以使用他们在我们自己的类中就像使用built in 类型。
python拥有大量用于实现对象与对象之间比较的魔法方法,这些对象使用运算符进行直观比较而不是难看的方法调用。同时它也提供了一种方法去重载python默认的对象比较行为(比较引用)。这里有一个这些方法和它们做了什么事情的列表:
例如,假设一个类是一个单词模型。我们可能要按字典比较单词(按字母),这是比较字符串默认行为,但我们也可能需要基于其他一些标准来做比较,比如按长度、或字节数量等。在下面的例子中,我们将比较长度。下面是实现:
| 12345678910111213141516171819 | 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),绝对值操作等。
好吧,现在我们开始介绍双目运算操作或函数,比如 +, -, * 等等. 这些很容易自解释.
你相信我说我能用一位来表示反射运算吗?可能有人会认为表示一个反射运算是大的吓人的“外国概念”,反射实际上它是非常简单的。看下面的例子:
| 1 | some_object + other |
| 12 | if instance == other_instance |