python类重载运算符_自定义Python类中的运算符和函数重载

python类重载运算符

If you’ve used the + or * operator on a str object in Python, you must have noticed its different behavior when compared to int or float objects:

如果您在Python的str对象上使用+*运算符,那么与intfloat对象相比,您必须注意到它的不同行为:

 >>> >>>  1 1 + + 2  2  # Adds the two numbers
# Adds the two numbers
3
3
>>> >>>  'Real' 'Real' + + 'Python'  'Python'  # Concatenates the two strings
# Concatenates the two strings
'RealPython'
'RealPython'
>>> >>>  3 3 * * 2  2  # Gives the product
# Gives the product
6
6
>>> >>>  'Python' 'Python' * * 3  3  # Repeats the string
# Repeats the string
'PythonPythonPython'
'PythonPythonPython'

You might have wondered how the same built-in operator or function shows different behavior for objects of different classes. This is called operator overloading or function overloading respectively. This article will help you understand this mechanism, so that you can do the same in your own Python classes and make your objects more Pythonic.

您可能想知道相同的内置运算符或函数对于不同类的对象如何显示不同的行为。 这分别称为运算符重载或函数重载。 本文将帮助您了解这种机制,以便您可以在自己的Python类中执行相同的操作,并使对象更具Pythonic性。

You’ll learn the following:

您将学到以下内容:

  • The API that handles operators and built-ins in Python
  • The “secret” behind len() and other built-ins
  • How to make your classes capable of using operators
  • How to make your classes compatible with Python’s built-in functions
  • 处理Python中的运算符和内建函数的API
  • len()和其他内置len()背后的“秘密”
  • 如何使您的班级能够使用运算符
  • 如何使您的类与Python的内置函数兼容

Free Bonus: Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.

免费奖金: 单击此处可获取免费的Python OOP作弊表 ,该指南将为您提供最佳的教程,视频和书籍,以了解有关使用Python进行面向对象编程的更多信息。

As a bonus, you’ll also see an example class, objects of which will be compatible with many of these operators and functions. Let’s get started!

另外,您还将看到一个示例类,该类的对象将与许多这些运算符和函数兼容。 让我们开始吧!

Python数据模型 (The Python Data Model)

Say you have a class representing an online order having a cart ( a list) and a customer (a str or instance of another class which represents a customer).

假设您有一个代表在线订单的类,该类具有购物车( list )和一个客户(代表客户的另一个类的str或实例)。

Note: If you need a refresher on OOP in Python, check out this tutorial on Real Python: Object-Oriented Programming (OOP) in Python 3

注意:如果需要Python上的OOP复习课程,请查看有关Real Python的本教程: Python 3中的面向对象编程(OOP)

In such a case, it is quite natural to want to obtain the length of the cart list. Someone new to Python might decide to implement a method called get_cart_len() in their class to do this. But you can configure the built-in len() in such a way that it returns the length of the cart list when given our object.

在这种情况下,很自然要获得购物车列表的长度。 Python的新手可能会决定在其类中实现一个名为get_cart_len()的方法来执行此操作。 但是您可以配置内置的len() ,以便在给定我们的对象时返回购物车列表的长度。

In another case, we might want to append something to the cart. Again, someone new to Python would think of implementing a method called append_to_cart() that takes an item and appends it to the cart list. But you can configure the + operator in such a way that it appends a new item to the cart.

在另一种情况下,我们可能希望将某些东西添加到购物车中。 再说一次,Python的新手会想到实现一个名为append_to_cart()的方法,该方法接受一个项目并将其追加到购物车列表中。 但是,您可以配置+运算符,以便将新项目追加到购物车。

Python does all this using special methods. These special methods have a naming convention, where the name starts with two underscores, followed by an identifier and ends with another pair of underscores.

Python使用特殊的方法来完成所有这些工作。 这些特殊方法具有命名约定,其中名称以两个下划线开头,后跟一个标识符,并以另一对下划线结尾。

Essentially, each built-in function or operator has a special method corresponding to it. For example, there’s __len__(), corresponding to len(), and __add__(), corresponding to the + operator.

本质上,每个内置函数或运算符都有一个与之相对应的特殊方法。 例如,还有的__len__(),对应于len()__add__()对应于+运算符。

By default, most of the built-ins and operators will not work with objects of your classes. You must add the corresponding special methods in your class definition to make your object compatible with built-ins and operators.

默认情况下,大多数内置函数和运算符将不适用于您的类的对象。 您必须在类定义中添加相应的特殊方法,以使您的对象与内置函数和运算符兼容。

When you do this, the behavior of the function or operator associated with it changes according to that defined in the method.

执行此操作时,函数或与其关联的运算符的行为会根据方法中定义的行为而变化。

This is exactly what the Data Model (Section 3 of the Python documentation) helps you accomplish. It lists all the special methods available and provides you with the means of overloading built-in functions and operators so that you can use them on your own objects.

这正是数据模型 (Python文档的第3节)可以帮助您完成的。 它列出了所有可用的特殊方法,并为您提供了重载内置函数和运算符的方法,以便您可以在自己的对象上使用它们。

Let’s see what this means.

让我们看看这意味着什么。

Fun fact: Due to the naming convention used in these methods, they are also called dunder methods, short for “dunder something dunder,” which is the shorter version of double underscore something double underscore.

有趣的事实:由于在这些方法中使用的命名约定,它们也被称为dunder方法 ,简称“dunder东西dunder”,这是根据分数d ouble 得分东西d ouble的短版。

len()[]的运算内部 (The Internals of Operations Like len() and [])

Every class in Python defines its own behavior for built-in functions and methods. When you pass an instance of some class to a built-in function or use an operator on the instance, it is actually equivalent to calling a special method with relevant arguments.

Python中的每个类都为内置函数和方法定义了自己的行为。 当您将某个类的实例传递给内置函数或对该实例使用运算符时,实际上等效于调用带有相关参数的特殊方法。

If there is a built-in function, func(), and the corresponding special method for the function is __func__(), Python interprets a call to the function as obj.__func__(), where obj is the object. In the case of operators, if you have an operator opr and the corresponding special method for it is __opr__(), Python interprets something like obj1 <opr> obj2 as obj1.__opr__(obj2).

如果有内置函数func() ,并且该func()的对应特殊方法是__func__() ,Python会将对该函数的调用解释为obj.__func__() ,其中obj是对象。 对于运算符,如果您有运算符opr ,并且对应的特殊方法是__opr__() ,Python会将obj1 <opr> obj2类的东西解释为obj1.__opr__(obj2)

So, when you’re calling len() on an object, Python handles the call as obj.__len__(). When you use the [] operator on an iterable to obtain the value at an index, Python handles it as itr.__getitem__(index), where itr is the iterable object and index is the index you want to obtain.

因此,当您在对象上调用len()时,Python会将调用作为obj.__len__() 。 当在可迭代对象上使用[]运算符以获取索引处的值时,Python会将其作为itr.__getitem__(index) ,其中itr是可迭代对象,而index是您要获取的索引。

Therefore, when you define these special methods in your own class, you override the behavior of the function or operator associated with them because, behind the scenes, Python is calling your method. Let’s get a better understanding of this:

因此,当您在自己的类中定义这些特殊方法时,您将覆盖与其关联的函数或运算符的行为,因为Python在后台调用了您的方法。 让我们更好地理解这一点:

As you can see, when you use the function or its corresponding special method, you get the same result. In fact, when you obtain the list of attributes and methods of a str object using dir(), you’ll see these special methods in the list in addition to the usual methods available on str objects:

如您所见,当您使用该函数或其相应的特殊方法时,将获得相同的结果。 实际上,当您使用dir()获得str对象的属性和方法的列表时,除了在str对象上可用的常用方法之外,您还将在列表中看到以下特殊方法:

 >>> >>>  dirdir (( aa )
)
['__add__',
['__add__',
 '__class__',
 '__class__',
 '__contains__',
 '__contains__',
 '__delattr__',
 '__delattr__',
 '__dir__',
 '__dir__',
 ...,
 ...,
 '__iter__',
 '__iter__',
 '__le__',
 '__le__',
 '__len__',
 '__len__',
 '__lt__',
 '__lt__',
 ...,
 ...,
 'swapcase',
 'swapcase',
 'title',
 'title',
 'translate',
 'translate',
 'upper',
 'upper',
 'zfill']
 'zfill']

If the behavior of a built-in function or operator is not defined in the class by the special method, then you will get a TypeError.

如果通过特殊方法未在类中定义内置函数或运算符的行为,则将收到TypeError

So, how can you use special methods in your classes?

那么,如何在类中使用特殊方法呢?

重载内置功能 (Overloading Built-in Functions)

Many of the special methods defined in the Data Model can be used to change the behavior of functions such as len, abs, hash, divmod, and so on. To do this, you only need to define the corresponding special method in your class. Let’s look at a few examples:

数据模型中定义的许多特殊方法可用于更改lenabshashdivmod等函数的行为。 为此,您只需要在类中定义相应的特殊方法。 让我们看几个例子:

使用len()赋予对象长度 (Giving a Length to Your Objects Using len())

To change the behavior of len(), you need to define the __len__() special method in your class. Whenever you pass an object of your class to len(), your custom definition of __len__() will be used to obtain the result. Let’s implement len() for the order class we talked about in the beginning:

要更改len()的行为,您需要在类中定义__len__()特殊方法。 每当将类的对象传递给len() ,将使用您自定义的__len__()定义来获取结果。 让我们为开始讨论的订单类实现len()

As you can see, you can now use len() to directly obtain the length of the cart. Moreover, it makes more intuitive sense to say “length of order” rather than calling something like order.get_cart_len(). Your call is both Pythonic and more intuitive. When you don’t have the __len__() method defined but still call len() on your object, you get a TypeError:

如您所见,您现在可以使用len()直接获取购物车的长度。 而且,说“定单长度”比调用order.get_cart_len()这样的东西更直观。 您的通话既Python式又直观。 当您没有定义__len__()方法但仍然在对象上调用len()时,您将得到TypeError

 >>> >>>  class class OrderOrder :
:
...     ...     def def __init____init__ (( selfself , , cartcart , , customercustomer ):
):
...         ...         selfself .. cart cart = = listlist (( cartcart )
)
...         ...         selfself .. customer customer = = customer
customer
...
...
>>> >>>  order order = = OrderOrder ([([ 'banana''banana' , , 'apple''apple' , , 'mango''mango' ], ], 'Real Python''Real Python' )
)
>>> >>>  lenlen (( orderorder )  )  # Calling len when no __len__
# Calling len when no __len__
Traceback (most recent call last):
  File Traceback (most recent call last):
  File "<stdin>", line "<stdin>" , line 1, in 1 , in <module>
<module>
TypeError: TypeError : object of type 'Order' has no len()
object of type 'Order' has no len()

But, when overloading len(), you should keep in mind that Python requires the function to return an integer. If your method were to return anything other than an integer, you would get a TypeError. This, most probably, is to keep it consistent with the fact that len() is generally used to obtain the length of a sequence, which can only be an integer:

但是,当重载len() ,应记住Python需要该函数返回整数。 如果您的方法返回的不是整数,则将收到TypeError 。 这很可能是为了使其与通常使用len()获取序列长度的事实保持一致,该序列只能是整数:

使对象与abs() (Making Your Objects Work With abs())

You can dictate the behavior of the abs() built-in for instances of your class by defining the __abs__() special method in the class. There are no restrictions on the return value of abs(), and you get a TypeError when the special method is absent in your class definition.

您可以通过在类中定义__abs__()特殊方法来__abs__()类实例的内置abs()的行为。 对于abs()的返回值没有任何限制,并且在类定义中不存在特殊方法时,您将收到TypeError

In a class representing a vector in a two-dimensional space, abs() can be used to get the length of the vector. Let’s see it in action:

在表示二维空间中向量的类中, abs()可用于获取向量的长度。 让我们来看看它的作用:

 >>> >>>  class class VectorVector :
:
...     ...     def def __init____init__ (( selfself , , x_compx_comp , , y_compy_comp ):
):
...         ...         selfself .. x_comp x_comp = = x_comp
x_comp
...         ...         selfself .. y_comp y_comp = = y_comp
y_comp
...
...
...     ...     def def __abs____abs__ (( selfself ):
):
...         ...         return return (( x x * * x x + + y y * * yy ) ) ** ** 0.5
0.5
...
...
>>> >>>  vector vector = = VectorVector (( 33 , , 44 )
)
>>> >>>  absabs (( vectorvector )
)
5.0
5.0

It makes more intuitive sense to say “absolute value of vector” rather than calling something like vector.get_mag().

说“向量的绝对值”而不是调用诸如vector.get_mag()类的东西更直观。

使用str()清晰地打印对象 (Printing Your Objects Prettily Using str())

The str() built-in is used to cast an instance of a class to a str object, or more appropriately, to obtain a user-friendly string representation of the object which can be read by a normal user rather than the programmer. You can define the string format your object should be displayed in when passed to str() by defining the __str__() method in your class. Moreover, __str__() is the method that is used by Python when you call print() on your object.

内置的str()用于将类的实例str转换为str对象,或更合适的方法是,获取该对象的用户友好字符串表示形式,该字符串表示形式可由普通用户而非程序员读取。 您可以通过在类中定义__str__()方法来定义将对象传递给str()时应以的字符串格式。 此外, __str__()是在对象上调用print()时Python使用的方法。

Let’s implement this in the Vector class to format Vector objects as xi+yj. A negative y-component will be handled using the format mini-language:

让我们在Vector类中实现它,将Vector对象格式化为xi+yj 。 负y分量将使用mini-language格式处理:

It is necessary that __str__() returns a str object, and we get a TypeError if the return type is non-string.

__str__()返回一个str对象,并且如果返回类型为非字符串,则我们将收到TypeError

使用repr()表示对象 (Representing Your Objects Using repr())

The repr() built-in is used to obtain the parsable string representation of an object. If an object is parsable, that means that Python should be able to recreate the object from the representation when repr is used in conjunction with functions like eval(). To define the behavior of repr(), you can use the __repr__() special method.

内置的repr()用于获取对象的可分析字符串表示形式。 如果对象是可解析的,则意味着将repreval()类的eval()结合使用时,Python应该能够从表示形式重新创建对象。 要定义repr()的行为,可以使用__repr__()特殊方法。

This is also the method Python uses to display the object in a REPL session. If the __repr__() method is not defined, you will get something like <__main__.Vector object at 0x...> trying to look at the object in the REPL session. Let’s see it in action in the Vector class:

这也是Python在REPL会话中显示对象的方法。 如果__repr__()方法,您将尝试在REPL会话中查看<__main__.Vector object at 0x...> 。 让我们在Vector类中看到它的作用:

 >>> >>>  class class VectorVector :
:
...     ...     def def __init____init__ (( selfself , , x_compx_comp , , y_compy_comp ):
):
...         ...         selfself .. x_comp x_comp = = x_comp
x_comp
...         ...         selfself .. y_comp y_comp = = y_comp
y_comp
...
...
...     ...     def def __repr____repr__ (( selfself ):
):
...         ...         return return ff 'Vector({self.x_comp}, {self.y_comp})'
'Vector({self.x_comp}, {self.y_comp})'
...

...

>>> >>>  vector vector = = VectorVector (( 33 , , 44 )
)
>>> >>>  reprrepr (( vectorvector )
)
'Vector(3, 4)'

'Vector(3, 4)'

>>> >>>  b b = = evaleval (( reprrepr (( vectorvector ))
))
>>> >>>  typetype (( bb ), ), bb .. x_compx_comp , , bb .. y_comp
y_comp
(__main__.Vector, 3, 4)

(__main__.Vector, 3, 4)

>>> >>>  vector  vector  # Looking at object; __repr__ used
# Looking at object; __repr__ used
'Vector(3, 4)'
'Vector(3, 4)'

Note: In cases where the __str__() method is not defined, Python uses the __repr__() method to print the object, as well as to represent the object when str() is called on it. If both the methods are missing, it defaults to <__main__.Vector ...>. But __repr__() is the only method that is used to display the object in an interactive session. Absence of it in the class yields <__main__.Vector ...>.

注意:如果未定义__str__()方法,Python将使用__repr__()方法打印对象,并在调用str()时表示对象。 如果缺少这两种方法,则默认为<__main__.Vector ...> 。 但是__repr__()是用于在交互式会话中显示对象的唯一方法。 该类中不存在它会产生<__main__.Vector ...>

Also, while this distinction between __str__() and __repr__() is the recommended behavior, many of the popular libraries ignore this distinction and use the two methods interchangeably.

此外,尽管建议在__str__()__repr__()之间进行这种区分,但许多流行的库都忽略了这种区分,并交替使用这两种方法。

Here’s a recommended article on __repr__() and __str__() by our very own Dan Bader: Python String Conversion 101: Why Every Class Needs a “repr”.

这是我们自己的Dan Bader推荐的关于__repr__()__str__()的文章: Python字符串转换101:为什么每个类都需要“ repr”

使用bool()使对象真实或错误 (Making Your Objects Truthy or Falsey Using bool())

The bool() built-in can be used to obtain the truth value of an object. To define its behavior, you can use the __bool__() (__nonzero__() in Python 2.x) special method.

内置的bool()可用于获取对象的真值。 要定义其行为,可以使用__bool__() (在Python 2.x中为__nonzero__() )特殊方法。

The behavior defined here will determine the truth value of an instance in all contexts that require obtaining a truth value such as in if statements.

此处定义的行为将在需要获取真值的所有上下文(例如if语句)中确定实例的真值。

As an example, for the Order class that was defined above, an instance can be considered to be truthy if the length of the cart list is non-zero. This can be used to check whether an order should be processed or not:

例如,对于上面定义的Order类,如果购物车列表的长度不为零,则可以认为实例是真实的。 这可用于检查是否应处理订单:

Note: When the __bool__() special method is not implemented in a class, the value returned by __len__() is used as the truth value, where a non-zero value indicates True and a zero value indicates False. In case both the methods are not implemented, all instances of the class are considered to be True.

注意:如果在类中未实现__bool__()特殊方法,则__len__()返回的值将用作真值,其中非零值表示True ,零值表示False 。 如果两种方法均未实现,则将该类的所有实例都视为True

There are many more special methods that overload built-in functions. You can find them in the documentation. Having discussed some of them, let’s move to operators.

还有许多其他特殊方法可以使内置函数过载。 您可以在文档中找到它们。 在讨论了其中一些内容之后,让我们转向操作员。

重载内置运算符 (Overloading Built-in Operators)

Changing the behavior of operators is just as simple as changing the behavior of functions. You define their corresponding special methods in your class, and the operators work according to the behavior defined in these methods.

更改操作员的行为就像更改函数的行为一样简单。 您在类中定义了它们对应的特殊方法,并且运算符根据这些方法中定义的行为进行工作。

These are different from the above special methods in the sense that they need to accept another argument in the definition other than self, generally referred to by the name other. Let’s look at a few examples.

这些与上面的特殊方法的区别在于它们需要接受定义中除了self之外的另一个参数,通常称为other 。 让我们看几个例子。

使用+使您的对象能够添加 (Making Your Objects Capable of Being Added Using +)

The special method corresponding to the + operator is the __add__() method. Adding a custom definition of __add__() changes the behavior of the operator. It is recommended that __add__() returns a new instance of the class instead of modifying the calling instance itself. You’ll see this behavior quite commonly in Python:

+运算符相对应的特殊方法是__add__()方法。 添加__add__()的自定义定义会更改操作符的行为。 建议__add__()返回该类的新实例,而不要修改调用实例本身。 您将在Python中非常普遍地看到这种行为:

 >>> >>>  a a = = 'Real'
'Real'
>>> >>>  a a + + 'Python'  'Python'  # Gives new str instance
# Gives new str instance
'RealPython'
'RealPython'
>>> >>>  a  a  # Values unchanged
# Values unchanged
'Real'
'Real'
>>> >>>  a a = = a a + + 'Python'  'Python'  # Creates new instance and assigns a to it
# Creates new instance and assigns a to it
>>> >>>  a
a
'RealPython'
'RealPython'

You can see above that using the + operator on a str object actually returns a new str instance, keeping the value of the calling instance (a) unmodified. To change it, we need to explicitly assign the new instance to a.

您可以在上面看到,在str对象上使用+运算符实际上会返回一个新的str实例,而保持调用实例( a )的值不变。 要改变它,我们需要新的实例明确分配给a

Let’s implement the ability to append new items to our cart in the Order class using the operator. We’ll follow the recommended practice and make the operator return a new Order instance that has our required changes instead of making the changes directly to our instance:

让我们实现使用操作符将新商品追加到Order类中的购物车中的功能。 我们将遵循推荐的做法,并让操作员返回具有所需更改的新Order实例,而不是直接对我们的实例进行更改:

Similarly, you have the __sub__(), __mul__(), and other special methods which define the behavior of -, *, and so on. These methods should return a new instance of the class as well.

同样,您具有__sub__()__mul__()以及其他定义-*行为的特殊方法。 这些方法也应该返回该类的新实例。

快捷方式: +=运算符 (Shortcuts: the += Operator)

The += operator stands as a shortcut to the expression obj1 = obj1 + obj2. The special method corresponding to it is __iadd__(). The __iadd__() method should make changes directly to the self argument and return the result, which may or may not be self. This behavior is quite different from __add__() since the latter creates a new object and returns that, as you saw above.

+=运算符是表达式obj1 = obj1 + obj2的快捷方式。 与此相对应的特殊方法是__iadd__()__iadd__()方法应该直接对self参数进行更改,并返回结果,该结果可能是self ,也可能不是。 该行为与__add__()完全不同,因为后者会创建一个新对象并返回该对象,如您在上面看到的那样。

Roughly, any += use on two objects is equivalent to this:

大致而言,对两个对象的任何+=用法都等效于此:

 >>> >>> result result = = obj1 obj1 + + obj2
obj2
>>> >>> obj1 obj1 = = result
result

Here, result is the value returned by __iadd__(). The second assignment is taken care of automatically by Python, meaning that you do not need to explicitly assign obj1 to the result as in the case of obj1 = obj1 + obj2.

在这里, result__iadd__()返回的值。 第二次分配由Python自动处理,这意味着您不需要像obj1 = obj1 + obj2的情况那样将obj1显式分配给结果。

Let’s make this possible for the Order class so that new items can be appended to the cart using +=:

让我们为Order类使之成为可能,以便可以使用+=将新项目追加到购物车:

As can be seen, any change is made directly to self and it is then returned. What happens when you return some random value, like a string or an integer?

可以看出,任何更改都是直接对self ,然后将其返回。 当您返回某个随机值(例如字符串或整数)时会发生什么?

 >>> >>>  class class OrderOrder :
:
...     ...     def def __init____init__ (( selfself , , cartcart , , customercustomer ):
):
...         ...         selfself .. cart cart = = listlist (( cartcart )
)
...         ...         selfself .. customer customer = = customer
customer
...
...
...     ...     def def __iadd____iadd__ (( selfself , , otherother ):
):
...         ...         selfself .. cartcart .. appendappend (( otherother )
)
...         ...         return return 'Hey, I am string!'
'Hey, I am string!'
...
...
>>> >>>  order order = = OrderOrder ([([ 'banana''banana' , , 'apple''apple' ], ], 'Real Python''Real Python' )
)
>>> >>>  order order += += 'mango'
'mango'
>>> >>>  order
order
'Hey, I am string!'
'Hey, I am string!'

Even though the relevant item was appended to the cart, the value of order changed to what was returned by __iadd__(). Python implicitly handled the assignment for you. This can lead to surprising behavior if you forget to return something in your implementation:

即使相关项目已附加到购物车, order的值仍更改为__iadd__()返回的__iadd__() 。 Python隐式为您处理分配。 如果您忘记在实现中返回某些内容,则可能导致令人惊讶的行为:

Since all Python functions (or methods) return None implicitly, order is reassigned to None and the REPL session doesn’t show any output when order is inspected. Looking at the type of order, you see that it is now NoneType. Therefore, always make sure that you’re returning something in your implementation of __iadd__() and that it is the result of the operation and not anything else.

由于所有Python函数(或方法) None隐式返回None ,因此将order重新分配为None并且在检查order时,REPL会话不显示任何输出。 查看order的类型,您会看到现在是NoneType 。 因此,请始终确保在实现__iadd__()中返回的内容是操作的结果,而不是其他任何结果。

Similar to __iadd__(), you have __isub__(), __imul__(), __idiv__() and other special methods which define the behavior of -=, *=, /=, and others alike.

__iadd__()相似,您具有__isub__()__imul__() __isub__()__imul__() __idiv__()以及其他定义-=*=/=等行为的特殊方法。

Note: When __iadd__() or its friends are missing from your class definition but you still use their operators on your objects, Python uses __add__() and its friends to get the result of the operation and assigns that to the calling instance. Generally speaking, it is safe to not implement __iadd__() and its friends in your classes as long as __add__() and its friends work properly (return something which is the result of the operation).

注意:当类定义中缺少__iadd__()或它的朋友,但是您仍然在对象上使用它们的运算符时,Python使用__add__()及其朋友来获取操作结果并将其分配给调用实例。 一般而言,只要__add__()及其朋友正常工作(不返回操作结果__iadd__() ,就可以在您的类中不实现__iadd__()及其朋友。

The Python documentation has a good explanation of these methods. Also, take a look at this example which shows the caveats involved with += and the others when working with immutable types.

Python 文档对这些方法有很好的解释。 另外,请看一下这个示例, 示例显示了使用不可变类型时与+=有关的警告和其他警告。

使用[]索引和切片对象 (Indexing and Slicing Your Objects Using [])

The [] operator is called the indexing operator and is used in various contexts in Python such as getting the value at an index in sequences, getting the value associated with a key in dictionaries, or obtaining a part of a sequence through slicing. You can change its behavior using the __getitem__() special method.

[]运算符称为索引运算符,在Python中的各种上下文中使用,例如按顺序获取索引处的值,获取字典中与键相关联的值或通过切片获取部分序列。 您可以使用__getitem__()特殊方法更改其行为。

Let’s configure our Order class so that we can directly use the object and obtain an item from the cart:

让我们配置Order类,以便我们可以直接使用该对象并从购物车中获取商品:

 >>> >>>  class class OrderOrder :
:
...     ...     def def __init____init__ (( selfself , , cartcart , , customercustomer ):
):
...         ...         selfself .. cart cart = = listlist (( cartcart )
)
...         ...         selfself .. customer customer = = customer
customer
...
...
...     ...     def def __getitem____getitem__ (( selfself , , keykey ):
):
...         ...         return return selfself .. cartcart [[ keykey ]
]
...
...
>>> >>>  order order = = OrderOrder ([([ 'banana''banana' , , 'apple''apple' ], ], 'Real Python''Real Python' )
)
>>> >>>  orderorder [[ 00 ]
]
'banana'
'banana'
>>> >>>  orderorder [[ -- 11 ]
]
'apple'
'apple'

You’ll notice that above, the name of the argument to __getitem__() is not index but key. This is because the argument can be of mainly three forms: an integer value, in which case it is either an index or a dictionary key, a string value, in which case it is a dictionary key, and a slice object, in which case it will slice the sequence used by the class. While there are other possibilities, these are the ones most commonly encountered.

您会注意到,上面的__getitem__()参数的名称不是index而是key 。 这是因为参数主要有以下三种形式: 整数值 (在这种情况下它是索引或字典键), 字符串值 (在这种情况下它是字典键)和slice对象 (在这种情况下)它将切片该类使用的序列。 尽管还有其他可能性,但这是最常见的可能性。

Since our internal data structure is a list, we can use the [] operator to slice the list, as in this case, the key argument will be a slice object. This is one of the biggest advantages of having a __getitem__() definition in your class. As long as you’re using data structures that support slicing (lists, tuples, strings, and so on), you can configure your objects to directly slice the structure:

由于我们的内部数据结构是一个列表,因此我们可以使用[]运算符对列表进行切片,因为在这种情况下, key参数将是一个切片对象。 这是在类中具有__getitem__()定义的最大优点之一。 只要您使用支持切片的数据结构(列表,元组,字符串等),就可以将对象配置为直接切片结构:

Note: There is a similar __setitem__() special method that is used to define the behavior of obj[x] = y. This method takes two arguments in addition to self, generally called key and value, and can be used to change the value at key to value.

注意:有一个类似的__setitem__()特殊方法用于定义obj[x] = y的行为。 此方法除self之外还接受两个参数,通常称为keyvalue ,可用于将key的值更改为value

反向运算符:使您的课程在数学上正确 (Reverse Operators: Making Your Classes Mathematically Correct)

While defining the __add__(), __sub__(), __mul__(), and similar special methods allows you to use the operators when your class instance is the left-hand side operand, the operator will not work if the class instance is the right-hand side operand:

在定义__add__()__sub__() __add__()__sub__() __mul__() ,类似的特殊方法允许您在类实例为左侧操作数时使用运算符,但如果类实例为右侧操作数,则该运算符将不起作用。手侧操作数:

 >>> >>>  class class MockMock :
:
...     ...     def def __init____init__ (( selfself , , numnum ):
):
...         ...         selfself .. num num = = num
num
...     ...     def def __add____add__ (( selfself , , otherother ):
):
...         ...         return return MockMock (( selfself .. num num + + otherother )
)
...
...
>>> >>>  mock mock = = MockMock (( 55 )
)
>>> >>>  mock mock = = mock mock + + 6
6
>>> >>>  mockmock .. num
num
11

11

>>> >>>  mock mock = = 6 6 + + MockMock (( 55 )
)
Traceback (most recent call last):
  File Traceback (most recent call last):
  File "<stdin>", line "<stdin>" , line 1, in 1 , in <module>
<module>
TypeError: TypeError : unsupported operand type(s) for +: 'int' and 'Mock'
unsupported operand type(s) for +: 'int' and 'Mock'

If your class represents a mathematical entity like a vector, a coordinate, or a complex number, applying the operators should work in both the cases since it is a valid mathematical operation.

如果您的班级代表数学实体,例如向量,座标或复数,则在两种情况下都应应用运算符,因为这是有效的数学运算。

Moreover, if the operators work only when the instance is the left operand, we are violating the fundamental principle of commutativity in many cases. Therefore, to help you make your classes mathematically correct, Python provides you with reverse special methods such as __radd__(), __rsub__(), __rmul__(), and so on.

此外,如果运算符仅在实例为左操作数时工作,则在许多情况下,我们违反了可交换性的基本原理。 因此,为了帮助您使类在数学上正确,Python为您提供了反向的特殊方法,例如__radd__()__rsub__() __radd__()__rsub__() __rmul__()等。

These handle calls such as x + obj, x - obj, and x * obj, where x is not an instance of the concerned class. Just like __add__() and the others, these reverse special methods should return a new instance of class with the changes of the operation rather than modifying the calling instance itself.

这些处理诸如x + objx - objx * obj之类的调用,其中x不是相关类的实例。 就像__add__()和其他方法一样,这些反向的特殊方法应返回具有更改操作的类的新实例,而不是修改调用实例本身。

Let’s configure __radd__() in the Order class in such a way that it will append something at the front of the cart. This can be used in cases where the cart is organized in terms of the priority of the orders:

让我们在Order类中配置__radd__() ,以便将某些内容附加在购物车的前面。 这可以用于根据订单优先级组织购物车的情况:

一个完整的例子 (A Complete Example)

To drive all these points home, it’s better to look at an example class which implements these operators together.

为了将所有这些观点讲清楚,最好看一看示例类,这些示例类一起实现这些运算符。

Let’s reinvent the wheel and implement our own class to represent complex numbers, CustomComplex. Objects of our class will support a variety of built-in functions and operators, making them behave very similar to the built-in complex numbers class:

让我们重新发明轮子,并实现我们自己的类以表示复数CustomComplex 。 我们的类的对象将支持各种内置函数和运算符,使其行为与内置复数类非常相似:

 from from math math import import hypothypot , , atanatan , , sinsin , , cos

cos

class class CustomComplexCustomComplex :
    :
    def def __init____init__ (( selfself , , realreal , , imagimag ):
        ):
        selfself .. real real = = real
        real
        selfself .. imag imag = = imag
imag

The constructor handles only one kind of call, CustomComplex(a, b). It takes positional arguments, representing the real and imaginary parts of the complex number.

构造函数仅处理一种调用CustomComplex(a, b) 。 它采用位置参数,表示复数的实部和虚部。

Let’s define two methods inside the class, conjugate() and argz(), which will give us the complex conjugate and the argument of a complex number respectively:

让我们在类中定义两个方法conjugate()argz() ,这两个方法将分别为我们提供复数共轭和复数的参数:

Note: __class__ is not a special method but a class attribute which is present by default. It has a reference to the class. By using it here, we are obtaining that and then calling the constructor in the usual manner. In other words, this is equivalent to CustomComplex(real, imag). This is done here to avoid refactoring the code if the name of the class changes someday.

注意: __class__不是特殊方法,而是默认存在的class属性。 它具有对该类的引用。 通过在这里使用它,我们得到了它,然后以通常的方式调用了构造函数。 换句话说,这等效于CustomComplex(real, imag) 。 这样做是为了避免在类名有一天改变时重构代码。

Next, we configure abs() to return the modulus of a complex number:

接下来,我们配置abs()以返回复数的模数:

 def def __abs____abs__ (( selfself ):
    ):
    return return hypothypot (( selfself .. realreal , , selfself .. imagimag )
)

We will follow the recommended distinction between __repr__() and __str__() and use the first for the parsable string representation and the second for a “pretty” representation.

我们将遵循建议的__repr__()__str__()之间的区别, __repr__()第一个用于可分析的字符串表示形式,将第二个用于“漂亮”表示形式。

The __repr__() method will simply return CustomComplex(a, b) in a string so that we can call eval() to recreate the object, while the __str__() method will return the complex number in brackets, as (a+bj):

__repr__()方法将简单地以字符串形式返回CustomComplex(a, b) ,以便我们可以调用eval()来重新创建对象,而__str__()方法将以方括号形式返回复数,例如(a+bj)

Mathematically, it is possible to add any two complex numbers or add a real number to a complex number. Let’s configure the + operator in such a way that it works for both cases.

从数学上讲,可以将任何两个复数相加或将实数添加到复数。 让我们以在两种情况下都可以使用的方式配置+运算符。

The method will check the type of the right-hand side operator. In case it is an int or a float, it will increment only the real part (since any real number, a, is equivalent to a+0j), while in the case of another complex number, it will change both the parts:

该方法将检查右侧运算符的类型。 如果它是intfloat ,它将仅增加实数部分(因为任何实数a都等于a+0j ),而如果是其他复数,它将改变这两个部分:

 def def __add____add__ (( selfself , , otherother ):
    ):
    if if isinstanceisinstance (( otherother , , floatfloat ) ) or or isinstanceisinstance (( otherother , , intint ):
        ):
        real_part real_part = = selfself .. real real + + other
        other
        imag_part imag_part = = selfself .. imag

    imag

    if if isinstanceisinstance (( otherother , , CustomComplexCustomComplex ):
        ):
        real_part real_part = = selfself .. real real + + otherother .. real
        real
        imag_part imag_part = = selfself .. imag imag + + otherother .. imag

    imag

    return return selfself .. __class____class__ (( real_partreal_part , , imag_partimag_part )
)

Similarly, we define the behavior for - and *:

同样,我们定义-*的行为:

Since both addition and multiplication are commutative, we can define their reverse operators by calling __add__() and __mul__() in __radd__() and __rmul__() respectively. On the other hand, the behavior of __rsub__() needs to be defined since subtraction is not commutative:

由于两个加法和乘法是可交换的,我们可以通过调用定义其反向运营商__add__()__mul__()__radd__()__rmul__()分别。 另一方面,由于减法不是可交换的,因此需要定义__rsub__()的行为:

 def def __radd____radd__ (( selfself , , otherother ):
    ):
    selfself .. __add____add__ (( otherother )

)

def def __rmul____rmul__ (( selfself , , otherother ):
    ):
    selfself .. __mul____mul__ (( otherother )

)

def def __rsub____rsub__ (( selfself , , otherother ):
    ):
    # x - y != y - x
    # x - y != y - x
    if if isinstanceisinstance (( otherother , , floatfloat ) ) or or isinstanceisinstance (( otherother , , intint ):
        ):
        real_part real_part = = other other - - selfself .. real
        real
        imag_part imag_part = = -- selfself .. imag

    imag

    return return selfself .. __class____class__ (( real_partreal_part , , imag_partimag_part )
)

Note: You might have noticed that we didn’t add a construct to handle a CustomComplex instance here. This is because, in such a case, both the operands are instances of our class, and __rsub__() won’t be responsible for handling the operation. Instead, __sub__() will be called. This is a subtle but important detail.

注意:您可能已经注意到,我们没有在此处添加用于处理CustomComplex实例的构造。 这是因为在这种情况下,两个操作数都是我们类的实例,而__rsub__()将不负责处理该操作。 而是会调用__sub__() 。 这是一个微妙但重要的细节。

Now, we take care of the two operators, == and !=. The special methods used for them are __eq__() and __ne__(), respectively. Two complex numbers are said to be equal if their corresponding real and imaginary parts are both equal. They are said to be unequal when either one of these are unequal:

现在,我们照顾两个运算符==!= 。 用于它们的特殊方法分别是__eq__()__ne__() 。 如果两个复数对应的实部和虚部都相等,则称它们相等。 当其中一个不相等时,它们被认为是不平等的:

Note: The Floating-Point Guide is an article that talks about comparing floats and floating-point precision. It highlights the caveats involved in comparing floats directly, which is something we’re doing here.

注意: 《浮点指南》是一篇有关比较浮点数和浮点精度的文章。 它突出显示了直接比较浮点数时需要注意的事项,这是我们在此所做的。

It is also possible to raise a complex number to any power using a simple formula. We configure the behavior for both the built-in pow() and the ** operator using the __pow__() special method:

也可以使用简单的公式将复数提高为任意幂。 我们使用__pow__()特殊方法为内置pow()**运算符配置行为:

 def def __pow____pow__ (( selfself , , otherother ):
    ):
    r_raised r_raised = = absabs (( selfself ) ) ** ** other
    other
    argz_multiplied argz_multiplied = = selfself .. argzargz () () * * other

    other

    real_part real_part = = roundround (( r_raised r_raised * * coscos (( argz_multipliedargz_multiplied ))
    ))
    imag_part imag_part = = roundround (( r_raised r_raised * * sinsin (( argz_multipliedargz_multiplied ))

    ))

    return return selfself .. __class____class__ (( real_partreal_part , , imag_partimag_part )
)

Note: Take a close look at the definition of the method. We are calling abs() to obtain the modulus of the complex number. So, once you’ve defined the special method for a particular function or operator in your class, it can be used in other methods of the same class.

注意:请仔细查看方法的定义。 我们正在调用abs()以获得复数的模数。 因此,一旦为类中的特定函数或运算符定义了特殊方法,就可以在同一类的其他方法中使用它。

Let’s create two instances of this class, one having a positive imaginary part and one having a negative imaginary part:

让我们创建此类的两个实例,一个实例具有正的虚部,一个实例具有负的虚部:

String representations:

字符串表示形式:

 >>> >>>  a
a
CustomComplex(1, 2)
CustomComplex(1, 2)
>>> >>>  b
b
CustomComplex(3, -4)
CustomComplex(3, -4)
>>> >>>  printprint (( aa )
)
(1+2j)
(1+2j)
>>> >>>  printprint (( bb )
)
(3-4j)
(3-4j)

Recreating the object using eval() with repr():

使用带有repr() eval()对象:

Addition, subtraction, and multiplication:

加法,减法和乘法:

 >>> >>>  a a + + b
b
CustomComplex(4, -2)
CustomComplex(4, -2)
>>> >>>  a a - - b
b
CustomComplex(-2, 6)
CustomComplex(-2, 6)
>>> >>>  a a + + 5
5
CustomComplex(6, 2)
CustomComplex(6, 2)
>>> >>>  3 3 - - a
a
CustomComplex(2, -2)
CustomComplex(2, -2)
>>> >>>  a a * * 6
6
CustomComplex(6, 12)
CustomComplex(6, 12)
>>> >>>  a a * * (( -- 66 )
)
CustomComplex(-6, -12)
CustomComplex(-6, -12)

Equality and inequality checks:

平等和不平等检查:

Finally, raising a complex number to some power:

最后,将复数提高一些幂:

 >>> >>>  a a ** ** 2
2
CustomComplex(-3, 4)
CustomComplex(-3, 4)
>>> >>>  b b ** ** 5
5
CustomComplex(-237, 3116)
CustomComplex(-237, 3116)

As you can see, objects of our custom class behave and look like those of a built-in class and are very Pythonic. The full example code for this class is embedded below.

如您所见,我们自定义类的对象的行为和外观类似于内置类的对象,并且具有Python风格。 此类的完整示例代码嵌入在下面。

回顾与资源 (Recap and Resources)

In this tutorial, you learned about the Python Data Model and how the Data Model can be used to build Pythonic classes. You learned about changing the behavior of built-in functions such as len(), abs(), str(), bool(), and so on. You also learned about changing the behavior of built-in operators like +, -, *, **, and so forth.

在本教程中,您了解了Python数据模型以及如何使用该数据模型来构建Pythonic类。 您了解了如何更改内置函数(例如len()abs()str()bool() 。 您还了解了如何更改内置运算符的行为,例如+-***等。

Free Bonus: Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.

免费奖金: 单击此处可获取免费的Python OOP作弊表 ,该指南将为您提供最佳的教程,视频和书籍,以了解有关使用Python进行面向对象编程的更多信息。

After reading this, you can confidently create classes that make use of the best idiomatic features of Python and make your objects Pythonic!

阅读本文后,您可以放心地创建利用Python最佳惯用功能的类,并使您的对象成为Pythonic!

For more information on the Data Model, and function and operator overloading, take a look at these resources:

有关数据模型以及函数和运算符重载的更多信息,请查看以下资源:

翻译自: https://www.pybloggers.com/2018/05/operator-and-function-overloading-in-custom-python-classes/

python类重载运算符

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值