目前学到运算符重载的部分,书中内容只做了例子,但关于原理有三点疑问弄不明白:
代码部分:
class Test:
def __init__(self, val):
self.val = val
def __add__(self, other):
print('add', self.val, other)
return self.val + other
问题部分:
1 、定义运算符重载的时候,它的编写是有规则的吗? 例如定义__add__,一定是接受 2 个参数(self, other),一定是 return self.val + other 吗?是否每种运算符重载都有特定的编写规则?还比如__getitem__重载,书中例子写的是:
class Test:
def __getitem__(self, i):
return self.data[1]
2 、为什么要这么写? 如果只是想让自定义的类拥有迭代的功能难道只写 def __getitem(self): pass 就可以了吗?
3 、在定义的__add__里面,也只是写了“+”这个符号,我的理解是:怎么“加”的是 python 本身封装好了的,运算符重载的用处只是在为了让用户定义的类拥有“+”的运算,顶多让你自定义在“+”的基础上还做些什么。这样理解对不对, 关于运算符重载能举比较实际点的用处吗?
1
no1xsyzy 2020-08-28 14:48:26 +08:00
1 、 这类魔法方法一般有固定的参数数量,但你只要保证 sub.__add__(obj) 能够正常调用即可,换句话说,允许采用 def __add__(self, other, magic_number=42): 这样的做法
2 、 __getitem__(self, key) 定义的是 self[key] 的行为。 想要能被迭代,需要定义 __iter__(self) 3 、 __add__ 是改变 + 或者说 operator.add 的实现 你写下了 a + b,实际上 Python 做了这样的工作: a 是否定义了 __add__,如是,则调用 a.__add__(b),如果没有引发异常或者返回 NotImplemented 则 a+b 的结果就是其返回值 否则检查 b.__radd__,剩下同上 如果仍然不成功,则引发 TypeError 。 SymPy 有大用。 |
2
pigspy 2020-08-28 14:49:22 +08:00
比如说一个二维向量
class Vec(object): x:int y:int 那么要表示两个向量相加,就可以使用运算符重载为 def __add__(self, other:Vec): this.x -= other.x this.y -= other.y |
3
Trim21 2020-08-28 14:50:51 +08:00 via Android
好像只定义__len__和 get item 也能迭代,之前看过一眼有这种说法,说错了不要打我()
|
4
InkStone 2020-08-28 14:50:56 +08:00
1. 函数声明有规则,这是跟 Python 的约定,一定要这么写。不然运行时 Python 调用时会报错。每种运算符都不一样。但具体实现无所谓,甚至不一定要实现+这个功能。
2. 为了让自定义类更有扩展性,用起来方便。迭代和下标引用不是一回事。迭代实现__iter__,下标引用实现__iter__。pass 不行,必须得实现。 3.不对。python 没有为非基础类型封装+这个操作,更不要说你自己的类型。 最后一个问题的回答同问题二。 |
5
no1xsyzy 2020-08-28 14:53:07 +08:00
就历史上来说,早期的 Python 非常的多的值都不是对象。
比如 tuple 之前就不是对象,所以无法运用对象语法 tup.len ,所以构成了 len(tup) 的做法。 然而后来发现要自己 OO 的话,必须要能够重载这些 builtin 函数,包括 len() str() repr() iter() 等 所以采用 “给类定义魔法方法” 的方式来改变 builtin 函数的行为。 所谓 “魔法”,在 Python 里就是说 “一般不需要使用,该使用的时候你自会知道该使用” 比如 metaclass 知道下它影响了什么就好了。 |
6
xiri 2020-08-28 14:53:17 +08:00
1. 不一定,没有固定的规则,这个是随你自己定的,但是你这样定义了之后使用改运算符的时候就要满足你自己的要求
2. 定义 __getitem__ 是为了能用索引访问元素啊(通过类似 p[i]这种形式取值),拥有迭代功能只是附加的 3. 重载“+”号你就能用该符号做计算,里面具体怎么算是没有要求的,你完全可以重载“+”号,然后把它的功能写成相减、相乘都可以 用处( v2 的回复不支持 md,并且会丢失缩进,将就看吧): 比如你定义了一个类用来表述复数: class ComplexNum: def __init__(self, a,b): self.a = a self.b = b 这时候考虑两个复数间的运算(以相加为例): x=ComplexNum(1,2) //表示复数 1+2i y=ComplexNum(2,3) //复数 2+3i x+y? 由于这个类是你自己定义的,python 不知道相加的时候该怎么处理,你这时候直接加就会报错 你完全可以单独取出每个对象中的 a 、b 值自己计算,但更好的办法是重载“+”运算符: class ComplexNum: def __init__(self, a,b): self.a = a self.b = b def __add__(self, other): return ComplexNum(self.a+other.a , self.b+other.b) 这样就可以直接使用“+”运算符计算了 z=x+y print(z.a) //3 print(z.b) //5 |
7
ipwx 2020-08-28 14:53:25 +08:00
楼主是不是从 C++ 过来的。。
pass 不是 = default,而是“啥也不干的占位符”。 |
8
xiaolinjia 2020-08-28 14:54:58 +08:00
问题 1: 有一定的规则,至少接收 2 个参数。一个表示 + 左边的对象,一个表示 + 右边的对象。
当然你 __add__ 方法签名里也可以加多个参数,不过跟我们一般的期望不符合。也不一定是 return self.val + other,只所以 return 这个,是因为我们想得到他们的和的结果。 问题 2:拥有迭代功能的背后是这个类被 iter 调用后可以返回一个迭代器。只要实现了 __iter__ 就可以,如果没 __iter__,可以退一步实现 __getitem__,也可以迭代。这时,会从 __getitem__(0) 开始迭代。 问题 3:+ 号对应每个类型的 __add__ 方法,比如 int 类型,他是 py 已经定义了 __add__ 方法,那他就可以 + 。像这例子的话,如果 self.val 传入了一个自定义的类型,且你这个类型没有定义 __add__ 方法。 你可以跑这个看看。 class Test: def __init__(self, val): self.val = val def __add__(self, other, c=1): print(1 + c) def __getitem__(self, item): pass if __name__ == '__main__': t = Test(1) t1 = Test(2) t + t1 for i in t: print(i) |
9
uswood OP |
11
imn1 2020-08-28 15:27:01 +08:00
list_a * list_b
我自定义了这个,实际就是 itertools.product 不过不敢动 buildin,是另设一个类 同样我还定义了 str_a - str_b,实际就是 str_a.replace(str_b, '') 不过不好玩,也很少用 …… |
12
uswood OP @xiri @xiaolinjia @no1xsyzy @pigspy @InkStone 明白了很形象,深入一点说,是不是意味着 1 、形参是什么随便定,只满足内部自己编写的处理流程就行,比如我要定义+,就需要 2 个数字 /字符串,我要切片,就要在__getitem__中调用 slice 内置函数-------所以运算符重载只是给自己的类添加处理模式,而不是重新定义“+”这个运算符的意义,只不过你在自己类的实例上写“+”的时候,python 会调用你的方法去处理而已~
误会了,还以为是自己要怎么去定义 python 的加法、索引。。 |
14
uswood OP @uswood 不过之所以这么误会是因为类的继承关系,以为在自己的类中写上__add__等于覆盖了 python 内置的类方法,但是没想到自己定义的类压根就不是内置类型的子类,压根不会去继承。。
|
15
princelai 2020-08-28 16:40:40 +08:00
所有原始类型都继承自 object,你可以看看 dir(object)
里面根本没有__add__,只不过恰好“+”被解释为__add__,而加法是一个双目中缀符号,必须有两个参数 例如我定义一个类并实例化 ``` class Test: def __init__(self, a): self.a = a def __add__(self, other): return self.a + other t1 = Test(2) ``` 那么下面三种是一个意思 ``` t1+5 t1.__add__(5) Test.__add__(t1,5) ``` +号只是解释器帮你解释为__add__,其他的方法除了第一种没办法实现,后两种都是可以的 |
16
Ricardoo 2020-08-28 17:44:18 +08:00
这个时候我就安利一本书了--《流畅的 python 》,查看第十一章 接口:从协议到抽象基类和第十三章 正确重载运算符
|
20
uswood OP @Ricardoo 哈哈哈 我竟然发现书架上有 谢谢安利 ,等学深入了再去看,感觉学完基础先学着做点实际的会比较维持兴趣
|
23
Hsinyao 2020-08-28 22:03:04 +08:00 via iPhone
看到帖子标题我就知道该来安利流畅的 python 了
|