如果你在字符串(str)对象上进行过 + 或 * 运算,你一定会注意到它跟整数或浮点数对象的行为差异:

>>> # 两个数字相加

>>> 1 + 2

3

>>> # Concatenates the two strings

>>> 'Real' + 'Python'

'RealPython'

>>> # Gives the product

>>> 3 * 2

6

>>> # Repeats the string

>>> 'Python' * 3

'PythonPythonPython'

你可能很好奇,为什么同一个内置运算符或函数,作用在不同对象上面会表现出不同的行为。这种现象被称为运算符重载或者函数重载。本文将帮助你理解这个机制,今后,你可以将它运用到你的自定义类中,让你的编码更 Pythonic 。

本文中你将学到:Python 处理运算符和内置函数的API

len() 以及其它内置函数背后的“秘密”

如何让你的类可以使用运算符进行运算

如何让你的类与内置函数的操作保持兼容及行为一致

最后,我们将提供一个具体类的实例。该对象的行为与内置函数及运算符的行为保持一致。

Python 数据模型

假设,你有一个用来表示在线购物车的类,包含一个购物车(列表)和一名顾客(字符串或者其它表示顾客类的实例)。

这种情形下,很自然地需要获取购物车的列表长度。Python 新手可能会考虑在他的类中实现一个叫 get_cart_len() 的方法来处理这个需求。实际上,你只需要配置一下,当我们传入购物车实例对象时,使用内置函数 len() 就可以返回购物车的长度。

另一个场景中,我们可能需要添加某些商品到购物车。某些新手同样会想要实现一个叫 append_to_cart() 的方法来处理获取一个项,并将它添加到购物车列表中。其实你只需配置一下 + 运算符就可以实现将项目添加到购物车列表的操作。

Python 使用特定的方法来处理这些过程。这些特殊的方法都有一个特定的命名约定,以双下划线开始,后面跟命名标识符,最后以双下划线结束。

本质上讲,每一种内置的函数或运算符都对应着对象的特定方法。比如,__len__() 方法对应内置 len() 函数,而 __add__() 方法对应 + 运算符。

默认情况下,绝大多数内置函数和运算符不会在你的类中工作。你需要在类定义中自己实现对应的特定方法,实例对象的行为才会和内置函数和运算符行为保持一致。当你完成这个过程,内置函数或运算符的操作才会如预期一样工作

这些正是数据模型帮你完成的过程(文档的第3部分)。该文档中列举了所有可用的特定方法,并提供了重载它们的方法以便你在自己的对象中使用。

我们看看这意味着什么。轶事:由于这些方法的特殊命名方式,它们又被称作 dunder 方法,是双下划线方法的简称。有时候它们也被称作特殊方法或魔术方法。我们更喜欢 dunder 方法这个叫法。

len() 和 [] 的内部运行机制

每一个 Python 类都为内置函数或运算符定义了自己的行为方式。当你将某个实例对象传入内置函数或使用运算符时,实际上等同于调用带相应参数的特定方法。

如果有一个内置函数,func(),它关联的特定方法是 __func__(),Python 解释器会将其解释为类似于 obj.__func__()的函数调用,obj 就是实例对象。如果是运算符操作,比如 opr ,关联的特定方法是 __opr__(),Python 将 obj1 obj2 解释为类似于 obj1.__opr__(obj2) 的形式。

所以,当你在实例对象上调用 len() 时,Python 将它处理为 obj.__len__() 调用。当你在可迭代对象上使用 [] 运算符来获取指定索引位置上的值时,Python 将它处理为 itr.__getitem__(index),itr 表示可迭代对象,index 表示你要索引的位置。

因此,在你定义自己的类时,你可以重写关联的函数或运算符的行为。因为,Python 在后台调用的是你定义的方法。我们看个例子来理解这种机制:

>>> a = 'Real Python'

>>> b = ['Real', 'Python']

>>> len(a)

11

>>> a.__len__()

11

>>> b[0]

'Real'

>>> b.__getitem__(0)

'Real'

如你所见,当你分别使用函数或者关联的特定方法时,你获得了同样的结果。实际上,如果你使用内置函数 dir() 列出一个字符串对象的所有方法和属性,你也可以在里面找到这些特定方法:

>>> dir(a)

['__add__',

'__class__',

'__contains__',

'__delattr__',

'__dir__',

...,

'__iter__',

'__le__',

'__len__',

'__lt__',

...,

'swapcase',

'title',

'translate',

'upper',

'zfill']

如果内置函数或运算符的行为没有在类中特定方法中定义,你会得到一个类型错误。那么,如何在自己的类中使用特定方法呢?

重载内置函数

数据模型中定义的大多数特定方法都可以用来改变 len, abs, hash, divmod 等内置函数的行为。你只需要在你的类中定义好关联的特定方法就好了。下面举几个栗子:

用 len() 函数获取你对象的长度

要更改 len() 的行为,你需要在你的类中定义 __len__() 这个特定方法。每次你传入类的实例对象给 len() 时,它都会通过你定义的 __len__() 来返回结果。下面,我们来实现前面 order 类的 len() 函数的行为:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __len__(self):

... return len(self.cart)

...

>>> order = Order(['banana', 'apple', 'mango'], 'Real Python')

>>> len(order)

3

你现在可以直接使用 len() 来获得购物车列表长度。相比 order.get_cart_len() 调用方式,使用 len() 更符合“队列长度”这个直观表述,你的代码调用更 Pythonic,更符合直观习惯。如果你没有定义 __len__() 这个方法,当你调用 len() 时就会返回一个类型错误:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

>>> order = Order(['banana', 'apple', 'mango'], 'Real Python')

>>> len(order) # Calling len when no __len__

Traceback (most recent call last):

File "", line 1, in

TypeError: object of type 'Order' has no len()

此外,当你重载 len() 时,你需要记住的是 Python 需要该函数返回的是一个整数值,如果你的方法函数返回的是除整数外的其它值,也会报类型错误(TypeError)。此做法很可能是为了与 len() 通常用于获取序列的长度这种用途(序列的长度只能是整数)保持一致:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __len__(self):

... return float(len(self.cart)) # Return type changed to float

...

>>> order = Order(['banana', 'apple', 'mango'], 'Real Python')

>>> len(order)

Traceback (most recent call last):

File "", line 1, in

TypeError: 'float' object cannot be interpreted as an integer

让你的对象提供 abs() 运算

我们可以通过定义类的 __abs__() 方法来控制内置函数 abs() 作用于实例对象时的行为。abs() 函数对返回值没有约束,只是在你的类没有定义关联的特定方法时会得到类型错误。在表示二维空间向量的类中, abs() 函数可以被用来获取向量的长度。下面演示如何做:

>>> class Vector:

... def __init__(self, x_comp, y_comp):

... self.x_comp = x_comp

... self.y_comp = y_comp

...

... def __abs__(self):

... return (x * x + y * y) ** 0.5

...

>>> vector = Vector(3, 4)

>>> abs(vector)

5.0

这样表述为“向量的绝对值”相对于 vector.get_mag() 这样的调用会显得更直观。

通过 str() 提供更加美观的对象输出格式

内置函数 str() 通常用于将类实例转换为字符串对象,更准确地说,为普通用户提供更友好的字符串表示方式,而不仅仅是面向程序员。通过在你的类定义中实现 __str__() 特定方法你可以自定义你的对象使用 str() 输出时的字符串输出格式。此外,当你使用 print() 输出你的对象时 Python 实际上调用的也是 __str__() 方法。

我们将在 Vector 类中实现 Vector 对象的输出格式为 xi+yj。负的 Y 方向的分量输出格式使用迷你语言来处理:

>>> class Vector:

... def __init__(self, x_comp, y_comp):

... self.x_comp = x_comp

... self.y_comp = y_comp

...

... def __str__(self):

... # By default, sign of +ve number is not displayed

... # Using `+`, sign is always displayed

... return f'{self.x_comp}i{self.y_comp:+}j'

...

>>> vector = Vector(3, 4)

>>> str(vector)

'3i+4j'

>>> print(vector)

3i+4j

需要注意的是 __str__() 必须返回一个字符串对象,如果我们返回值的类型为非字符串类型,将会报类型错误。

使用 repr() 来显示你的对象

repr() 内置函数通常用来获取对象的可解析字符串表示形式。如果一个对象是可解析的,这意味着使用 repr 再加上 eval() 此类函数,Python 就可以通过字符串表述来重建对象。要定义 repr() 函数的行为,你可以通过定义 __repr__() 方法来实现。

这也是 Python 在 REPL(交互式)会话中显示一个对象所使用的方式 。如果 __repr__() 方法没有定义,你在 REPL 会话中试图输出一个对象时,会得到类似 这样的结果。我们来看 Vector 类这个例子的实际运行情况:

>>> class Vector:

... def __init__(self, x_comp, y_comp):

... self.x_comp = x_comp

... self.y_comp = y_comp

...

... def __repr__(self):

... return f'Vector({self.x_comp}, {self.y_comp})'

...

>>> vector = Vector(3, 4)

>>> repr(vector)

'Vector(3, 4)'

>>> b = eval(repr(vector))

>>> type(b), b.x_comp, b.y_comp

(__main__.Vector, 3, 4)

>>> vector # Looking at object; __repr__ used

'Vector(3, 4)'注意:如果 __str__() 方法没有定义,当在对象上调用 str() 函数,Python 会使用 __repr__() 方法来代替,如果两者都没有定义,默认输出为 。在交互环境中 __repr__()是用来显示对象的唯一方式,类定义中缺少它,只会输出 。

尽管,这是官方推荐的两者行为的区别,但在很多流行的库中实际上都忽略了这种行为差异,而交替使用它们。

关于 __repr__() 和 __str__() 的问题推荐阅读 Dan Bader 写的这篇比较出名的文章:Python 字符串转换 101:为什么每个类都需要定义一个 “repr”

使用 bool() 提供布尔值判断

内置的函数 bool() 可以用来提供真值检测,要定义它的行为,你可以通过定义 __bool__() (Python 2.x版是 __nonzero__())特定方法来实现。

此处的定义将供所有需要判断真值的上下文(比如 if 语句)中使用。比如,前面定义的 Order 类,某个实例中可能需要判断购物车长度是否为非零。用来检测是否继续处理订单:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __bool__(self):

... return len(self.cart) > 0

...

>>> order1 = Order(['banana', 'apple', 'mango'], 'Real Python')

>>> order2 = Order([], 'Python')

>>> bool(order1)

True

>>> bool(order2)

False

>>> for order in [order1, order2]:

... if order:

... print(f"{order.customer}'s order is processing...")

... else:

... print(f"Empty order for customer {order.customer}")

Real Python's order is processing...

Empty order for customer Python

注意:如果类的 __bool__() 特定方法没有定义, __len__() 方法返回值会被用来做真值判断,如果是一个非零值则为真,零值为假。如果两个方法都没有被定义,此类的所有实例检测都会被判断为真值。

还有更多用来重载内置函数的特定方法,你可以在官方文档中找到它们的用法,下面我们开始讨论运算符重载的问题。

重载内置运算符

要改变一个运算符的行为跟改变函数的行为一样,很简单。你只需在类中定义好对应的特定方法,运算符就会按照你设定的方式运行。

跟上面的特定方法不同的是,这些方法定义中,除了接收自身(self)这个参数外,它还需要另一个参数。下面,我们看几个例子。

让对象能够使用 + 运算符做加法运算

与 + 运算符对应的特定方法是 __add__() 方法。添加一个自定义的 __add__() 方法将会改变该运算符的行为。建议让 __add__() 方法返回一个新的实例对象而不要修改调用的实例本身。在 Python 中,这种行为非常常见:

>>> a = 'Real'

>>> a + 'Python' # Gives new str instance

'RealPython'

>>> a # Values unchanged

'Real'

>>> a = a + 'Python' # Creates new instance and assigns a to it

>>> a

'RealPython'

你会发现上面例子中字符串对象进行 + 运算会返回一个新的字符串,原来的字符串本身并没有被改变。要改变这种方式,我们需要显式地将生成的新实例赋值给 a。

我们将在 Order 类中实现通过 + 运算符来将新的项目添加到购物车中。我们遵循推荐的方法,运算后返回一个新的 Order 实例对象而不是直接更改现有实例对象的值:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __add__(self, other):

... new_cart = self.cart.copy()

... new_cart.append(other)

... return Order(new_cart, self.customer)

...

>>> order = Order(['banana', 'apple'], 'Real Python')

>>> (order + 'orange').cart # New Order instance

['banana', 'apple', 'mango']

>>> order.cart # Original instance unchanged

['banana', 'apple']

>>> order = order + 'mango' # Changing the original instance

>>> order.cart

['banana', 'apple', 'mango']

同样的,还有其他的 __sub__(), __mul__() 等等特定方法,它们分别对应 -,*,等等运算符。它们也都是返回新的实例对象。

一种快捷方式:+= 运算符

+= 运算符通常作为表达式 obj1 = obj1 + obj2 的一种快捷方式。对应的特定方法是 iadd(),该方法会直接修改自身的值,返回的结果可能是自身也可能不是自身。这一点跟 add() 方法有很大的区别,后者是生成新对象作为结果返回。

大致来说,+= 运算符等价于:

>>> result = obj1 + obj2

>>> obj1 = result

上面,result 是 __iadd__() 返回的值。第二步赋值是 Python 自动处理的,也就是说你无需显式地用表达式 obj1 = obj1 + obj2 将结果赋值给 obj1 。

我们将在 Order 类中实现这个功能,这样我们就可以使用 += 来添加新项目到购物车中:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __iadd__(self, other):

... self.cart.append(other)

... return self

...

>>> order = Order(['banana', 'apple'], 'Real Python')

>>> order += 'mango'

>>> order.cart

['banana', 'apple', 'mango']

如上所见,所有的更改是直接作用在对象自身上,并返回自身。如果我们让它返回一些随机值比如字符串、整数怎样?

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __iadd__(self, other):

... self.cart.append(other)

... return 'Hey, I am string!'

...

>>> order = Order(['banana', 'apple'], 'Real Python')

>>> order += 'mango'

>>> order

'Hey, I am string!'

尽管,我们往购物车里添加的是相关的项,但购物车的值却变成了 __iadd__() 返回的值。Python 在后台隐式处理这个过程。如果你在方法实现中忘记处理返回内容,可能会出现令人惊讶的行为:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __iadd__(self, other):

... self.cart.append(other)

...

>>> order = Order(['banana', 'apple'], 'Real Python')

>>> order += 'mango'

>>> order # No output

>>> type(order)

NoneType

Python 中所有的函数(方法)默认都是返回 None,因此,order 的值被设置为默认值 None,交互界面不会有输出显示。如果检查 order 的类型,显示为 NoneType 类型。因此,你需要确保在 __iadd__() 的实现中返回期望得到的结果而不是其他什么东东。

与 __iadd__() 类似, __isub__(), __imul__(), __idiv__() 等特定方法相应地定义了 -=, *=, /= 等运算符的行为。注意:当 __iadd__() 或者同系列的方法没有在你的类中定义,而你又在你的对象上使用这些运算符时。Python 会用 __add__() 系列方法来替代并返回结果。通常来讲,如果 __add__() 系列方法能够返回预期正确的结果,不使用 __iadd__() 系列的方法是一种安全的方式。

Python 的文档提供了这些方法的详细说明。此外,可以看看当使用不可变类型涉及到的 +=及其他运算符需要注意到的附加说明的代码实例。

使用 [] 运算符来索引和分片你的对象

[] 运算符被称作索引运算符,在 Python 各上下文中都有用到,比如获取序列某个索引的值,获取字典某个键对应的值,或者对序列的切片操作。你可以通过 __getitem__() 特定方法来控制该运算符的行为。

我们设置一下 Order 类的定义,让我们可以直接获取购物车对象中的项:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __getitem__(self, key):

... return self.cart[key]

...

>>> order = Order(['banana', 'apple'], 'Real Python')

>>> order[0]

'banana'

>>> order[-1]

'apple'

你可能会注意到上面的例子中, __getitem__() 方法的参数名并不是 index 而是 key。这是因为,参数主要接收三种类型的值:整数值,通常是一个索引或字典的键值;字符串,字典的键值;切片对象,序列对象的切片。当然,也可能会有其他的值类型,但这三种是最常见的形式。

因为我们的内部数据结构是一个列表,我们可以使用 [] 运算符来对列表进行切片,这时 key 参数会接收一个切片对象。这就是在类中定义 __getitem__() 方法的最大优势。只要你使用的数据结构支持切片操作(列表、元组、字符串等等),你就可以定义你的对象直接对数据进行切片:

>>> order[1:]

['apple']

>>> order[::-1]

['apple', 'banana']注意:有一个类似的 __setitem__() 特定方法可定义类似 obj[x] = y 这种行为。此方法除自身外还需要两个参数,一般称为 key 和 value,用来更改指定 key 索引的值。

逆运算符:让你的类在数学计算上正确

在你定义了 __add__(), __sub__(), __mul__(),以及类似的方法后,类实例作为左侧操作数时可以正确运行,但如果作为右侧操作数则不会正常工作:

>>> class Mock:

... def __init__(self, num):

... self.num = num

... def __add__(self, other):

... return Mock(self.num + other)

...

>>> mock = Mock(5)

>>> mock = mock + 6

>>> mock.num

11

>>> mock = 6 + Mock(5)

Traceback (most recent call last):

File "", line 1, in

TypeError: unsupported operand type(s) for +: 'int' and 'Mock'

如果你的类表示的是一个数学实体,比如向量、坐标或复数,运算符应该在这两种方式下都能正确运算,因为它是有效的数学运算规则。此外,如果某个运算符仅仅在操作数为左侧时才工作,这在数学上违背了交换律规则。因此,为了保证在数学上的正确,Python 为你提供了反向计算的 radd(), rsub(), rmul()等特定方法。

这些方法处理类似 x + obj, x - obj, 以及 x * obj 形式的运算,其中 x 不是一个类实例对象。和 __add__() 及其他方法一样,这些方法也应该返回一个新的实例对象,而不是修改自身。

我们在 Order 类中定义 __radd__() 方法,这样就可以将某些项操作数放在购物车对象前面进行添加。这还可以用在购物车内订单是按照优先次序排列的情况。:

>>> class Order:

... def __init__(self, cart, customer):

... self.cart = list(cart)

... self.customer = customer

...

... def __add__(self, other):

... new_cart = self.cart.copy()

... new_cart.append(other)

... return Order(new_cart, self.customer)

...

... def __radd__(self, other):

... new_cart = self.cart.copy()

... new_cart.insert(0, other)

... return Order(new_cart, self.customer)

...

>>> order = Order(['banana', 'apple'], 'Real Python')

>>> order = order + 'orange'

>>> order.cart

['banana', 'apple', 'orange']

>>> order = 'mango' + order

>>> order.cart

['mango', 'banana', 'apple', 'orange']

一个完整的例子

要掌握以上关键点,最好自己实现一个包含以上所有操作的自定义类。下面我们自己来造一个轮子,实现一个复数的自定义类 CustomComplex。这个类的实例将支持各种内置函数和运算符,行为表现上将非常类似于 Python 自带的复数类:

from math import hypot, atan, sin, cos

class CustomComplex:

def __init__(self, real, imag):

self.real = real

self.imag = imag

构造函数只支持一种调用方式,即 CustomComplex(a, b)。它通过位置参数来表示复数的实部和虚部。我们在这个类中定义两个方法 conjugate() 和 argz()。它们分别提供复数共轭和复数的辐角:

def conjugate(self):

return self.__class__(self.real, -self.imag)

def argz(self):

return atan(self.imag / self.real)

注意: class 并不是特定方法,只是默认的一个类属性通常指向类本身。这里我们跟调用构造函数一样来对它进行调用,换句话来说其实调用的就是 CustomComplex(real, imag)。这样调用是为了防止今后更改类名时要再次重构代码。

下一步,我们配置 abs() 返回复数的模:

def __abs__(self):

return hypot(self.real, self.imag)

我们遵循官方建议的 __repr__() 和 __str__() 两者差异,用第一个来实现可解析的字符串输出,用第二个来实现“更美观”的输出。 __repr__() 方法简单地返回 CustomComplex(a, b) 字符串,这样我们在调用 eval() 重建对象时很方便。 __str__() 方法用来返回带括号的复数输出形式,比例 (a+bj):

def __repr__(self):

return f"{self.__class__.__name__}({self.real}, {self.imag})"

def __str__(self):

return f"({self.real}{self.imag:+}j)"

数学上讲,我们可以进行两个复数相加或者将一个实数和复数相加。我们定义 + 运算符来实现这个功能。方法将会检测运算符右侧的类型,如果是一个整数或者浮点数,它将只增加实部(因为任意实数都可以看做是 a+0j),当类型是复数时,它会同时更改实部和虚部:

def __add__(self, other):

if isinstance(other, float) or isinstance(other, int):

real_part = self.real + other

imag_part = self.imag

if isinstance(other, CustomComplex):

real_part = self.real + other.real

imag_part = self.imag + other.imag

return self.__class__(real_part, imag_part)

同样,我们定义 - 和 * 运算符的行为:

def __sub__(self, other):

if isinstance(other, float) or isinstance(other, int):

real_part = self.real - other

imag_part = self.imag

if isinstance(other, CustomComplex):

real_part = self.real - other.real

imag_part = self.imag - other.imag

return self.__class__(real_part, imag_part)

def __mul__(self, other):

if isinstance(other, int) or isinstance(other, float):

real_part = self.real * other

imag_part = self.imag * other

if isinstance(other, CustomComplex):

real_part = (self.real * other.real) - (self.imag * other.imag)

imag_part = (self.real * other.imag) + (self.imag * other.real)

return self.__class__(real_part, imag_part)

因为加法和乘法可以交换操作数,我们可以在反向运算符 __radd__() 和 __rmul__() 方法中这样调用 __add__() 和 __mul__() 。此外,减法运算的操作数是不可以交换的,所以需要 __rsub__() 方法的行为:

def __radd__(self, other):

return self.__add__(other)

def __rmul__(self, other):

return self.__mul__(other)

def __rsub__(self, other):

# x - y != y - x

if isinstance(other, float) or isinstance(other, int):

real_part = other - self.real

imag_part = -self.imag

return self.__class__(real_part, imag_part)注意:你也许发现我们并没有增添一个构造函数来处理 CustomComplex 实例。因为这种情形下,两个操作数都是类的实例, __rsub__() 方法并不负责处理实际的运算,仅仅是调用 __sub__() 方法来处理。这是一个微妙但是很重要的细节。

下面,我们来看看另外两个运算符:== 和 != 。这两个分别对应的特定方法是 __eq__() 和 __ne__()。如果两个复数的实部和虚部都相同则两者是相等的。只要两个部分任意一个不相等两者就不相等:

def __eq__(self, other):

# Note: generally, floats should not be compared directly

# due to floating-point precision

return (self.real == other.real) and (self.imag == other.imag)

def __ne__(self, other):

return (self.real != other.real) or (self.imag != other.imag)

注意:浮点指南这篇文章讨论了浮点数比较和浮点精度的问题,它涉及到一些浮点数直接比较的一些注意事项,这与我们在这里要处理的情况有点类似。

同样,我们也可以通过简单的公式来提供复数的幂运算。我们通过定义 __pow__() 特定方法来设置内置函数 pow() 和 ** 运算符的行为:

def __pow__(self, other):

r_raised = abs(self) ** other

argz_multiplied = self.argz() * other

real_part = round(r_raised * cos(argz_multiplied))

imag_part = round(r_raised * sin(argz_multiplied))

return self.__class__(real_part, imag_part)注意:认真看看方法的定义。我们调用 abs() 来获取复数的模。所以,我们一旦为特定功能函数或运算符定义好了特定方法,它就可以被用于此类的其他方法中。

我们创建这个类的两个实例,一个拥有正的虚部,一个拥有负的虚部:

>>> a = CustomComplex(1, 2)

>>> b = CustomComplex(3, -4)

字符串表示:

>>> a

CustomComplex(1, 2)

>>> b

CustomComplex(3, -4)

>>> print(a)

(1+2j)

>>> print(b)

(3-4j)

使用 eval() 和 repr()重建对象

>>> b_copy = eval(repr(b))

>>> type(b_copy), b_copy.real, b_copy.imag

(__main__.CustomComplex, 3, -4)

加减乘法:

>>> a + b

CustomComplex(4, -2)

>>> a - b

CustomComplex(-2, 6)

>>> a + 5

CustomComplex(6, 2)

>>> 3 - a

CustomComplex(2, -2)

>>> a * 6

CustomComplex(6, 12)

>>> a * (-6)

CustomComplex(-6, -12)

相等和不等检测:

>>> a == CustomComplex(1, 2)

True

>>> a == b

False

>>> a != b

True

>>> a != CustomComplex(1, 2)

False

最后,复数的幂运算:

>>> a ** 2

CustomComplex(-3, 4)

>>> b ** 5

CustomComplex(-237, 3116)

正如你所见,我们自定义类的对象外观及行为上类似于内置的对象而且很 Pythonic。

回顾总结

本教程中,你学习了 Python 数据模型,以及如何通过数据模型来构建 Pythonic 的类。学习了改变 len(), abs(), str(), bool() 等内置函数的行为,以及改变 +, -, *, **, 等内置运算符的行为。

如果想要进一步地了解数据模型、函数和运算符重载,请参考以下资源:Python 文档,数据模型的第 3.3 节,特定方法名

流畅的 Python(Fluent Python by Luciano Ramalho)

Python 技巧(Python Tricks)

完整示例代码:

from math import hypot, atan, sin, cos

class CustomComplex():

"""A class to represent a complex number, a+bj.Attributes:real - int, representing the real partimag - int, representing the imaginary partImplements the following:* Addition with a complex number or a real number using `+`* Multiplication with a complex number or a real number using `*`* Subtraction of a complex number or a real number using `-`* Calculation of absolute value using `abs`* Raise complex number to a power using `**`* Nice string representation using `__repr__`* Nice user-end viewing using `__str__`Notes:* The constructor has been intentionally kept simple* It is configured to support one kind of call:CustomComplex(a, b)* Error handling was avoided to keep things simple"""

def __init__(self, real, imag):

"""Initializes a complex number, setting real and imag partArguments:real: Number, real part of the complex numberimag: Number, imaginary part of the complex number"""

self.real = real

self.imag = imag

def conjugate(self):

"""Returns the complex conjugate of a complex numberReturn:CustomComplex instance"""

return CustomComplex(self.real, -self.imag)

def argz(self):

"""Returns the argument of a complex numberThe argument is given by:atan(imag_part/real_part)Return:float"""

return atan(self.imag / self.real)

def __abs__(self):

"""Returns the modulus of a complex numberReturn:float"""

return hypot(self.real, self.imag)

def __repr__(self):

"""Returns str representation of an instance of theclass. Can be used with eval() to get anotherinstance of the classReturn:str"""

return f"CustomComplex({self.real}, {self.imag})"

def __str__(self):

"""Returns user-friendly str representation of an instanceof the classReturn:str"""

return f"({self.real}{self.imag:+}j)"

def __add__(self, other):

"""Returns the addition of a complex number withint, float or another complex numberReturn:CustomComplex instance"""

if isinstance(other, float) or isinstance(other, int):

real_part = self.real + other

imag_part = self.imag

if isinstance(other, CustomComplex):

real_part = self.real + other.real

imag_part = self.imag + other.imag

return CustomComplex(real_part, imag_part)

def __sub__(self, other):

"""Returns the subtration from a complex number ofint, float or another complex numberReturn:CustomComplex instance"""

if isinstance(other, float) or isinstance(other, int):

real_part = self.real - other

imag_part = self.imag

if isinstance(other, CustomComplex):

real_part = self.real - other.real

imag_part = self.imag - other.imag

return CustomComplex(real_part, imag_part)

def __mul__(self, other):

"""Returns the multiplication of a complex number withint, float or another complex numberReturn:CustomComplex instance"""

if isinstance(other, int) or isinstance(other, float):

real_part = self.real * other

imag_part = self.imag * other

if isinstance(other, CustomComplex):

real_part = (self.real * other.real) - (self.imag * other.imag)

imag_part = (self.real * other.imag) + (self.imag * other.real)

return CustomComplex(real_part, imag_part)

def __radd__(self, other):

"""Same as __add__; allows 1 + CustomComplex('x+yj')x + y == y + x"""

pass

def __rmul__(self, other):

"""Same as __mul__; allows 2 * CustomComplex('x+yj')x * y == y * x"""

pass

def __rsub__(self, other):

"""Returns the subtraction of a complex number fromint or floatx - y != y - xSubtration of another complex number is not handled by __rsub__Instead, __sub__ handles it since both sides are instances ofthis classReturn:CustomComplex instance"""

if isinstance(other, float) or isinstance(other, int):

real_part = other - self.real

imag_part = -self.imag

return CustomComplex(real_part, imag_part)

def __eq__(self, other):

"""Checks equality of two complex numbersTwo complex numbers are equal when:* Their real parts are equal AND* Their imaginary parts are equalReturn:bool"""

# note: comparing floats directly is not a good idea in general

# due to floating-point precision

return (self.real == other.real) and (self.imag == other.imag)

def __ne__(self, other):

"""Checks inequality of two complex numbersTwo complex numbers are unequal when:* Their real parts are unequal OR* Their imaginary parts are unequalReturn:bool"""

return (self.real != other.real) or (self.imag != other.imag)

def __pow__(self, other):

"""Raises a complex number to a powerFormula:z**n = (r**n)*[cos(n*agrz) + sin(n*argz)j], wherez = complex numbern = powerr = absolute value of zargz = argument of zReturn:CustomComplex instance"""

r_raised = abs(self) ** other

argz_multiplied = self.argz() * other

real_part = round(r_raised * cos(argz_multiplied))

imag_part = round(r_raised * sin(argz_multiplied))

return CustomComplex(real_part, imag_part)

python自定义函数如何命名_Python 自定义类之函数和运算符重载相关推荐

  1. python给内置函数重命名_python – 以Pandas Groupby函数重命名列名

    1).我有一个以下示例数据集: >>> df ID Region count 0 100 Asia 2 1 101 Europe 3 2 102 US 1 3 103 Africa ...

  2. python数字类型及运算_Python数据类型之数字(Numbers)和运算符

    # Numbers(数字)类型分类 # 1.整数 int # 2.浮点数 float # 3.复数 complex # 整型:通常被称为整数,可以是正整数或负整数,不携带小数点:Python3中整型是 ...

  3. python计算复数的辐角_Python 自定义类中的函数和运算符重载

    如果你曾在字符串(str)对象上进行过 + 或 * 运算,你一定注意到它跟整数或浮点数对象的行为差异: >>> # 加法 >>> 1 + 2 3 >>& ...

  4. python函数的命名_python函数命名

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 命名空间的生命周期名称空间的生命周期 内置名称空间:(最长)只要 python解 ...

  5. python巩固函数和模块_Python学习教程6:函数,模块和类的使用

    Python函数 定义 函数是组织好的,可重复利用的,用来实现单一,或相关联功能的代码片段. 函数能提高应用的模块行,使代码逻辑更加的条理清晰. 内建函数 内建函数是Python中自带的,可供用户重复 ...

  6. python视图函数是什么_python项目Django(视图函数)

    一 Django的视图函数view 一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应. 响应可以是一张网页的HTML内容,一个重定向,一个404错 ...

  7. python全局变量赋值报错_python全局变量、回调函数

    1.python全局变量相关概念及使用 来自菜鸟教程上的例子: http://www.runoob.com/python3/python3-function.html 一.python入参需要注意地方 ...

  8. python中匿名函数的作用_Python 中的匿名函数,你会用吗

    原标题:Python 中的匿名函数,你会用吗 概念 我们从一个例子引入. 这里有一个元素为非空字符串的列表,按字符串最后一个字母将列表进行排序.如果原列表是 ['abc', 'g', 'def'],则 ...

  9. python函数参数学习_python学习笔记-11.函数参数和返回值进阶

    1. 函数参数和返回值的作用 函数根据有没有参数以及有没有返回值,可以相互组合,共有4种形式: 无参数,无返回值 无参数,有返回值 有参数,无返回值 有参数,有返回值 定义函数时,是否接收参数,或者是 ...

  10. python查找文件并重命名_python复制文件并重命名

    标签:多个   file   请求   重命名   XML   基本   path   main   复制文件 def copy_files(path,newpath): #定义函数名称 old_na ...

最新文章

  1. Opencv头文件记要~
  2. HyperlinkButton——WP8控件学习
  3. Array.from()
  4. FPGA笔录(2)-触发器与锁存器原理
  5. 如何分析request download状态一直处于running的问题
  6. Linux 复习重点目录
  7. 网站大流量高并发访问的处理解决办法
  8. dj电商-应用整合在一起,不完整版
  9. 如何保证http传输安全性
  10. 深入理解PHP之数组(遍历顺序)
  11. 华为小米 OPPO 们联合起来才不是为了打倒微信!
  12. Tuxera Ntfs for mac内核扩展批准不了怎么办 手动批准mac内核扩展
  13. 合并报表软件系统推荐
  14. Matlab编写摩斯代码,以摩斯电码为例为 Arduino 编写库
  15. 网页上的在线打印如何下载成本地PDF格式(人工亲测)
  16. 怎么用计算机计算t分布的概率,MATLAB如何使用tpdf函数计算T分布的概率密度
  17. 3dmax打开错误html,Windows安装3dmax软件失败提示错误三种解决办法
  18. 写入iCloud在模拟器和真机上失败的解决办法
  19. 算法图解-狄克斯特拉算法
  20. javascript检测各种浏览器型号和版本、检测是否支持flash并显示版本

热门文章

  1. 这个时代再也难出现贵子
  2. SQL“多字段模糊匹配关键字查询”
  3. 老婆生病了,后果很严重!
  4. [Android] View控件显示隐藏动画效果
  5. python怎么画小海龟_python画图之“小海龟”turtle
  6. centos-8搭建k8s并简单使用pv、pvc
  7. python列表的嵌套_Python 展开多层嵌套的列表
  8. cookie的相关概念及原理
  9. android向DDR读写数据,解决刷机回安卓时提示:Romcode/初始化DDR/读取初始化结果/USB...的问题...
  10. 华为手机 图标消失_华为手机升级EMUI 10后解决Google Play“消失”教程