关于运算符重载的原理问题 求大神 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
uswood
V2EX    Python

关于运算符重载的原理问题 求大神

  •  
  •   uswood 2020-08-28 14:30:23 +08:00 3017 次点击
    这是一个创建于 1894 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前学到运算符重载的部分,书中内容只做了例子,但关于原理有三点疑问弄不明白:

    代码部分:

    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 本身封装好了的,运算符重载的用处只是在为了让用户定义的类拥有“+”的运算,顶多让你自定义在“+”的基础上还做些什么。这样理解对不对, 关于运算符重载能举比较实际点的用处吗?

    23 条回复    2020-08-28 22:03:04 +08:00
    no1xsyzy
        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 有大用。
    pigspy
        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
    Trim21
        3
    Trim21  
       2020-08-28 14:50:51 +08:00 via Android
    好像只定义__len__和 get item 也能迭代,之前看过一眼有这种说法,说错了不要打我()
    InkStone
        4
    InkStone  
       2020-08-28 14:50:56 +08:00
    1. 函数声明有规则,这是跟 Python 的约定,一定要这么写。不然运行时 Python 调用时会报错。每种运算符都不一样。但具体实现无所谓,甚至不一定要实现+这个功能。

    2. 为了让自定义类更有扩展性,用起来方便。迭代和下标引用不是一回事。迭代实现__iter__,下标引用实现__iter__。pass 不行,必须得实现。
    3.不对。python 没有为非基础类型封装+这个操作,更不要说你自己的类型。 最后一个问题的回答同问题二。
    no1xsyzy
        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
    知道下它影响了什么就好了。
    xiri
        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
    ipwx
        7
    ipwx  
       2020-08-28 14:53:25 +08:00
    楼主是不是从 C++ 过来的。。

    pass 不是 = default,而是“啥也不干的占位符”。
    xiaolinjia
        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)
    uswood
        9
    uswood  
    OP
       2020-08-28 15:17:11 +08:00
    我的天,别的论坛都死气沉沉的,没想到收到这么多回复~ 谢谢大神 我先看看 有问题再提 @no1xsyzy @pigspy @InkStone @Trim21 @ipwx @xiaolinjia @xiri @
    uswood
        10
    uswood  
    OP
       2020-08-28 15:20:02 +08:00
    @ipwx 哈哈 没有没有 我就是偷懒不想写函数了
    imn1
        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, '')

    不过不好玩,也很少用
    ……
    uswood
        12
    uswood  
    OP
       2020-08-28 15:29:05 +08:00
    @xiri @xiaolinjia @no1xsyzy @pigspy @InkStone 明白了很形象,深入一点说,是不是意味着 1 、形参是什么随便定,只满足内部自己编写的处理流程就行,比如我要定义+,就需要 2 个数字 /字符串,我要切片,就要在__getitem__中调用 slice 内置函数-------所以运算符重载只是给自己的类添加处理模式,而不是重新定义“+”这个运算符的意义,只不过你在自己类的实例上写“+”的时候,python 会调用你的方法去处理而已~

    误会了,还以为是自己要怎么去定义 python 的加法、索引。。
    no1xsyzy
        13
    no1xsyzy  
       2020-08-28 15:33:00 +08:00
    @uswood #9 其实是因为你的问题太简单大家都能回答(
    #12 对,其实就是一种 “约定 > 配置”。
    uswood
        14
    uswood  
    OP
       2020-08-28 15:33:43 +08:00
    @uswood 不过之所以这么误会是因为类的继承关系,以为在自己的类中写上__add__等于覆盖了 python 内置的类方法,但是没想到自己定义的类压根就不是内置类型的子类,压根不会去继承。。
    princelai
        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__,其他的方法除了第一种没办法实现,后两种都是可以的
    Ricardoo
        16
    Ricardoo  
       2020-08-28 17:44:18 +08:00
    这个时候我就安利一本书了--《流畅的 python 》,查看第十一章 接口:从协议到抽象基类和第十三章 正确重载运算符
    zhaofq
        17
    zhaofq  
       2020-08-28 18:59:26 +08:00
    @Ricardoo 同样安利,这个非常适合你现在的情况
    volvo007
        18
    volvo007  
       2020-08-28 19:04:29 +08:00
    @Ricardoo 我很怀疑 LZ 就是这本书没看懂才来问的
    uswood
        19
    uswood  
    OP
       2020-08-28 20:59:10 +08:00
    @princelai 明白了谢谢~ 我看的这本书翻译非常晦涩,还以为它是说替代了系统的加法运算
    uswood
        20
    uswood  
    OP
       2020-08-28 21:00:19 +08:00
    @Ricardoo 哈哈哈 我竟然发现书架上有 谢谢安利 ,等学深入了再去看,感觉学完基础先学着做点实际的会比较维持兴趣
    uswood
        21
    uswood  
    OP
       2020-08-28 21:00:38 +08:00
    @zhaofq 收到安利~
    uswood
        22
    uswood  
    OP
       2020-08-28 21:01:28 +08:00
    @volvo007 不不不,我看的是 python 学习手册,这翻译简直了。。
    Hsinyao
        23
    Hsinyao  
       2020-08-28 22:03:04 +08:00 via iPhone
    看到帖子标题我就知道该来安利流畅的 python 了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5616 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 01:38 PVG 09:38 LAX 17:38 JFK 20:38
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86