其他见此:

全面学习Python魔法方法(magic methods) I

全面学习Python魔法方法(magic methods) II

全面学习Python魔法方法(magic methods) III

Introduction

什么是魔法方法?

它们是面向对象 Python 中的一切。

它们是您可以定义的特殊方法,可以为你的类添加“magic”。

它们总是被双下划线包围(例如__init____lt__)。

但是在它们却散落在 Python 的文档中,这是一个关于 Python 魔法方法的总结。

Construction and Initialization

每个人都知道最基本的 magic method,__init__,这是我们定义对象初始化行为的方式。但是,当调用x = SomeClass()时,__init__不是第一个被调用。

实际上,一个被调用的为__new__方法,它实际上创建了实例,然后将创建时的所有参数传递给初始化器。而在对象的生命周期的另一端,则有__del__方法。

  • __new__(cls, [...)

    __new__是在对象的实例化中调用的第一个方法。它接受类,然后将所有参数传递给__init__。实际上,__new__很少使用,但它确实有其用途,特别是在子类化(subclassing)元组或字符串这样的不可变类型时。Python文档中有很详细的介绍。

  • __init__(self, [...)

    类的初始化器。只要构造函数被调用,则参数就会被传递给它(例如,我们调用x = SomeClass(10,'foo'),会将10foo作为参数传递给__init__)。__init__普遍用于Python类定义。

  • __del__(self)

    如果说__new____init__构成了对象的构造函数,__del__就是析构函数。它没有实现语句del x(因此代码不会转换为x.__del__());相反,它定义了对象何时被垃圾回收。对于那些可能需要在删除时,进行额外清理的对象(如套接字或文件对象)是非常有用的。但要小心,如果对象在解释器退出时仍处于活动状态时,则无法保证__del__将被执行,所以在完成操作时,关闭连接close()

    在python中,对于开发者来说很少会直接销毁对象(如果需要,应该使用del关键字销毁)。Python的内存管理机制能够很好的胜任这份工作。也就是说,不管是手动调用del还是由python自动回收都会触发__del__方法执行。

1
2
3
4
5
6
7
8
9
10
11
12
from os. path import join

class 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.fil

Making Operators Work on Custom Classes

使用Python魔法方法的最大优势之一,就是它们提供了一种简单的方法,使得对象可以像内置类型一样进行操作。

在一些语言中,通常会执行以下操作:

1
2
if instance.equals(other_instance):
# do something

你当然也可以选择在 Python 中这样做,但这会增加混乱并且冗长。如果不同的库对相同的操作使用不同的名称,这会使得工作量大大增加。

但是,借助魔法方法的强大功能,我们可以定义一个方法(在本例中为__eq__),并以这样的代码来说明我们的想法:

1
2
if instance == other_instance:
# do something

这就是魔法方法的力量的一部分。其中绝大多数允许我们,重新为运算符定义含义,以便于可以在我们自己的类上使用运算符,就像这些类是内建类型。

Comparison magic methods 比较魔法方法

Python有一大堆魔法方法,旨在使用运算符实现对象之间的直观比较。同时还提供了一种重载 Python 默认行为以进行对象比较的方法(通过引用)。下面列出了这些方法及其作用:

  • __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)。基于长度测试相等性是没有意义的,所以我们使用str的一般实现。

实际上,不必定义每个比较魔法方法来进行比较。标准库在模块functools中为我们提供了一个类装饰器,如果你只定义__eq__和另一个(例如__gt____ lt__等),它将定义所有全面的比较方法。这个特性在Python 2.7+可用,通过将 @total_ordering 放在类定义之上来使用它。

Numeric Magic Methods 数值魔法方法

就像可以创建类的实例,然后通过比较运算符进行比较的方法一样,可以为数值运算符定义行为。

可以将数值魔法方法分为5类:一元运算符,普通的算术运算符,反射算术运算符(稍后将详细介绍),扩充赋值和类型转换。

Unary operators and functions 一元运算符

  • __pos__(self) 实现一元取正运算符行为(例如,+some_object
  • __neg__(self)实现一元取负运算符行为(例如,-some_object
  • __abs__(self) 实现内置函数abs()的行为
  • __invert__(self) 使用~运算符实现取反行为
  • __round__(self, n) 实现内置函数round()的行为,n是要舍入的小数位数
  • __floor__(self) 实现math.floor()的行为,即向下取整
  • __ceil__(self) 实现math.ceil()的行为,即向上取整
  • __trunc__(self) 实现math.trunc()的行为,即截断为整数

Normal arithmetic operators 普通算术运算符

可以定义一些典型的二元运算符:+, - , * 等。 在大多数情况下,是自解释性的。

魔法方法 Magic Methods 重载的运算符
__add__(self, other) +
__sub__(self, other) -
__mul__(self, other) *
__floordiv__(self, other) //
__truediv__(self, other) 实现_true_ division
__mod__(self, other) %
__divmod__(self, other) 使用内置函数divmod()
__pow__ **
__lshift__(self, other) <<
__rshift__(self, other) >>
__and__(self, other) &
__or__(self, other) |
__xor__(self, other) ^

Reflected arithmetic operators 反射算术运算符

这是一个例子:

some_object + other

这是“常规”情况的一种补充。反射是与此其等价的,除了调换了操作数:

other + some_object

在大多数情况下,反射操作的结果与其正常的等效操作相同,因此您可能最终将__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)实现反映了真正的除法
  • __rmod__(self, other)使用运算符实现反射的取模
  • __rdivmod__(self, other)当调用divmod(other, self)时使用内置函数的divmod()实现长除法的行为
  • __rpow__使用**运算符实现反射指数的行为
  • __rlshift__(self, other)使用<<运算符实现反向左按位移位
  • __rrshift__(self, other)使用>>运算符实现反向右移位
  • __rand__(self, other)使用运算符实现按位反射and
  • __ror__(self, other)使用|运算符实现按位反射or
  • __rxor__(self, other)使用^运算符实现反射按位xor

Augmented assignment

Python还有各种各样的魔法方法,允许为 Augmented assignment 自定义行为,它将“常规”运算符与赋值结合起来。

例如,对于a += b__iadd__返回a + b,它将被分配给a

  • __iadd__(self, other)使用赋值实现添加
  • __isub__(self, other)使用赋值实现减法
  • __imul__(self, other)使用赋值实现乘法
  • __ifloordiv__(self, other)使用//=运算符实现赋值的整数除法
  • __idiv__(self, other)使用/=运算符实现赋值除法
  • __itruediv__(self, other)通过赋值实现真正的除法
  • __imod__(self, other)使用%=运算符实现带模式的模数
  • __ipow__使用**=运算符实现赋值的指数行为
  • __ilshift__(self, other)使用<<=运算符实现左移位
  • __irshift__(self, other)使用>>=运算符实现右移位
  • __iand__(self, other)使用&=运算符按位实现and赋值
  • __ior__(self, other)使用|=运算符实现按位or赋值
  • __ixor__(self, other)使用^=运算符实现按位xor赋值

Type conversion magic methods

Python还有一系列魔法方法,用于实现内置类型转换函数(如float())的行为。

  • __int__(self)实现类型转换为int
  • __long__(self)实现类型转换为long
  • __float__(self)实现类型转换为float
  • __complex__(self)实现类型转换为complex
  • __oct__(self)实现类型转换为八进制。
  • __hex__(self)实现类型转换为十六进制。
  • __index__(self)在切片表达式中使用对象时,实现类型转换为int。 如果定义可能在切片中使用的自定义数字类型,则应定义__index__
  • __trunc__(self)调用math.trunc(self)时调用。__trunc__应该将self截断的值返回到整数类型(通常为long)。

Representing your Classes

通常使用字符串表示一个类是非常有用的。在Python中,可以在类定义中实现一些方法,以自定义返回类的表示形式。

  • __str__(self)定义在类的实例上调用str()时的行为
  • __repr__(self)定义在类的实例上调用repr()时的行为。 str()repr()之间的主要区别在于受众。 repr()旨在生成大多数机器可读的输出(在许多情况下, 它甚至可能是有效的Python代码), 而str()旨在是人类可读的
  • __format__(self, formatstr)定义在新样式字符串格式中使用类实例的行为。 例如, "Hello, 0:abc!".format(a)将导致调用a.__format__("abc")。这对于定义那些,可能希望提供特殊格式选项的数字或字符串类型时非常有用
  • __hash__(self)定义在类的实例上调用hash()的行为。 它必须返回一个整数, 其结果用于字典中的快速键比较。请注意, 这通常也需要实现__eq__。 按以下规则生活:a == b表示`hash(a)== hash(b)``
  • __bool__(self)定义在类的实例上调用bool()时的行为。 应返回TrueFalse, 具体取决于您是否要将实例视为TrueFalse
  • __dir__(self)定义在类的实例上调用dir()的行为。 此方法应返回用户的属性列表。通常, 实现__dir__是不必要的, 但如果重新定义__getattr____getattribute__或以其他方式动态生成属性, 则对于类的交互式使用非常重要

我们已经完成了魔法方法指南中的无聊(并且没有示例…)部分。现在已经介绍了一些更基本的魔法方法,现在是时候转向更高级的方法了