开源中国

我们不支持 IE 10 及以下版本浏览器

It appears you’re using an unsupported browser

为了获得更好的浏览体验,我们强烈建议您使用较新版本的 Chrome、 Firefox、 Safari 等,或者升级到最新版本的IE浏览器。 如果您使用的是 IE 11 或以上版本,请关闭“兼容性视图”。
Python 的 Magic Methods 指南 - 技术翻译 - 开源中国社区

Python 的 Magic Methods 指南 【已翻译100%】

标签: Python
霄宇 推荐于 4年前 (共 23 段, 翻译完成于 01-05) 评论 9
收藏  
185
推荐标签: Python 待读

介绍

本指南是数月博客的总结。主题是魔术方法。

什么是魔术方法呢?它们是面向对象Python语言中的一切。它们是你可以自定义并添加“魔法”到类中的特殊方法。它们被双下划线环绕(比如__init__或__lt__)。它们的文档也不像它所需要的那么齐备。Python的所有魔术方法都在Python文档的同一区域,但它们的使用分散,组织松散。而且文档的这部分区域中几乎没有一个示例(这很有可能是设计好的,因为在语法参考里它们都很详尽,但伴随的是枯燥的语法描述等等)。

因此,为了解决Python文档中我认为的缺陷,我想提供一些更简单直白的表述——示例驱动型的Python魔术方法文档。我从每周的博客开始,现在我已经完成了,并把它们合到了一起。

我希望你能喜欢它。把它作为一个教程、复习或参考使用;它希望能成为一个Python魔术方法用户友好的指导。

Garfielt
 翻译得不错哦!

构造与初始化

我们每个知道的最基本的“魔法”方法是__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__ 使用的例子:

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 类型。这就意味着你可以避免丑陋的,违反直觉的,非标准化的基本操作方法。在一些语言中,他们通常这样写:

if instance.equals(other_instance):
    # do something

当让Python中也可以这么做,但是这增加了混乱和不必要的冗余。不同的类库中的相同的方法可能会用不同名字,使得使用者做了太多不必要的操作。相比之下“魔法”方法是强大的,我们可以使用它定义一个方法代替上面的例子(__eq__ , 在这个例子中):

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)

  • 定义大于等于符号的行为,>=

ITgo
 翻译得不错哦!

例如,假设一个类是一个单词模型。我们可能要按字典比较单词(按字母),这是比较字符串默认行为,但我们也可能需要基于其他一些标准来做比较,比如按长度、或字节数量等。在下面的例子中,我们将比较长度。下面是实现:

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放置在类定义前面来使用它。

Garfielt
 翻译得不错哦!

数值魔术方法

就如同你可以通过定义比较操作来比较你自己的类实例一样,你也可以自己定义数学运算符号的行为。好吧,先系紧你的裤腰带,深呼吸......,这些操作可多着呢。由于文章组织需要,我把这些数学“魔术方法”分为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)

    • 实现一个异或操作(^)的行为

superligen
 翻译得不错哦!

反射算术运算符

你相信我说我能用一位来表示反射运算吗?可能有人会认为表示一个反射运算是大的吓人的“外国概念”,反射实际上它是非常简单的。看下面的例子:

some_object + other

这是一个正常的加法。除了可以交换操作数以外,反射运算和加法是一样的:

other + some_object

除了执行那种 other对像作为第一个操作数,而它自身作为第二个操作数的运算以外,所有的魔法方法做的事情与正常运算表示的意义是等价的。在大部分情况下反射运算结果和它正常的运算是等价的,所以你可以不定义__radd__,而是调用__add__等等。注意,对像(本例中的other)在运算符左边的时候,必须保证该对像没有定义(或者返回NotImplemented的)它的非反射运算符。例如,在这个例子中,some_object.__radd__  只有在 other没有定义__add__的时候才会被调用。

  • __radd__(self, other)

  • 反射加法

  • __rsub__(self, other)

  • 反射减法的

  • __rmul__(self, other)

  • 反射除法

  • __rfloordiv__(self, other)

  • 反射地板除,使用//运算符的

  • __rdiv__(self, other)

  • 反射除法,使用/运算符的.

  • __rtruediv__(self, other)

  • 反射真除.注意只有from __future__ import division 的时候它才有效

  • __rmod__(self, other)

  • 反射取模运算,使用%运算符.

  • __rdivmod__(self, other)

  • 长除法,使用divmod()内置函数,当divmod(other,self)时被调用.

  • __rpow__

  • 反射乘方,使用**运算符的

  • __rlshift__(self, other)

  • 反射左移,使用<<操作符.

  • __rrshift__(self, other)

  • 反射右移,使用>>操作符.

  • __rand__(self, other)

  • 反射位与,使用&操作符.

  • __ror__(self, other)

  • 反射位或,使用|操作符.

  • __rxor__(self, other)

  • 反射异或,使用^操作符.

Rhys
 翻译得不错哦!

增量运算

Python 还有很多种魔法方法,允许一些习惯行为被定义成增量运算。你很可能已经熟悉了增量运算,增量运算是算术运算和赋值运算的结合。如果你还不知道我在说什么,就看一下下面的例子:

x = 5x += 1 # in other words x = x + 1

每一个方法的返回值都会被赋给左边的变量。(比如,对于a += b, __iadd__ 可能会返回a + b, a + b会赋给变量a。) 下面是清单:

  • __iadd__(self, other)

  • 加法赋值

  • __isub__(self, other)

  • 减法赋值.

  • __imul__(self, other)

  • 乘法赋值

  • __ifloordiv__(self, other)

  • 整除赋值,地板除,相当于 //= 运算符.

  • __idiv__(self, other)

  • 除法赋值,相当于 /= 运算符.

  • __itruediv__(self, other)

  • 真除赋值,注意只有你 whenfrom __future__ import divisionis,才有效.

  • __imod_(self, other)

  • 模赋值,相当于 %= 运算符.

  • __ipow__

  • 乘方赋值,相当于 **= 运算符.

  • __ilshift__(self, other)

  • 左移赋值,相当于 <<= 运算符.

  • __irshift__(self, other)

  • 左移赋值,相当于 >>= 运算符.

  • __iand__(self, other)

  • 与赋值,相当于 &= 运算符.

  • __ior__(self, other)

  • 或赋值,相当于 |= 运算符.

  • __ixor__(self, other)

  • 异或运算符,相当于 ^= 运算符.

类型转换魔法

Python 同样有一系列的魔法方法旨在实现内置类型的转换,比如float() 函数。它们是:

  • __int__(self)

  • 转换成整型.

  • __long__(self)

  • 转换成长整型.

  • __float__(self)

  • 转换成浮点型.

  • __complex__(self)

  • 转换成 复数型.

  • __oct__(self)

  • 转换成八进制.

  • __hex__(self)

  • 转换成十六进制.

  • __index__(self)

  • 当对象被切片时转换成int型。如果你定义了一个可能被用来做切片操作的数值型,你就应该定义__index__.

  • __trunc__(self)

  • 当 math.trunc(self) 使用时被调用.__trunc__返回自身类型的整型截取 (通常是一个长整型).

  • __coerce__(self, other)

  • 执行混合类型的运算,如果转换不能完成,应该返回None;否则,要返回一对两个元数的元组self和other, 被操作成同类型。

Rhys
 翻译得不错哦!

表示你的类

用一个字符串来表示一个类往往会非常有用。在Python中,有很多你可以在类定义中实施的方法来自定义内置函数的返回值以表示出你所写出的类的某些行为。

  • __str__(self) 

  • 定义当 str() 被你的一个类的实例调用时所要产生的行为。

  • __repr__(self)

  • 定义 当 repr()  被你的一个类的实例调用时所要产生的行为。 str() repr() 的主要区别是其目标群体。 repr() 返回的是机器可读的输出,而 str() 返回的是人类可读的。 

  • __unicode__(self)

  • 定义当 unicode() 被你的一个类的实例调用时所要产生的行为。 unicode() str() 很相似,但是返回的是unicode字符串。注意,如果对你的类调用 str() 然而你只定义了 __unicode__() ,那么其将不会工作。你应该定义 __str__() 来确保调用时能返回正确的值,并不是每个人都有心情去使用unicode。

  • __format__(self, formatstr)

  • 定义当你的一个类的实例被用来用新式的格式化字符串方法进行格式化时所要产生的行为。例如, "Hello, {0:abc}!".format(a) 将会导致调用 a.__format__("abc") 。这对定义你自己的数值或字符串类型是十分有意义的,你可能会给出一些特殊的格式化选项。

  • __hash__(self) 

  • 定义当 hash()被你的一个类的实例调用时所要产生的行为。它返回一个整数,用来在字典中进行快速比较。请注意,这通常也承担着实现__eq__。有下面这样的规则:a == b 暗示着 hash(a) == hash(b) 。


  • __nonzero__(self) 

  • 定义当 bool() 被你的一个类的实例调用时所要产生的行为。本方法应该返回True或者False,取决于你想让它返回的值。

  • __dir__(self)

  • 定义当 dir() 被你的一个类的实例调用时所要产生的行为。该方法应该返回一个属性的列表给用户,一般而言,实现 __dir__ 是不必要的,但是,如果你重新定义了__getattr__或__getattribute__(你将在下一节中看到)或者其它的动态生成属性,那么它对你的类的交互使用是至关重要的。

  • __sizeof__(self)

  • 定义当 sys.getsizeof() 被你的一个类的实例调用时所要产生的行为。该方法应该以字节为单位,返回你的对象的大小。这通常对于以C扩展的形式实现的Python类更加有意义,其有助于理解这些扩展。

我们几乎完成了对这些枯燥的魔法方法(并且没有实例)的指导。现在,我们已经提及到了一些较基本的魔法方法,到了该转移到更高级内容的时候了。

鄂世嘉
 翻译得不错哦!

属性访问控制

很多用过其它语言的人抱怨Python缺乏对类真正的封装(比如没办法定义private属性和public的getter和settter)。但这不是真的啊:真相是Python通过“魔法”实现了大量的封装,而不是使用明确的方法或字段修饰符。看一下吧:

  • __getattr__(self, name)

  • 你可以定义如何处理用户试图访问一个不存在(不存在或还没创建)属性的行为。这对于捕获或者重定向一般的拼写错误非常有用,给出访问了不能访问的属性的警告(如果你愿意,你还可以推断并返回那个属性。),或者巧妙地处理一个AttributeError异常。它只有在一个不存在的属性被访问的情况下才被调用,然而,这并不是一个真正封装的方案。 

  • __setattr__(self, name, value)

  • 与__getattr__不同,__setattr__是一个真正的封装方案。它允许你定义当给一个存在或不存在的属性赋值时的行为,意味着对任何属性值的改变你都可以定义一个规则。可是,你得小心使用__setattr__,在这个清单结尾的例子会向你说明。

  • __delattr__

  • 它与__setattr__非常像, 只不过是用来删除而不是设置属性。 __detattr__需要预防措施,就像setattr一样,当被调用时可能会引起无限递归(当__delattr__已经实现时,调用 del self.name 就会引起无限的递归)。

  • __getattribute__(self, name)

  •  __getattribute__相当适合它的同伴__setattr__和__delattr__.但我却不建议你使用它。__getattribute__只有在新风格的类中才会被使用(所有的新风格类在Python最新的版本中,在老版本中,你可以子类化object来获得一个新风格类。它允许你定义一条规则来处理无论什么时候属性值被访问时的行为。比如类似于由于其它的伙伴犯错而引起的无限递归(这时你就可以调用基类的__getattribute__方法来阻止它)。它也避免了对__getattr__的依赖,当__getattribute__方法已经实现的时候,__getattr__只有在__getattribute__被明确的调用或抛出一个AttributeError异常的时候才会被调用。这个方法能被使用(毕竟,这是你的选择),但是我不推荐它,因为它很少使用并且运行的时候很难保证没有BUG。 

Rhys
 翻译得不错哦!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们
评论(9)
Ctrl/CMD+Enter

在“复制”那段中第一句,英文原文是“Sometimes, particularly when dealing with mutable objects”,被翻译成了“尤其是当你在处理不可变对象时”。大哥,这错误太2了,其他的错误我都不想指出了~~@鄂世嘉 @红薯
希望翻译出来的东西有人审查下吧~~

引用来自“摩云飞”的评论

在“复制”那段中第一句,英文原文是“Sometimes, particularly when dealing with mutable objects”,被翻译成了“尤其是当你在处理不可变对象时”。大哥,这错误太2了,其他的错误我都不想指出了~~@鄂世嘉 @红薯
希望翻译出来的东西有人审查下吧~~

您这不是就审查了嘛~多谢指正
@superligen
pickle应该叫做序列化比较合适
好文,mark!感谢
我是来看看有没有吐糟翻译质量的

引用来自“DrZ”的评论

@superligen
pickle应该叫做序列化比较合适

多谢,以前说惯了,就顺着写了,序列化要地道点
@红薯 此文在2012-05-18就已经翻译过了,具体网址:http://article.yeeyan.org/view/311527/287706

引用来自“Dyllian”的评论

@红薯 此文在2012-05-18就已经翻译过了,具体网址:http://article.yeeyan.org/view/311527/287706

这个我翻译的时候就发现了,还有很多其它版本。不过大家在这里翻译也是另一种分享的方式,有问题互相指正。

引用来自“鄂世嘉”的评论

引用来自“Dyllian”的评论

@红薯 此文在2012-05-18就已经翻译过了,具体网址:http://article.yeeyan.org/view/311527/287706

这个我翻译的时候就发现了,还有很多其它版本。不过大家在这里翻译也是另一种分享的方式,有问题互相指正。

也对,理解的方式不同可能翻译的不同,互相吸取
顶部