wbzxz 发表于 2023-6-18 15:52:22

魔法方法 __add__和__radd__的用法的疑问

本帖最后由 wbzxz 于 2023-6-18 15:54 编辑

class S(str):
    def __add__(self, other):
      return len(self) * len(other)

class S1(str):
    def __add__(self, other):
      return NotImplemented

class S2(str):
    def __radd__(self, other):
      return len(self) + len(other)

class S3(str):
    def __iadd__(self, other):
      return len(self) + len(other)
class S4(str):
    pass
class S5():
    pass

s = S('FishC')
s1 = S1('Apple')
s2 = S2('Banana')
s3 = S3('Orange')
s4 = S4('Pear')
s5 = S5

print('s类型: ',type(s), s)
print('s1类型: ',type(s1), s1)
print('s2类型: ',type(s2), s2)
print('s3类型: ',type(s3), s3)
print('s4类型: ',type(s4), s4)
print('s5类型: ',type(s5), s5)
print('"nwu"类型: ',type('nwu'))
print('-' * 20)

print('s + s:', s + s)
print('s + s1:', s + s1)
print('s + s2:', s + s2)
print('s + s3:', s + s3)
print('s + s4:', s + s4)
# print('s + s5:', s + s5)
print("s + 'nwu':", s + 'nwu')
print('-' * 20)

# print('s1 + s:', s1 + s)
# print('s1 + s1:', s1 + s1)
print('s1 + s2:', s1 + s2)
# print('s1+s3:', s1 + s3)
# print('s1+s4:', s1 + s4)
# print('s1+s5:', s1 + s5)
# print("s1 + 'nwu':", s1 + 'nwu')
print('-' * 20)

print('s2 + s2:', s2 + s2)
print('s2 + s:', s2 + s)
print('s2 + s1:', s2 + s1)
print('s2 + s3:', s2 + s3)
print('s2 + s4:', s2 + s4)
# print('s2 + s5:', s2 + s5)
print("s2 + 'nwu':", s2 + 'nwu')
print('-' * 20)

print('s3 + s:', s3 + s)
print('s3 + s1:', s3 + s1)
print('s3 + s2:', s3 + s2)
print("s3 + 'nwu':", s3 + 'nwu')
print('s3 + s3:', s3 + s3)
print('s3 + s4:', s3 + s4)
# print('s3 + s5:', s3 + s5)
print('-' * 20)

print('s4 + s', s4 + s)
print('s4 + s1', s4 + s1)
print('s4 + s2', s4 + s2)
print('s4 + s3', s4 + s3)
print('s4 + s4', s4 + s4)
# print('s4 + s5', s4 + s5)
print('s4 + "nwu"', s4 + "nwu")
print('-' * 20)

# print('s5 + s', s5 + s)
# print('s5 + s1', s5 + s1)
# print('s5 + s2', s5 + s2)
# print('s5 + s3', s5 + s3)
# print('s5 + s4', s5 + s4)
# print('s5 + s5', s5 + s5)
# print('s5 + "nwu"', s5 + "nwu")

print('-' * 20)
print("'nwu' + s:", 'nwu' + s)
print("'nwu' + s1:", 'nwu' + s1)
print("'nwu' + s2:", 'nwu' + s2)
print("'nwu' + s3:", 'nwu' + s3)
print("'nwu' + s4:", 'nwu' + s4)
# print("'nwu' + s5:", 'nwu' + s5)
print('-' * 20)

运行结果如下:

s类型:<class '__main__.S'> FishC
s1类型:<class '__main__.S1'> Apple
s2类型:<class '__main__.S2'> Banana
s3类型:<class '__main__.S3'> Orange
s4类型:<class '__main__.S4'> Pear
s5类型:<class 'type'> <class '__main__.S5'>
"nwu"类型:<class 'str'>
--------------------
s + s: 25
s + s1: 25
s + s2: 30
s + s3: 30
s + s4: 20
s + 'nwu': 15
--------------------
s1 + s2: 11
--------------------
s2 + s2: BananaBanana
s2 + s: BananaFishC
s2 + s1: BananaApple
s2 + s3: BananaOrange
s2 + s4: BananaPear
s2 + 'nwu': Banananwu
--------------------
s3 + s: OrangeFishC
s3 + s1: OrangeApple
s3 + s2: 12
s3 + 'nwu': Orangenwu
s3 + s3: OrangeOrange
s3 + s4: OrangePear
--------------------
s4 + s PearFishC
s4 + s1 PearApple
s4 + s2 10
s4 + s3 PearOrange
s4 + s4 PearPear
s4 + "nwu" Pearnwu
--------------------
--------------------
'nwu' + s: nwuFishC
'nwu' + s1: nwuApple
'nwu' + s2: 9
'nwu' + s3: nwuOrange
'nwu' + s4: nwuPear
--------------------

上面是代码和运行结果,代码中注释部分是出错的情况,出错的情况分为两种,一个是“object of type 'type' has no len()”,一个是“unsupported operand type(s) for +: 'S1' and 'S'”

经过分析,个人认为是按照下面的顺序执行:

如果左边有__add__方法, 执行__add__方法;
如果在左边没有__add__方法, 或者调用 __add__ 方法返回NotImplemented,就在在右边找__radd__方法, 找到就执行__radd__方法,
如果左边没有找到__add__方法, 右边没有找到__radd__方法, 那就去左边父类找__add___方法, 找到就执行__add___方法(因为代码中父类是str, 肯定可以找到__add__方法)
如果左边有__add__方法,但是调用 __add__ 方法返回NotImplemented,右边没有找到__radd__方法, 则会报错

上面的顺序基本都能解释运行结果, 但是有一条不行, 就是'nwu' + s2: 9
个人认为, 'nwu'有__add__方法, 运行结果应该是 nwuBanana, 但是不是,不知道为什么, 请大家看看到底是哪里出错了呢?

歌者文明清理员 发表于 2023-6-18 15:53:01

本帖最后由 歌者文明清理员 于 2023-6-18 15:54 编辑

根据代码和运行结果,我们可以看出:


[*]当左边对象有__add__方法时,会直接执行左边的__add__方法;
[*]当左边对象没有__add__方法或者调用__add__方法返回NotImplemented时,会去右边对象找__radd__方法,如果找到就执行右边的__radd__方法;
[*]如果左边对象没有找到__add__方法,右边对象也没有找到__radd__方法,那么会去左边对象的父类中查找__add__方法,并执行该方法;
[*]如果左边对象有__add__方法,但是调用__add__方法返回了NotImplemented,右边对象也没有找到__radd__方法,那么会报错。

根据上面的规则,我们来分析一下'nwu' + s2这个表达式,左边是一个字符串类型的对象,有__add__方法,右边是一个自定义的字符串类型S2对象,没有__add__方法。因此,根据规则2,“对象没有__add__方法时,默认会在右边寻找__radd__方法,如果找到,就执行右边的__radd__方法”。而S2类中有一个__radd__方法,因此Python执行了S2类中的__radd__方法。

S2类中的__radd__方法返回的是左边的字符串长度加上右边的字符串长度,因此结果为9。

wbzxz 发表于 2023-6-18 15:57:25

歌者文明清理员 发表于 2023-6-18 15:53
根据代码和运行结果,我们可以看出:




请教一下

'nwu' 有 __add__方法, 按照规则2 , 左边对象有__add__方法, 应该调用__add__方法啊?

歌者文明清理员 发表于 2023-6-18 15:58:10

wbzxz 发表于 2023-6-18 15:57
请教一下

'nwu' 有 __add__方法, 按照规则2 , 左边对象有__add__方法, 应该调用__add__方法啊?

你说的没错,按照规则2,左边对象有__add__方法时,应该会调用左边对象的__add__方法。但是在这个例子中,我们发现结果却不是这样的。

经过进一步的分析,个人认为是由于'nwu'被视为一个基础类型,而不是一个自定义类型。因此,当对基础类型进行加法运算时,Python并没有去调用其__add__方法,而是直接将其视为一个常量,无法执行加法操作,因此返回了错误的结果。

wbzxz 发表于 2023-6-18 16:03:14

歌者文明清理员 发表于 2023-6-18 15:58
你说的没错,按照规则2,左边对象有__add__方法时,应该会调用左边对象的__add__方法。但是在这个例子中 ...

'nwu' + s: nwuFishC
'nwu' + s1: nwuApple
'nwu' + s2: 9
'nwu' + s3: nwuOrange
'nwu' + s4: nwuPear

如果按照您所说的, 'nwu'进行运算时, 没有调用其__add__方法, 那为什么s, s1, s3, s4运算时, 又调用了__add__方法了呢?

歌者文明清理员 发表于 2023-6-18 16:04:51

wbzxz 发表于 2023-6-18 16:03
'nwu' + s: nwuFishC
'nwu' + s1: nwuApple
'nwu' + s2: 9


当我们运行 'nwu' + s 时,由于 'nwu' 和 s 类型不同,因此 Python 会尝试将两个对象进行加法运算。在这种情况下,Python 首先会检查 'nwu' 对象是否有定义 __add__ 方法,如果有,则直接执行 __add__ 方法;如果没有,则会检查 s 对象是否有定义 __radd__ 方法,如果有,则执行 __radd__ 方法。由于 'nwu' 是字符串类型的基础数据类型,没有自身的 __add__ 方法,因此 Python 会检查 s 对象是否有定义 __radd__ 方法。

在我们的例子中,S、S1、S3、S4 都是 str 类型的子类,因此它们都继承了 str 类型的 __add__ 方法。而 S2 类定义了 __radd__ 方法,因此 s2 的加法运算会执行 __radd__ 方法。因此在这些例子中,Python 会执行 s、s1、s3、s4 对象的 __add__ 方法,而不是 __radd__ 方法。

isdkz 发表于 2023-6-18 16:05:29

'nwu' 的 __add__ 方法是 str.__add__,而 s2 的 __add__ 方法是 S2.__add__,

你不能只看方法名,还要看他们归属的对象

歌者文明清理员 发表于 2023-6-18 16:07:55

isdkz 发表于 2023-6-18 16:05
'nwu' 的 __add__ 方法是 str.__add__,而 s2 的 __add__ 方法是 S2.__add__,

你不能只看方法名,还要 ...

完了,你又醒了{:10_284:}

isdkz 发表于 2023-6-18 16:10:10

歌者文明清理员 发表于 2023-6-18 16:07
完了,你又醒了

额,我现在都不在乎这点,就是解答他的疑惑罢了,因为这问题他发两次了

歌者文明清理员 发表于 2023-6-18 16:17:54

isdkz 发表于 2023-6-18 16:10
额,我现在都不在乎这点,就是解答他的疑惑罢了,因为这问题他发两次了

https://blog.csdn.net/weixin_41923961/article/details/80183048
比netlify香多了

wbzxz 发表于 2023-6-18 16:34:10

歌者文明清理员 发表于 2023-6-18 16:04
当我们运行 'nwu' + s 时,由于 'nwu' 和 s 类型不同,因此 Python 会尝试将两个对象进行加法运算。在这 ...

如果找您的说法, 下面这段代码, 应该执行结果应该是8啊, 因为Python会执行s对象的__add__方法:

class S(str):
    def __add__(self, other):
      return len(self) * len(other)


s = S('FishC')
print('"nwu" + s: ', 'nwu' + s)

实际运行结果: "nwu" + s:nwuFishC

歌者文明清理员 发表于 2023-6-18 16:34:26

歌者文明清理员 发表于 2023-6-18 16:17
https://blog.csdn.net/weixin_41923961/article/details/80183048
比netlify香多了

主要是不用fq

歌者文明清理员 发表于 2023-6-18 23:22:39

wbzxz 发表于 2023-6-18 16:34
如果找您的说法, 下面这段代码, 应该执行结果应该是8啊, 因为Python会执行s对象的__add__方法:




你说的没错,根据规则1,当左边对象有__add__方法时,会直接执行左边的__add__方法。因此,在你提供的代码中,'nwu' 是字符串类型的基础数据类型,不具备 __add__ 方法,而 s 是 str 类型的子类,重写了 __add__ 方法,因此 Python 执行的应该是 s 的 __add__ 方法,计算出的结果应该是 8。我之前的回答可能造成了一些混淆,非常抱歉。

wbzxz 发表于 2023-6-19 13:41:07

歌者文明清理员 发表于 2023-6-18 23:22
你说的没错,根据规则1,当左边对象有__add__方法时,会直接执行左边的__add__方法。因此,在你提供的 ...

对啊,按照您的分析, "nwu" + s的计算结果应该是8,但是实际的运行结果并不是8,而是nwuFishC。

歌者文明清理员 发表于 2023-6-19 22:35:05

wbzxz 发表于 2023-6-19 13:41
对啊,按照您的分析, "nwu" + s的计算结果应该是8,但是实际的运行结果并不是8,而是nwuFishC。

非常抱歉,我的回答有误。你的代码确实符合规则1,当左边对象有 __add__ 方法时,会直接执行左边的 __add__ 方法。所以 s 对象的 __add__ 方法应该被调用,计算长度得到了 5,最终的结果是 "nwu" + s 等于 8。

我之前的回答有误,造成了混淆,非常抱歉给您带来困惑。

让我来解释一下你之前提供的代码。

首先,你定义了一个名为 S 的类,该类继承了 str 类,并重写了 __add__ 方法。在你调用 "nwu" + s 时,Python 会尝试将 "nwu" 和 s 进行加法运算。根据规则2,Python 会首先检查左边对象 str 是否有定义 __add__ 方法,由于字符串类型(也即 str 类型)是 Python 中基础的数据类型,因此它自己没有重写 __add__ 方法,但是作为父类却定义了该方法。因此,当 "nwu" 和 s 相加时,Python 会执行 str 类型中的 __add__ 方法,该方法会返回一个新的字符串,该字符串由 "nwu" 和 s 拼接起来组成(实际上就是字符串的拼接)。所以最终的结果是 "nwuFishC"。

希望这次回答能够帮助你理解你之前的代码。如果你还有任何问题,请随时问我。

wbzxz 发表于 2023-6-19 23:12:35

本帖最后由 wbzxz 于 2023-6-19 23:34 编辑

歌者文明清理员 发表于 2023-6-19 22:35
非常抱歉,我的回答有误。你的代码确实符合规则1,当左边对象有 __add__ 方法时,会直接执行左边的 __add ...

非常感谢您的指点,我大概理解了,是因为“字符串”的特殊性造成的,我把我理解的规则改了一下,加了第五条,这应该就对了吧, 请您指正。

1.当左边对象有__add__方法时,会直接执行左边的__add__方法;
2.当左边对象没有__add__方法或者调用__add__方法返回NotImplemented时,会去右边对象找__radd__方法,如果找到就执行右边的__radd__方法;
3.如果左边对象没有找到__add__方法,右边对象也没有找到__radd__方法,那么会去左边对象的父类中查找__add__方法,并执行该方法;
4.如果左边对象有__add__方法,但是调用__add__方法返回了NotImplemented,右边对象也没有找到__radd__方法,那么会报错;
5.字符串作为一种基础数据类型,可以认为本身是没有__add__方法的(但是作为父类定义了该方法),当字符串在操作符的左边时,按照第2条规则执行。
页: [1]
查看完整版本: 魔法方法 __add__和__radd__的用法的疑问