全面学习Python魔法方法(magic methods) I
目录
其他见此:
全面学习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')
,会将10
和foo
作为参数传递给__init__
)。__init__
普遍用于Python类定义。 -
__del__(self)
如果说
__new__
和__init__
构成了对象的构造函数,__del__
就是析构函数。它没有实现语句del x
(因此代码不会转换为x.__del__()
);相反,它定义了对象何时被垃圾回收。对于那些可能需要在删除时,进行额外清理的对象(如套接字或文件对象)是非常有用的。但要小心,如果对象在解释器退出时仍处于活动状态时,则无法保证__del__
将被执行,所以在完成操作时,关闭连接close()
。在python中,对于开发者来说很少会直接销毁对象(如果需要,应该使用
del
关键字销毁)。Python的内存管理机制能够很好的胜任这份工作。也就是说,不管是手动调用del
还是由python自动回收都会触发__del__
方法执行。
1 | from os. path import join |
Making Operators Work on Custom Classes
使用Python魔法方法的最大优势之一,就是它们提供了一种简单的方法,使得对象可以像内置类型一样进行操作。
在一些语言中,通常会执行以下操作:
1 | if instance.equals(other_instance): |
你当然也可以选择在 Python 中这样做,但这会增加混乱并且冗长。如果不同的库对相同的操作使用不同的名称,这会使得工作量大大增加。
但是,借助魔法方法的强大功能,我们可以定义一个方法(在本例中为__eq__
),并以这样的代码来说明我们的想法:
1 | if instance == other_instance: |
这就是魔法方法的力量的一部分。其中绝大多数允许我们,重新为运算符定义含义,以便于可以在我们自己的类上使用运算符,就像这些类是内建类型。
Comparison magic methods 比较魔法方法
Python有一大堆魔法方法,旨在使用运算符实现对象之间的直观比较。同时还提供了一种重载 Python 默认行为以进行对象比较的方法(通过引用)。下面列出了这些方法及其作用:
-
__eq__(self, other)
定义相等运算符的行为,==
-
__ne__(self, other)
定义不等式运算符的行为,!=
-
__lt__(self, other)
定义小于运算符的行为, <
-
__gt__(self, other)
定义大于运算符的行为, >
-
__le__(self, other)
定义小于等于运算符的行为, <=
-
__ge__(self, other)
定义大于等于运算符的行为, >=
例如,考虑一个类来建模一个单词。我们可能希望按字典顺序(按字母表)比较单词,这是字符串的默认比较行为,但我们也可能希望根据其他一些标准(如长度或音节数)来进行。
在这个例子中,我们将按长度进行比较:
1 | class Word (str ): |
现在,我们可以创建两个单词(使用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()
时的行为。 应返回True
或False
, 具体取决于您是否要将实例视为True
或False
__dir__(self)
定义在类的实例上调用dir()
的行为。 此方法应返回用户的属性列表。通常, 实现__dir__
是不必要的, 但如果重新定义__getattr__
或__getattribute__
或以其他方式动态生成属性, 则对于类的交互式使用非常重要
我们已经完成了魔法方法指南中的无聊(并且没有示例…)部分。现在已经介绍了一些更基本的魔法方法,现在是时候转向更高级的方法了