每个人都知道__init__
方法的作用。 它是一个内置方法,每次创建新对象时都会调用它。 然而,这并不是 Python 为我们创建的唯一特殊方法。 还有更多对于创建更强大的类非常有用的特殊方法。
在整篇文章中,我将使用一个类 Point
作为示例,它表示一个 二维 点。 这是__init__
方法:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
1、类的描述
当我们想要表示一个类时,有两个特殊的方法会被调用:__repr__(self)
和 __str__(self)
。
它们不同的用法:
__repr__
理想情况下应该返回一个对象,该对象可用于重新创建类的当前状态。 返回的对象不必是字符串。 但是,如果它是一个字符串,则它的格式应该为"<..description..>"
。- 另一方面,
__str__
必须始终返回一个字符串。 每当我们将对象转换为字符串时(例如,当我们使用print
时),就会调用这个方法。
默认情况下,__str__
方法定义为:
def __str__(self):
return self.__repr__(self):
所以我们只能重写 __repr__
,只要它返回一个字符串。 例如,我们的 Point 类:
def __repr__(self):
return "<PointObject:x=" + str(self.x) + ",y=" + str(self.y) + ">"
但是如果我们想在调用 __repr__
时返回一个不同的类型(比如元组)怎么办? 然后我们需要重写这两个方法:
def __repr__(self):
return (self.x, self.y)def __str__(self):
return "Point(" + str(self.x) + ", "+ str(self.y) + ")"
2、检查是否相等
你有没有想过用 ==
来检查两个实例是否相等?
每次写 a == b
时,内部调用的方法是 a.__eq__(b)
。 所以为了能够比较自定义类的对象,我们只需要重写这个方法。 让我们看看怎么做:
def __eq__(self, other):
if not isinstance(other, Point):
return False
return self.x == other.x and self.y == other.y
第一行用于检查另一个对象是否也是类 Point
的实例。 如果不是,该方法将始终返回 False。 否则,该方法将检查两个点的坐标是否相同。
还有另一种方法, __ne__
,每当我们使用 !=
时都会调用它。 但是,我们不需要实现它,因为默认情况下它使用 __eq__
的定义来返回正确的结果。
3、比较不同的对象
现在我们可以检查两个对象是否相等,也许我们更想要对它们进行排序。 但要做到这一点,我们必须能够使用运算符 < 、 <= 、 > 、 >= 。 让我们看看它们在幕后是如何工作的。
与相等运算符一样,每当我们比较两个对象时,都会调用一些内置函数。 它们是:__lt__
、__le__
、__gt__
、__ge__
。 所以我们需要重写这四个方法。 下面是我们如何为我们的 Point
类实现 总排序:
def __gt__(self, other):
if not isinstance(other, Point):
raise TypeError
return self.x > other.x or (self.x == other.x and self.y > self.y)
def __lt__(self, other):
if not isinstance(other, Point):
raise TypeError
return self.x < other.x or (self.x == other.x and self.y < self.y)
def __ge__(self, other):
# Defined recursively
return self > other or self == other
def __le__(self, other):
# Defined recursively
return self < other or self == other
请注意:
- 您可以定义其中之一,然后以递归方式指定其他,但这会使代码变慢。
- 你必须已经定义了
__eq__
,否则这些方法将不起作用。
4、添加和减去对象
现在我们可能想对我们的对象进行一些算术运算。 这些也可以使用五种特殊方法来实现:
- 当我们使用
a+b
时会调用__add__(self, other)
- 当我们使用
a-b
时会调用__sub__(self, other)
- 当我们使用
a*b
时会调用__mul__(self, other)
- 当我们使用
a/b
时会调用__truediv__(self, other)
- 当我们使用
a
b
a^b
ab 时会调用
__pow__(self, other)
让我们看看如何加减平面中的两个点:
def __add__(self, other):
if not isinstance(other, Point):
raise TypeError
return Point(self.x+other.x, self.y+other.y)def __sub__(self, other):
if not isinstance(other, Point):
raise TypeError
return Point(self.x-other.x, self.y-other.y)
请记住始终检查其他对象是否具有正确的类型。 此外,这些方法应该更改任何现有对象:它们必须返回正确类型的新对象。
5、使类可散列
假设您想将您的类用作字典中的键。 如果你这样尝试,你会得到这样的错误:
TypeError: unhashable type: 'Point'
事实上,如果一个对象 t 是可散列的,则对象是可以被用作字段中的键的。 但是如何使对象可散列呢? 您需要实现 __hash__(self)
方法。
最简单的方法是创建一个包含定义对象的所有字段的元组,然后返回其哈希值。 请注意,用于散列的属性应该是常量。 因此,我们还需要重新定义 __init__
方法以将 x
和 y
设为私有。 此外,定义__hash__
的类也必须定义__eq__
。
class Point:
def __init__(self, x, y):
self.__x = x
self.__y = y
@property
def x(self):
return self.__x
@property
def y(self):
return self.__y
def __eq__(self, other):
if not isinstance(other, Point):
return False
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
6、对象的长度
我们知道如何使用 len(x)
方法来获取列表的长度。 但是如果我们想用它来获取自定义对象的长度呢? 在这种情况下,我们需要重写 __len__(self)
方法。 唯一的限制是它必须是整数。
例如,我们希望将“Point”对象的长度定义为距原点的曼哈顿距离。 所以我们可以实现这个方法:
def __len__(self):
return abs(self.x)+abs(self.y)
现在我们可以使用 len
方法来获取曼哈顿距离:
>>> len( Point(1, -3) )
4
7、转换为布尔值
有时我们想将我们的类用作布尔值。 例如,如果一个点不是原点,或者它在某个区域之外,则它可能是“真”。 为此,我们将对象转换为 bool
,我们使用 bool(Point(1, 2))
。 这个方法在内部调用了 __bool__(self)
方法。 所以我们需要重写这个方法来创建一个自定义行为:
def __bool__(self):
# 如果该点是原点,则返回 False
return self.x != 0 or self.y != 0
注意:
,如果定义了 __len__
而未定义 __bool__
,则布尔值实现为 len(self) > 0
。