【Python】17.画屏

碧阑干外绣帘垂,

猩色折枝。

——韩翎《已凉》

本期文章8400

根据之前文章的后台统计数据推算

本期预计所需阅读时间50分钟

本系列文章已加入“维权骑士”(rightknights.com)的版权保护计划

本文原创内容受到保护

魔术方法(Magic Methods)操作符重载

魔术方法是一种特殊的方法,魔术方法的名字的开头和结尾都有两个下划线(__)。

说到两个下划线,很容易想到,到目前为止,我们所遇到的唯一一个魔术方法是__init__(详见上期文章),但还有其他好多个魔术方法。它们用于实现那些无法表示为普通方法的功能。

魔术方法的一个常见用途是运算符重载

运算符重载的意思是,在某个特定的类里重新定义某个已有的运算符,以允许在这个类的对象上使用+和*等运算符。

来看例子,用到的魔术方法是"__add__",它重写了"+"运算符。

class Vector2D:

def__init__(self, x, y):

self.x = x

self.y = y

def__add__(self, other):

return Vector2D(self.x + other.x, self.y + other.y)

first = Vector2D(5, 7)

second = Vector2D(3, 9)

result = first + second

print(result.x)

print(result.y)

运行结果:

可以看到,魔术方法__add__定义了这个类的对象的加法操作(即使用+运算符进行运算时的运算方式)。按常理来说,如果没有这个魔术方法,默认的+运算符是不能对Vector2D类的对象进行加法运算的,我们通过使用__add__魔术方法,定义出了特别针对于Vector2D类的加法运算,也就是上文说的“允许在这个类的对象上使用+运算符”。一旦定义了__add__方法,我们就可以使用+运算符运算这个类的任意两个对象。

也就是说,__add__方法允许为我们自己写的某个类的对象的+运算符定义自定义行为。在上面的例子中,__add__方法把+运算符左右两个对象的相应属性相加,并返回包含运算结果的新对象。

测试题17.1.

实例化出一个对象时,自动调用到的魔术方法是?

点击下方空白区域查看答案

__init__

要明白的是,只要方法名字两端各有两个下划线符号,这个方法就是魔术方法。

所以我们可以想到,既然每个魔术方法都是针对某个特定的运算符来进行重新定义,那么魔术方法的总数量必然是有限的,而且每个运算符对应特定名称的一个魔术方法,魔术方法的名称与运算符的对应关系是Python预先规定好了的。

以下是一些常见运算符所对应的魔术方法:

比如,表达式

x + y #加法运算过程

在执行运算时实际上是被转换为

x .__ add __(y) #调用对象x的一个方法的过程

常见的数据类型都已经预先包含了__add__等方法——如整数的__add__方法的功能是把数值相加并返回相加之和,字符串的__add__方法的功能是把字符串拼接起来并返回拼接后的字符串,这些都是我们使用+等运算符对它们执行运算时已经见过好多次的了。

但是如果上面表达式中的x所对应的类不包含__add__方法,并且x和y的类型不同,则系统会自动调用y .__ radd __(x),也就是y的__radd__方法。由此可见,如果想使用+运算符进行计算,那么参与运算的两个对象要么是同类对象并且这个类包含__add__方法,要么不是同类对象并且某一个对象所对应的类中有__radd__方法。

对于刚刚提到的所有魔术方法,都有相对应的"r方法",这些"r方法"比原来的魔术方法的名字多了一个r(位于开头的双下划线符号之后),用以包括使用相应运算符对不同类的对象进行运算时,所要调用的代码。来看例子:

class SpecialString:

def__init__(self, cont):

self.cont = cont

def__truediv__(self, other):

line = "=" * len(other.cont)

return "\n".join([self.cont, line, other.cont])

spam = SpecialString("spam")

hello = SpecialString("Hello world!")

print(spam / hello)

运行结果:

>>>

spam

============

Hello world!

>>>

在上面的例子中,我们为我们的SpecialString类定义了除法运算。当然,具体这个“除法运算”做什么,是由我们的__truediv__魔术方法所定义的。__truediv__方法只是向/运算符定义了一些代码,具体这些代码的功能还是不是“除法”,就不一定了。正如在上面的例子中,我们定义了SpecialString类的除法运算,但是我们的这个“除法运算”只是在两个字符串中间插入一些等号,并没有执行任何常规意义上的“除法”操作。

我们来分析一下上面示例中的代码,以更好地理解魔术方法的调用过程:

我们实例化出了两个SpecialString类的对象,即spam和hello。

spam作为SpecialString类的一个实例,通过__init__方法把参数字符串"spam"赋予给了这个对象的cont属性。同样地,SpecialString类实例化出的hello对象把参数字符串"Hello world!"作为其cont属性。实际上,通过__init__方法中的代码我们可以看出,SpecialString类的任何一个实例对象都只有一个属性,即cont属性。

回想一下上面的内容:“/”运算符对应的魔术方法是__truediv__。另外,因为spam和hello是同一个类的两个对象,所以,Python在运行这份代码的时候,把“print(spam / hello)”这行语句转换为了下面的代码:

spam.__truediv__(hello)

这个时候请注意,“.__ truediv__”左边的东西(即spam)被视为主体,而右边的东西(即hello)则被视为参数。所以,我们可以理解为这里实际上调用的是spam对象的__truediv__方法,传入这个方法的参数是hello对象。

然后,看spam对象的__truediv__方法:self.cont就是spam.cont,即spam对象的cont属性,也就是字符串“spam”。 同样,other.cont就是hello.cont,即hello对象的cont属性,也就是字符串“Hello world!”。

因此,在执行这个__truediv__方法时,return语句实际上是:

return "\n".join([spam.cont, line, hello.cont])

len(other.cont)实际上是len("Hello world!"),也就是字符串“Hello world!”中的字符数,即12,因此,在两个字符串之间一共插入12个等号"=",最终实现了上文给出的运行结果。

测试题17.2.

如果A类没有包含任何魔术方法,那么,下面的表达式:

A( )^B( )

实际上执行的操作是?

点击下方空白区域查看答案

B().__rxor__(A())

A不包含任何魔术方法,那么当然就不包含与^运算符相对应的魔术方法。所以这里调用到的是__xor__方法的"r方法",同时当然是B类的方法,所以总而言之这里调用到的是B类的__rxor__方法。

Python还提供了用于进行“比较”操作的魔术方法,如下:

注:

lt是lower than(小于)的缩写,

le是lower and equal(小于等于)的缩写,

eq是equal(等于)的缩写,

ne是not equal(不等于)的缩写,

gt是greater than(大于)的缩写,

gt是greater and equal(大于等于)的缩写。

上面各项用于“比较”的魔术方法,在常见类型(如整数和字符串)的定义中,返回值是布尔类型True或False,有关这个返回值,我们在之前的文章中说到判断运算符的时候(第4篇文章)提到过。

但是需要注意的是,与之前的魔术方法一样,在我们自己定义的类中,这些魔术方法不一定真的要实现“判断”的功能,这些魔术方法只是在运行到相应运算符的时候调用的方法而已,具体这些方法所实现的功能是什么,就由我们自己来定了。

当执行等于与不等的判断时,如果未定义__ne__(也就是未定义“不等”的判断方式),则把__eq__的相反判断方式视为“不等”的判断方式。

其他运算符之间没有这种关系。

来看例子:

class SpecialString:

def__init__(self, cont):

self.cont = cont

def__gt__(self, other):

for index in range(len(other.cont)+1):

result = other.cont[:index] + ">" + self.cont

result += ">" + other.cont[index:]

print(result)

spam = SpecialString("spam")

eggs = SpecialString("eggs")

spam > eggs

运行结果:

>>>

>spam>eggs

e>spam>ggs

eg>spam>gs

egg>spam>s

eggs>spam>

>>>

如上文所述,我们可以为重载的运算符定义任何自定义行为。比如在上面这个例子中,我们就并没有真的去判断是不是“spam大于eggs”,而是执行了一些其他的操作(不一步一步分析了,但是应该比较容易能看得懂)。

测试题17.3.

魔术方法__le__所对应的运算符是?

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180828G1VHMC00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券