您可以根据所使用的操作数来更改 Python 中运算符的含义。 在本教程中,您将学习如何在 Python 面向对象编程中使用运算符重载。
Python 运算符重载
Python 运算符用于内置类。 但是同一运算符对于不同类型的行为会有所不同。 例如,+
运算符将对两个数字执行算术加法,合并两个列表或连接两个字符串。
Python 中的这种功能允许同一运算符根据上下文具有不同的含义,这称为运算符重载。
那么,当我们将它们与用户定义类的对象一起使用时会发生什么呢? 让我们考虑下面的类,它试图在二维坐标系中模拟一个点。
1 2 3 4 5 6 7 8 |
<span class="pl-k">class</span> <span class="pl-v">Point</span>: <span class="pl-k">def</span> <span class="pl-en">__init__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">x</span><span class="pl-c1">=</span><span class="pl-c1">0</span>, <span class="pl-s1">y</span><span class="pl-c1">=</span><span class="pl-c1">0</span>): <span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span> <span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-s1">y</span> <span class="pl-s1">p1</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">1</span>, <span class="pl-c1">2</span>) <span class="pl-s1">p2</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">2</span>, <span class="pl-c1">3</span>) <span class="pl-en">print</span>(<span class="pl-s1">p1</span><span class="pl-c1">+</span><span class="pl-s1">p2</span>) |
输出
1 2 3 4 |
<span class="pl-v">Traceback</span> (<span class="pl-s1">most</span> <span class="pl-s1">recent</span> <span class="pl-s1">call</span> <span class="pl-s1">last</span>): <span class="pl-v">File</span> <span class="pl-s">"<string>"</span>, <span class="pl-s1">line</span> <span class="pl-c1">9</span>, <span class="pl-s1">in</span> <span class="pl-c1"><</span><span class="pl-s1">module</span><span class="pl-c1">></span> <span class="pl-en">print</span>(<span class="pl-s1">p1</span><span class="pl-c1">+</span><span class="pl-s1">p2</span>) <span class="pl-v">TypeError</span>: <span class="pl-en">unsupported</span> <span class="pl-s1">operand</span> <span class="pl-s1">type</span>(<span class="pl-s1">s</span>) <span class="pl-s1">for</span> <span class="pl-c1">+</span>: <span class="pl-s">'Point'</span> <span class="pl-c1">and</span> <span class="pl-s">'Point'</span> |
在这里,我们可以看到出现了TypeError
,因为 Python 不知道如何将两个Point
对象加在一起。
但是,我们可以通过运算符重载在 Python 中完成此任务。 但是首先,让我们对特殊函数有所了解。
Python 特殊函数
以双下划线__
开头的类函数在 Python 中称为特殊函数。
这些函数不是我们为类定义的典型函数。 我们上面定义的__init__()
函数就是其中之一。 每次我们创建该类的新对象时都会调用它。
Python 中还有许多其他特殊函数。 访问 Python 特殊函数了解有关它们的更多信息。
使用特殊函数,我们可以使我们的类与内置函数兼容。
1 2 3 |
<span class="pl-c1">>></span><span class="pl-c1">></span> <span class="pl-s1">p1</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">2</span>,<span class="pl-c1">3</span>) <span class="pl-c1">>></span><span class="pl-c1">></span> <span class="pl-en">print</span>(<span class="pl-s1">p1</span>) <span class="pl-c1"><</span><span class="pl-s1">__main__</span>.<span class="pl-v">Point</span> <span class="pl-s1">object</span> <span class="pl-s1">at</span> <span class="pl-c1">0x00000000031F8CC0</span><span class="pl-c1">></span> |
假设我们希望print()
函数打印Point
对象的坐标,而不是所得到的。 我们可以在我们的类中定义__str__()
方法,该方法控制对象的打印方式。 让我们看看如何实现这一目标:
1 2 3 4 5 6 7 |
<span class="pl-k">class</span> <span class="pl-v">Point</span>: <span class="pl-k">def</span> <span class="pl-en">__init__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-c1">0</span>, <span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-c1">0</span>): <span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span> <span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-s1">y</span> <span class="pl-k">def</span> <span class="pl-en">__str__</span>(<span class="pl-s1">self</span>): <span class="pl-k">return</span> <span class="pl-s">"({0},{1})"</span>.<span class="pl-en">format</span>(<span class="pl-s1">self</span>.<span class="pl-s1">x</span>,<span class="pl-s1">self</span>.<span class="pl-s1">y</span>) |
现在,让我们再次尝试print()
函数。
1 2 3 4 5 6 7 8 9 10 |
<span class="pl-k">class</span> <span class="pl-v">Point</span>: <span class="pl-k">def</span> <span class="pl-en">__init__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">x</span><span class="pl-c1">=</span><span class="pl-c1">0</span>, <span class="pl-s1">y</span><span class="pl-c1">=</span><span class="pl-c1">0</span>): <span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span> <span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-s1">y</span> <span class="pl-k">def</span> <span class="pl-en">__str__</span>(<span class="pl-s1">self</span>): <span class="pl-k">return</span> <span class="pl-s">"({0}, {1})"</span>.<span class="pl-en">format</span>(<span class="pl-s1">self</span>.<span class="pl-s1">x</span>, <span class="pl-s1">self</span>.<span class="pl-s1">y</span>) <span class="pl-s1">p1</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">2</span>, <span class="pl-c1">3</span>) <span class="pl-en">print</span>(<span class="pl-s1">p1</span>) |
输出:
1 |
(<span class="pl-c1">2</span>, <span class="pl-c1">3</span>) |
这样更好,事实证明,当我们使用内置函数str()
或format()
时,将调用相同的方法。
1 2 3 4 5 |
<span class="pl-c1">>></span><span class="pl-c1">></span> <span class="pl-en">str</span>(<span class="pl-s1">p1</span>) <span class="pl-s">'(2,3)'</span> <span class="pl-c1">>></span><span class="pl-c1">></span> <span class="pl-en">format</span>(<span class="pl-s1">p1</span>) <span class="pl-s">'(2,3)'</span> |
因此,当您使用str(p1)
或format(p1)
时,Python 内部会调用p1.__str__()
方法。 因此得名,特殊函数。
现在让我们回到操作符重载。
重载+
运算符
要重载+
运算符,我们将需要在该类中实现__add__()
函数。 拥有权利的同时也被赋予了重大的责任。 我们可以在此函数内做任何喜欢的事情。 但是返回坐标和的Point
对象更为明智。
1 2 3 4 5 6 7 8 9 10 11 12 |
<span class="pl-k">class</span> <span class="pl-v">Point</span>: <span class="pl-k">def</span> <span class="pl-en">__init__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">x</span><span class="pl-c1">=</span><span class="pl-c1">0</span>, <span class="pl-s1">y</span><span class="pl-c1">=</span><span class="pl-c1">0</span>): <span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span> <span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-s1">y</span> <span class="pl-k">def</span> <span class="pl-en">__str__</span>(<span class="pl-s1">self</span>): <span class="pl-k">return</span> <span class="pl-s">"({0},{1})"</span>.<span class="pl-en">format</span>(<span class="pl-s1">self</span>.<span class="pl-s1">x</span>, <span class="pl-s1">self</span>.<span class="pl-s1">y</span>) <span class="pl-k">def</span> <span class="pl-en">__add__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">other</span>): <span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">+</span> <span class="pl-s1">other</span>.<span class="pl-s1">x</span> <span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">+</span> <span class="pl-s1">other</span>.<span class="pl-s1">y</span> <span class="pl-k">return</span> <span class="pl-v">Point</span>(<span class="pl-s1">x</span>, <span class="pl-s1">y</span>) |
现在让我们再次尝试加法操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<span class="pl-k">class</span> <span class="pl-v">Point</span>: <span class="pl-k">def</span> <span class="pl-en">__init__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">x</span><span class="pl-c1">=</span><span class="pl-c1">0</span>, <span class="pl-s1">y</span><span class="pl-c1">=</span><span class="pl-c1">0</span>): <span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span> <span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-s1">y</span> <span class="pl-k">def</span> <span class="pl-en">__str__</span>(<span class="pl-s1">self</span>): <span class="pl-k">return</span> <span class="pl-s">"({0},{1})"</span>.<span class="pl-en">format</span>(<span class="pl-s1">self</span>.<span class="pl-s1">x</span>, <span class="pl-s1">self</span>.<span class="pl-s1">y</span>) <span class="pl-k">def</span> <span class="pl-en">__add__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">other</span>): <span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">+</span> <span class="pl-s1">other</span>.<span class="pl-s1">x</span> <span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">+</span> <span class="pl-s1">other</span>.<span class="pl-s1">y</span> <span class="pl-k">return</span> <span class="pl-v">Point</span>(<span class="pl-s1">x</span>, <span class="pl-s1">y</span>) <span class="pl-s1">p1</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">1</span>, <span class="pl-c1">2</span>) <span class="pl-s1">p2</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">2</span>, <span class="pl-c1">3</span>) <span class="pl-en">print</span>(<span class="pl-s1">p1</span><span class="pl-c1">+</span><span class="pl-s1">p2</span>) |
输出:
1 |
(<span class="pl-c1">3</span>,<span class="pl-c1">5</span>) |
实际发生的是,当您使用p1 + p2
时,Python 会调用p1.__add__(p2)
,而后者依次为Point.__add__(p1,p2)
。 此后,将按照我们指定的方式执行加法运算。
同样,我们也可以重载其他运算符。 我们需要实现的特殊函数列表如下。
运算符 | 表达式 | 内部 |
---|---|---|
加成 | p1 + p2 |
p1.__add__(p2) |
减法 | p1 - p2 |
p1.__sub__(p2) |
乘法 | p1 * p2 |
p1.__mul__(p2) |
幂 | p1 ** p2 |
p1.__pow__(p2) |
除法 | p1 / p2 |
p1.__truediv__(p2) |
整数除法 | p1 // p2 |
p1.__floordiv__(p2) |
余数(模) | p1 % p2 |
p1.__mod__(p2) |
按位左移 | p1 << p2 |
p1.__lshift__(p2) |
按位右移 | p1 >> p2 |
p1.__rshift__(p2) |
按位与 | p1 & p2 |
p1.__and__(p2) |
按位或 | p1 | p2 |
p1.__or__(p2) |
按位异或 | p1 ^ p2 |
p1.__xor__(p2) |
按位非 | ~p1 |
p1.__invert__() |
重载比较运算符
Python 不仅将运算符重载不限于算术运算符。 我们也可以重载比较运算符。
假设我们想在我们的Point
类中实现小于符号<
符号。
让我们从原点比较这些点的大小,并为此目的返回结果。 可以如下实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<span class="pl-c"># overloading the less than operator</span> <span class="pl-k">class</span> <span class="pl-v">Point</span>: <span class="pl-k">def</span> <span class="pl-en">__init__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">x</span><span class="pl-c1">=</span><span class="pl-c1">0</span>, <span class="pl-s1">y</span><span class="pl-c1">=</span><span class="pl-c1">0</span>): <span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">=</span> <span class="pl-s1">x</span> <span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">=</span> <span class="pl-s1">y</span> <span class="pl-k">def</span> <span class="pl-en">__str__</span>(<span class="pl-s1">self</span>): <span class="pl-k">return</span> <span class="pl-s">"({0},{1})"</span>.<span class="pl-en">format</span>(<span class="pl-s1">self</span>.<span class="pl-s1">x</span>, <span class="pl-s1">self</span>.<span class="pl-s1">y</span>) <span class="pl-k">def</span> <span class="pl-en">__lt__</span>(<span class="pl-s1">self</span>, <span class="pl-s1">other</span>): <span class="pl-s1">self_mag</span> <span class="pl-c1">=</span> (<span class="pl-s1">self</span>.<span class="pl-s1">x</span> <span class="pl-c1">**</span> <span class="pl-c1">2</span>) <span class="pl-c1">+</span> (<span class="pl-s1">self</span>.<span class="pl-s1">y</span> <span class="pl-c1">**</span> <span class="pl-c1">2</span>) <span class="pl-s1">other_mag</span> <span class="pl-c1">=</span> (<span class="pl-s1">other</span>.<span class="pl-s1">x</span> <span class="pl-c1">**</span> <span class="pl-c1">2</span>) <span class="pl-c1">+</span> (<span class="pl-s1">other</span>.<span class="pl-s1">y</span> <span class="pl-c1">**</span> <span class="pl-c1">2</span>) <span class="pl-k">return</span> <span class="pl-s1">self_mag</span> <span class="pl-c1"><</span> <span class="pl-s1">other_mag</span> <span class="pl-s1">p1</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">1</span>,<span class="pl-c1">1</span>) <span class="pl-s1">p2</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">-</span><span class="pl-c1">2</span>,<span class="pl-c1">-</span><span class="pl-c1">3</span>) <span class="pl-s1">p3</span> <span class="pl-c1">=</span> <span class="pl-v">Point</span>(<span class="pl-c1">1</span>,<span class="pl-c1">-</span><span class="pl-c1">1</span>) <span class="pl-c"># use less than</span> <span class="pl-en">print</span>(<span class="pl-s1">p1</span><span class="pl-c1"><</span><span class="pl-s1">p2</span>) <span class="pl-en">print</span>(<span class="pl-s1">p2</span><span class="pl-c1"><</span><span class="pl-s1">p3</span>) <span class="pl-en">print</span>(<span class="pl-s1">p1</span><span class="pl-c1"><</span><span class="pl-s1">p3</span>) |
输出:
1 2 3 |
<span class="pl-c1">True</span> <span class="pl-c1">False</span> <span class="pl-c1">False</span> |
类似地,下面列出了我们需要实现以重载其他比较运算符的特殊函数。
运算符 | 表达式 | 内部 |
---|---|---|
小于 | p1 < p2 |
p1.__lt__(p2) |
小于或等于 | p1 <= p2 |
p1.__le__(p2) |
等于 | p1 == p2 |
p1.__eq__(p2) |
不等于 | p1 != p2 |
p1.__ne__(p2) |
大于 | p1 > p2 |
p1.__gt__(p2) |
大于或等于 | p1 >= p2 |
p1.__ge__(p2) |