6.1 魔术方法
Python 中的运算符重载允许为用户自定义类定义或改变内置运算符(比如 +, -, *, /)的行为。 这是通过一些特殊的方法实现的,这些方法也被称为魔术方法。
例如,你可以在自己的类中重载比较运算符:
| 运算符 | 无下划线方法 | 方法签名 |
|---|---|---|
| == | eq() |
__eq__(self, other) |
| != | ne() |
__ne__(self, other) |
| < | lt() |
__lt__(self, other) |
| <= | le() |
__le__(self, other) |
| > | gt() |
__gt__(self, other) |
| >= | ge() |
__ge__(self, other) |
假设你写了一个类,并希望你的类的对象能够按你所需的方式进行比较。你只需要在类中实现 «__eq__» 方法,Python 会在每次代码中比较你的类的对象时调用这个方法。
示例:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# 使用
v1 = Vector(2, 3)
v2 = Vector(2, 3)
v3 = Vector(4, 5)
print(v1 == v2) # 输出:True
print(v1 == v3) # 输出:False
每次你比较两个对象时,Python 会检查它们是否实现了 函数 «__eq__»。如果有,就调用这个方法。如果没有,就直接比较对象的引用。
事实上,上例中的意思是(只需要添加对方法存在的检查):
# 使用
v1 = Vector(2, 3)
v2 = Vector(2, 3)
v3 = Vector(4, 5)
print(v1.__eq__(v2)) # 输出:True
print(v1.__eq__(v3)) # 输出:False
6.2 所有运算符列表
总共有 6 组运算符可以重载。
算术运算符:
| 运算符 | 无下划线方法 | 方法签名 |
|---|---|---|
| + | add |
__add__(self, other) |
| - | sub |
__sub__(self, other) |
| * | mul |
__mul__(self, other) |
| / | truediv |
__truediv__(self, other) |
| // | floordiv |
__floordiv__(self, other) |
| % | mod |
__mod__(self, other) |
| ** | pow |
__pow__(self, other) |
比较运算符:
| 运算符 | 无下划线方法 | 方法签名 |
|---|---|---|
| == | eq |
__eq__(self, other) |
| != | ne |
__ne__(self, other) |
| < | lt |
__lt__(self, other) |
| <= | le |
__le__(self, other) |
| > | gt |
__gt__(self, other) |
| >= | ge |
__ge__(self, other) |
逻辑运算符:
| 运算符 | 无下划线方法 | 方法签名 |
|---|---|---|
| & | and |
__and__(self, other) |
| | | or |
__or__(self, other) |
| ^ | xor |
__xor__(self, other) |
| ~ | invert |
__invert__(self) |
索引和切片运算符:
| 运算符 | 方法 |
|---|---|
| obj[key] | __getitem__(self, key) |
| obj[key] = value | __setitem__(self, key, value) |
| del obj[key] | __delitem__(self, key) |
一元运算符:
| 运算符 | 方法 |
|---|---|
| - | __neg__(self) |
| + | __pos__(self) |
| abs() | __abs__(self) |
| ~ | __invert__(self) |
赋值运算符:
| 运算符 | 方法 |
|---|---|
| += | __iadd__(self, other) |
| -= | __isub__(self, other) |
| *= | __imul__(self, other) |
| /= | __itruediv__(self, other) |
| //= | __ifloordiv__(self, other) |
| %= | __imod__(self, other) |
| **= | __ipow__(self, other) |
可能这就是 Python 比较慢的原因——每次执行运算符前,它会在类及其所有父类中寻找相应的方法。 不过,这让我们可以写出世界上最紧凑的代码 :)
6.3 索引运算符
可以比较对象或者减去集合,在某种程度上是显而易见的。 如果你编写一个类,并且考虑到其上的逻辑或数学运算,你自己会想到这一点。
我想和你们探讨一个有趣的例子——索引运算符。让我们从代码示例开始:
class CustomList:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
def __delitem__(self, index):
del self.data[index]
def __repr__(self):
return repr(self.data)
# 使用
c_list = CustomList([1, 2, 3, 4, 5])
print(c_list[1]) # 输出:2
c_list[1] = 10
print(c_list) # 输出:[1, 10, 3, 4, 5]
del c_list[1]
print(c_list) # 输出:[1, 3, 4, 5]
这里我们看到三个操作的示例:
通过索引读取数据通过索引写入数据甚至通过索引删除数据。
数据也不一定必须是以列表形式存储的。或者索引也不一定必须是数字。例如,dictionary(字典)类就是这样实现的。
创建 SuperList
还记得 list 类吗?它可以分配元素,但只能使用已经存在的索引。让我们创建一个自己的类,叫做 SuperList,你可以用任意索引来访问它的元素:
如果索引 < 0,将元素插入到开头如果索引 >= len,添加元素到末尾其他情况下只返回元素
示例:
class SuperList(list):
def __init__(self, value):
super().__init__(value)
def __setitem__(self, index, value):
if index >= len(self):
super().append(value)
elif index < 0:
super().insert(0, value)
else:
super().__setitem__(index, value)
lst = SuperList([1, 2, 3])
lst[200] = 100
lst[-200] = 99
print(lst) # [99, 1, 2, 3, 100]
所以索引重载是一个很棒的功能,我推荐你在实践中使用它。今天就先这样吧。
GO TO FULL VERSION