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 · 2443 次点击
    这是一个创建于 1334 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    代码部分:

    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   ·   我们的愿景   ·   实用小工具   ·   1145 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 22:51 · PVG 06:51 · LAX 15:51 · JFK 18:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.