译者 | 布加迪
审校 | 重楼
在Python中,魔术方法(Magic Method)可以帮助您模拟Python类中内置函数的行为。这些方法有前后双下划线(__),因此也被称为Dunder方法。
这些魔术方法还可以帮助您在Python中实现操作符重载。您可能见过这样的例子,就像两个整数与乘法运算符*一起使用得到乘积一样。当它与字符串和整数k一起使用时,字符串会重复k次:
>>> 3 * 4
12
>>> 'code' * 3
'codecodecode'
我们在本文中将通过创建一个简单的二维向量Vector2D类来探索Python中的魔术方法。
我们将从您可能熟悉的方法入手,逐步构建更有帮助的魔术方法。
不妨开始编写一些魔术方法!
1. __init__
考虑下面的Vector2D类:
class Vector2D:
pass
一旦您创建了类,并实例化对象,就可以添加如下属性:obj_name.attribute_name = value。
然而,您需要在实例化对象时初始化这些属性,而不是手动向创建的每个实例添加属性(当然,这一点也不有趣!)。
为此,您可以定义__init__方法。不妨为Vector2D类定义__init__方法:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
v = Vector2D(3, 5)
2. __repr__
当您尝试检查或打印输出实例化的对象时,您将发现没有得到任何有帮助的信息。
v = Vector2D(3, 5)
print(v)
Output >>> <__main__.Vector2D object at 0x7d2fcfaf0ac0>
这就是为什么您应该添加一个表示字符串,一个对象的字符串表示。为此,添加__repr__方法,如下所示:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
v = Vector2D(3, 5)
print(v)
Output >>> Vector2D(x=3, y=5)
__repr__应该包含创建类实例所需的所有属性和信息。__repr__方法通常用于调试目的。
3. __str__
__str__也用于添加对象的字符串表示。通常,__str__方法用于为类的最终用户提供信息。
不妨给我们的类添加一个__str__方法:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector2D(x={self.x}, y={self.y})"
v = Vector2D(3, 5)
print(v)
Output >>> Vector2D(x=3, y=5)
如果没有__str__的实现,它就返回到__repr__。因此对于您创建的每个类,您至少应该添加__repr__方法。
4. __eq__
接下来,不妨添加一个方法来检查Vector2D类的任意两个对象是否相等。如果两个向量有相同的x和y坐标,它们是相等的。
现在创建两个具有相等x和y值的Vector2D对象,并比较它们是否相等:
v1 = Vector2D(3, 5)
v2 = Vector2D(3, 5)
print(v1 == v2)
结果为False,因为默认情况下比较会检查内存中对象ID是否相等。
Output >>> False
不妨添加__eq__方法来检查是否相等:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __eq__(self, other):
return self.x == other.x and self.y == other.y
检查相等性现在应该按预期工作:
v1 = Vector2D(3, 5)
v2 = Vector2D(3, 5)
print(v1 == v2)
Output >>> True
5. __len__
Python的内置len()函数可以帮助您计算内置可迭代对象(iterable)的长度。比如说,就向量而言,length应该返回该向量所包含的元素的个数。
所以不妨为Vector2D类添加一个__len__方法:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __len__(self):
return 2
v = Vector2D(3, 5)
print(len(v))
Vector2D类的所有对象长度为2:
Output >>> 2
6. __add__
现在不妨考虑对向量执行的常见运算。不妨添加魔术方法来加减任意两个向量。
如果您直接尝试添加两个向量对象,就会遇到错误。所以您应该添加一个__add__方法:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
您现在可以像这样添加任意两个向量:
v1 = Vector2D(3, 5)
v2 = Vector2D(1, 2)
result = v1 + v2
print(result)
Output >>> Vector2D(x=4, y=7)
7. __sub__
接下来,不妨添加一个__sub__方法来计算Vector2D类的任意两个对象之间的差异:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __sub__(self, other):
return Vector2D(self.x - other.x, self.y - other.y)
v1 = Vector2D(3, 5)
v2 = Vector2D(1, 2)
result = v1 - v2
print(result)
Output >>> Vector2D(x=2, y=3)
8. __mul__
我们还可以定义__mul__方法来定义对象之间的乘法。
不妨来处理:
- 标量乘法:向量与标量的乘法
- 内积:两个向量的点积
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __mul__(self, other):
# Scalar multiplication
if isinstance(other, (int, float)):
return Vector2D(self.x * other, self.y * other)
# Dot product
elif isinstance(other, Vector2D):
return self.x * other.x + self.y * other.y
else:
raise TypeError("Unsupported operand type for *")
现在我们将举几个例子,看看__mul__方法是如何实际工作的。
v1 = Vector2D(3, 5)
v2 = Vector2D(1, 2)
# Scalar multiplication
result1 = v1 * 2
print(result1)
# Dot product
result2 = v1 * v2
print(result2)
Output >>>
Vector2D(x=6, y=10)
13
9. __getitem__
__getitem__魔术方法让您可以索引对象,并使用熟悉的方括号[]语法访问属性或属性切片。
对于Vector2D类的对象v:
- v [0]:x坐标
- v [1]:y坐标
如果您尝试通过索引访问,您会遇到错误:
v = Vector2D(3, 5)
print(v[0],v[1])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
----> 1 print(v[0],v[1])
TypeError: 'Vector2D' object is not subscriptable
不妨实现__getitem__ 方法:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __getitem__(self, key):
if key == 0:
return self.x
elif key == 1:
return self.y
else:
raise IndexError("Index out of range")
现在您可以使用索引访问这些元素,如下所示:
v = Vector2D(3, 5)
print(v[0])
print(v[1])
Output >>>
3
5
10. __call__
借助__call__方法的实现,您可以像调用函数一样调用对象。
在Vector2D类中,我们可以实现__call__,按给定因子缩放向量:
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __call__(self, scalar):
return Vector2D(self.x * scalar, self.y * scalar)
如果您现在调用3,会得到缩放3倍的向量:
v = Vector2D(3, 5)
result = v(3)
print(result)
Output >>> Vector2D(x=9, y=15)
11. __getattr__
__getattr__方法用于获取对象的特定属性的值。
就这个例子而言,我们可以添加一个__getattr__ dunder方法,一旦被调用可计算向量的量值(L2-norm):
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector2D(x={self.x}, y={self.y})"
def __getattr__(self, name):
if name == "magnitude":
return (self.x ** 2 + self.y ** 2) ** 0.5
else:
raise AttributeError(f"'Vector2D' object has no attribute '{name}'")
不妨验证这是否像预期的那样工作:
v = Vector2D(3, 4)
print(v.magnitude)
Output >>> 5.0
结论
这就是本教程的全部内容!希望您已经学会了如何为您的类添加魔术方法,以模拟内置函数的行为。
我们已介绍了一些最有用的魔术方法,但这并非详尽的清单。为了进一步理解,您可以创建一个所选择的Python类,根据所需的功能添加魔术方法。最后祝编程愉快!
原文标题:Harness the Power of AI for Business,作者:Bala Priya C