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

    事情是这样的:

    假设我有这样一个需求:

    给定三点: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


    明白了吗。

    另外,基础都没打好就别玩儿这些高级特性了。你如果对面向对象编程有 [基础] 的了解就不会问出这个问题。
    xxapp
        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.x - 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   ·   我们的愿景   ·   实用小工具   ·   3176 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 35ms · UTC 10:54 · PVG 18:54 · LAX 03:54 · JFK 06:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.