关于 Python3 重载运算符 与 引用 - 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
johnnyleaf
V2EX    Python

关于 Python3 重载运算符 与 引用

  •  
  •   johnnyleaf 2020-05-28 14:49:10 +08:00 4527 次点击
    这是一个创建于 2029 天前的主题,其中的信息可能已经有所发展或是发生改变。

    事情是这样的:

    假设我有这样一个需求:

    给定三点:A 、B 、C ;其中 A 与 B 坐标已知,C 点坐标 = A 点 + B 点, 当 A 点坐标发生变化时,C 点也应该发生变化。

    我的代码如下

    首先我定义一个 Point 类

    class Point: def __init__(self, coordinate:tuple): self.x = coordinate[0] self.y = coordinate[1] def __repr__(self): return "(%.2f, %.2f)" % (self.x, self.y) def __add__(self, other:Point): if isinstance(other, Point): x = self.x + other.x y = self.y + other.y return Point(coordinate=(x,y)) 

    然后定义 A 与 B 的坐标 以及 C 的关系:

    A = Point((1,1)) B = Point((4,1)) C = A + B 

    此时 C 点的坐标应该为:(5,2)

    但我进行如下操作后

    A.x=5 

    C 点的坐标并没有发生变化...原因我知道,因为__add__给 C 返回的是一个新的 Point 对象。

    我想请教各位!假如说 代码里我只能用重载运算符来完成 +-*/ 我该如何能让 C 点的坐标根据 A 点 /B 点的坐标变化而变化呢?

    38 条回复    2020-05-29 04:12:51 +08:00
    sarvatathagata
        1
    sarvatathagata  
       2020-05-28 14:57:30 +08:00
    不知道 python 有什么机制做到这个。。。然而这不就是 Qt 中的信号 /槽吗
    aijam
        2
    aijam  
       2020-05-28 14:58:19 +08:00
    C 里存 A, B 的 reference
    dayeye2006199
        3
    dayeye2006199  
       2020-05-28 15:00:03 +08:00
    难不成要这样

    ```python
    class SumOfPoints:
    def __init__(self, left, right):
    self.left = left
    self.right = right

    @property
    def x(self):
    return self.left.x + self.right.x

    @property
    def y(self):
    return self.left.y + self.right.y

    class Point:

    def __init__(self, coordinate:tuple):
    self.x = coordinate[0]
    self.y = coordinate[1]

    def __repr__(self):
    return "(%.2f, %.2f)" % (self.x, self.y)

    def __add__(self, other):
    if isinstance(other, Point):
    x = self.x + other.x
    y = self.y + other.y
    return SumOfPoints(self, other)
    ```

    ```python
    A = Point((1,1))
    B = Point((4,1))
    C = A + B

    assert C.x==5

    A.x = 5

    assert C.x==9
    ```
    johnnyleaf
        4
    johnnyleaf  
    OP
       2020-05-28 15:00:17 +08:00
    @sarvatathagata 是的,其实也可以根据 监听者模式来设计这个模块,但是对于监听者模式本身我掌握的并不牢固。所以在想有没有其他的办法可以实现同样的功能。
    johnnyleaf
        5
    johnnyleaf  
    OP
       2020-05-28 15:05:12 +08:00
    @dayeye2006199 哈哈~感谢感谢 这个办法不错,但是 假如现实需求略复杂 可能出现
    > A=(1, 1) B = (4, 1), C=(5, 3), D = C - B + A
    以上代码,可以实现一个平行四边形,如果按照您的思路,我可能需要建立 SumOfPoints 与 Point 之间的__add__ 与 __ sub__ 等操作。
    1iuh
        6
    1iuh  
       2020-05-28 15:06:34 +08:00
    你用重载运算符来做这事特别不合理, 你这样写感觉就类似 a = 1, b =1 c = a +b 这时 c = 2, 然后 a = a +2, c 的值就变成了 3 。

    实现是可以实现, 把 Point 的类扩展一下,Point A 和 Point B 相加时,在新的对象里放入 Point A 和 Point B 。新对象的 x = self._pointa.x + self.pointb.x
    johnnyleaf
        7
    johnnyleaf  
    OP
       2020-05-28 15:09:32 +08:00
    @1iuh 其实我有同感,我也是在思路探索阶段 ,,那您有更加合理的思路吗或者解决方案吗?
    gwy15
        8
    gwy15  
       2020-05-28 15:15:36 +08:00
    imn1
        9
    imn1  
       2020-05-28 15:17:12 +08:00
    你需要一个 setter 来自动改变属性,参考手册__setter__相关,或 @setter
    问题是,你这里实际上是两种 Point,一种 xy 属性不变,另一种 xy 来自外部参数计算结果,不应该定义为同一个 Class
    可以考类 C 继承 Point,并加入自动设置的__setter__类方法
    至于运算符,好像不是需求重点,用了运算符,反而不能自动了(运算符意味着进行运算才触发)
    junnplus
        10
    junnplus  
       2020-05-28 15:22:01 +08:00
    可以用 memoryview 试试,每个 point 保存两个 memoryview 对象,以及操作符
    InkStone
        11
    InkStone  
       2020-05-28 15:22:02 +08:00   1
    其实我很想说,你需要的是函数式编程和 monad……
    aijam
        12
    aijam  
       2020-05-28 15:25:39 +08:00
    johnnyleaf
        13
    johnnyleaf  
    OP
       2020-05-28 15:28:18 +08:00
    @aijam 感谢 感谢
    johnnyleaf
        14
    johnnyleaf  
    OP
       2020-05-28 15:29:51 +08:00
    @InkStone 关于函数式和 monad 我本身掌握不佳,熟悉后再做尝试。
    nightwitch
        15
    nightwitch  
       2020-05-28 15:30:06 +08:00
    这种做法是严重的设计缺陷, 有可能你的变量引用链条很长, 几百行代码之前的对某个变量的更改,可能引起正在工作的代码的更改,还是悄无声息的更改. ,找 bug 能找到崩溃.

    帮你实现了一份差不多感觉的
    https://paste.ubuntu.com/p/NQ43VjrSYg/
    johnnyleaf
        16
    johnnyleaf  
    OP
       2020-05-28 15:30:19 +08:00
    @junnplus 感谢 感谢,已经解决了
    johnnyleaf
        17
    johnnyleaf  
    OP
       2020-05-28 15:32:46 +08:00
    @nightwitch 感谢你,你说的问题确实存在,所以我放弃了,决定定义两个类来存储这两种不同的结构。
    hustlibraco
        18
    hustlibraco  
       2020-05-28 15:35:11 +08:00
    @gwy15 请教一下,Vec2DF 起什么作用啊?
    johnnyleaf
        19
    johnnyleaf  
    OP
       2020-05-28 15:36:08 +08:00
    @imn1 是的,我了解了有关 @setter 的东西。我决定用两个类来分别处理这两种不同的对象。感谢你
    gwy15
        20
    gwy15  
       2020-05-28 15:42:15 +08:00
    @hustlibraco type alias,带个 F 表示是个 callable,免得嵌套太长不好看
    no1xsyzy
        21
    no1xsyzy  
       2020-05-28 15:47:11 +08:00
    @johnnyleaf #5 > 我可能需要建立 SumOfPoints 与 Point 之间的__add__ 与 __ sub__ 等操作。
    可以做一个 ABCPoint,然后把 SumOfPoints 和 Point 都注册进去,之后判断就是 isinstance(other, ABCPoint)
    或者让 SumOfPoints 继承 Point
    neoblackcap
        22
    neoblackcap  
       2020-05-28 15:47:59 +08:00
    不一样的类型是正确的,其实你需要的不是什么 Point,是一个 LazyPoint 或者一个公式,Excel 的公式用过吧。
    C 应该是代表 Add(A, B),需要用到值的时候再求值。完美满足你的要求。你自己控制一下类似的 Lazy 类对象的求值时机就好了。性能都可控
    no1xsyzy
        23
    no1xsyzy  
       2020-05-28 15:54:01 +08:00
    @aijam #12 调用被放大了,复杂度 O(2^n)
    johnnyleaf
        24
    johnnyleaf  
    OP
       2020-05-28 15:55:33 +08:00
    @neoblackcap 是的 我正在实现一个新类去实现你讲的类似功能。感谢
    jmc891205
        25
    jmc891205  
       2020-05-28 16:01:03 +08:00
    为什么会有这种需求。
    那当你有 100 万个点,修改 1 个点就会导致另外 99 万多个点的坐标都更新,这性能岂不是太差了。
    johnnyleaf
        26
    johnnyleaf  
    OP
       2020-05-28 16:05:19 +08:00
    @jmc891205 首先,目前确定的 业务中点的个数在 10 个之内。其次我决定尝试对更新做控制。不会让它自动更新的哈。
    ipwx
        27
    ipwx  
       2020-05-28 16:06:35 +08:00   1
    我觉得你需要的是 lazy evaluation 。通过占位符(比如 _1 + _2 )产生一个表达式对象,然后当你把真实的点倒进去的时候,给你把结果算出来。

    Point 这种对象,从语义上通常认为是不可变的常量。否则会让人 confuse 。
    Vegetable
        28
    Vegetable  
       2020-05-28 16:35:06 +08:00
    https://gist.github.com/luliangce/5c1c84392a51c02cb90b62e173d12ed0

    你这里涉及到一个问题,那就是一旦赋值一个 Point,马上就会破坏调用链,这就让代码变得非常怪异
    milkpuff
        29
    milkpuff  
       2020-05-28 17:22:20 +08:00
    运行一个 thread 跑死循环,判断变量是否改变,如果改变就重新计算??
    whileFalse
        30
    whileFalse  
       2020-05-28 19:04:45 +08:00
    你需要的是一个新类。

    class AddPoint(Point):
    def __init__(self, p1, p2):
    self.p1 = p1
    self.p2 = p2

    @property
    def x(self):
    return self.p1.x + self.p2.x


    明白了吗。

    另外,基础都没打好就别玩儿这些高级特性了。你如果对面向对象编程有 [基础] 的了解就不会问出这个问题。
    xapp
        31
    xxapp  
       2020-05-28 19:37:17 +08:00 via Android
    了解下响应式编程?
    GeruzoniAnsasu
        32
    GeruzoniAnsasu  
       2020-05-28 20:04:56 +08:00 via Android   1
    数学
    约束
    响应式



    我觉得即使你完全不会函数式编程也该现场入门一下。。

    每个点类存储一个变换函数,对于自变量来说,这个函数是设置时的闭包:

    a=A(1,2)

    a.x==lambda :1
    a.y==lambda :2

    对于因变量来说,这个函数是变换函数

    c=a+b

    c.x==lambda:a.x()+b.x()
    c.y==lambda:a.y()+b.y()


    当要取得 c 的值时调用 c.x()
    =>(lambda:a.x()+b.x())()
    =>(lambda:(lambda:1)()+(lambda:xb)())()
    =>(lambda:1+xb)()


    这不是比你递归通知约束对象计算新值要容易多了


    至于怎么实现,约束方法本身已经是实现方法了,品一品
    whileFalse
        33
    whileFalse  
       2020-05-28 20:14:32 +08:00
    @GeruzoniAnsasu 挺好,不过我估计 lz 还想给 c 赋值,然后反过来影响 a……
    hustlibraco
        34
    hustlibraco  
       2020-05-28 21:17:26 +08:00
    @gwy15 Union[Vec2D, Vec2DF]的定义主要是为了 Point 既能接受坐标数组作为初始化参数,也能接受函数作为初始化数据,因此后面可以传 Lamdba 表达式初始化,从而实现 lazy evaluation 的功能?
    noqwerty
        35
    noqwerty  
       2020-05-28 21:23:24 +08:00 via Android
    V 站现在能像楼主一样把自己需求说这么清楚的问题太少了
    jxie0755
        36
    jxie0755  
       2020-05-28 21:28:13 +08:00 via iPhone
    你什么时候需要用 C,就在用之前再调用当时的 A 和 B 嘛。
    lithbitren
        37
    lithbitren  
       2020-05-29 04:07:31 +08:00
    一个继承字典的类就能解决变量展示和修改问题了。


    class Point(dict):
    def __init__(self, x, y):
    self['x'] = x
    self['y'] = y

    def __getattr__(self, attr):
    return self[attr]() if callable(self[attr]) else self[attr]

    def __setattr__(self, attr, value):
    self[attr] = value

    def __add__(self, other):
    return Point(lambda: self.x + other.y, lambda: self.x + other.y)

    def __sub__(self, other):
    return Point(lambda: self.x - other.y, lambda: self. - other.y)

    def __str__(self):
    return f'Point({self.x:.2f}, {self.y:.2f})'

    if __name__ == "__main__":
    a = Point(1, 2)
    print('a', a) # Point(1.00, 2.00)
    print('a.x', a.x) # 1
    a.x = 3
    print('a', a) # Point(3.00, 2.00)
    print('a.x', a.x) # 3
    b = Point(0, 6)
    print('b', b) # Point(0.00, 6.00)
    c = a + b - Point(100, 100)
    print('c', c) # Point(-91.00, -91.00)
    b.y = 1000
    print('c', c) # Point(903.00, 903.00)
    lithbitren
        38
    lithbitren  
       2020-05-29 04:12:51 +08:00
    我 r,加减写错了,重写重写。。。


    class Point(dict):

    def __init__(self, x, y):
    self['x'] = x
    self['y'] = y

    def __getattr__(self, attr):
    return self[attr]() if callable(self[attr]) else self[attr]

    def __setattr__(self, attr, value):
    self[attr] = value

    def __add__(self, other):
    return Point(lambda: self.x + other.x, lambda: self.y + other.y)

    def __sub__(self, other):
    return Point(lambda: self.x - other.x, lambda: self.y - other.y)

    def __str__(self):
    return f'Point({self.x:.2f}, {self.y:.2f})'

    if __name__ == "__main__":
    a = Point(1, 2)
    print('a', a) # Point(1.00, 2.00)
    print('a.x', a.x) # 1
    a.x = 3
    print('a', a) # Point(3.00, 2.00)
    print('a.x', a.x) # 3
    b = Point(0, 6)
    print('b', b) # Point(0.00, 6.00)
    c = a + b - Point(100, 100)
    print('c', c) # Point(-97.00, -92.00)
    b.y = 1000
    print('c', c) # Point(-97.00, 902.00)
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     891 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 31ms UTC 22:00 PVG 06:00 LAX 14:00 JFK 17:00
    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