python 当一个(或两个)对象更新时,保持两个对象的总和更新

iih3973s  于 2023-01-19  发布在  Python
关注(0)|答案(3)|浏览(111)

我想在Python中创建一个类,实现一个__add__对象方法,它允许对同一个类的两个对象求和,让我们调用这个类Indicator和这两个对象来对ind1ind2求和,Indicator对象只有一个属性elements,它是一个整数值字典。
我的__add__实现结合了两个对象的elements属性,最终用同一个键对这些值求和。

from __future__ import annotations
import copy
class Indicator:
    def __init__(self, elements={}):
        self.elements = elements

    def __add__(self, other: Indicator):
        new = copy.deepcopy(self)
        new.values =  {k: self.elements.get(k, 0) + other.elements.get(k, 0) for k in set(self.elements) | set(other.elements)}
        return new

ind1 = Indicator({1:1,2:2,3:3})
ind2 = Indicator({1:1,2:2})

new = ind1 + ind2
print('ind1: ',ind1.elements)
print('ind2: ',ind2.elements)
print(new.elements) # {1: 2, 2: 4, 3: 3}

我希望__add__返回一个对象,其elements属性随着求和中的一个或两个对象在代码流中的更新而更新。
例如,

ind1.elements[4] = 4
print(new.elements) # I would like this to be {1: 2, 2: 4, 3: 3, 4:4}

ind1.elements[1] = 3
print(new.elements) # I would like this to be {1: 4, 2: 4, 3: 3, 4:4}

我该怎么做呢?

    • 编辑**

首先,感谢所有发表评论/回答的用户,根据评论和回答中给出的建议,我想出了以下解决方案,想法是添加两个列表作为Indicator的属性:self.addsself.linked

  • 列表self.adds收集求和的加数。当__add__被调用时,它被填满。因此,在下面的例子中,ind1.adds is []ind2.adds is [],因为这两个对象不是从求和中产生的。相反,new.adds is [ind1,ind2]
  • 列表self.linked收集了所有在self更新时需要更新的对象。在下面的例子中,ind1.linked is [new]ind2.linked is [new]

我对这个解决方案并不完全满意。例如,如果我们对三个对象求和,然后修改其中一个,它就无法工作。我可以尝试修复代码,但我想知道我是否在做一些非常规的事情。有什么想法吗?代码如下所示

from __future__ import annotations
import copy

class Indicator:
    def __init__(self, elements=None):
        if elements is None:
            self._elements = {}
        else:
            self._elements = elements
        self.adds = []
        self.linked = []

    @property
    def elements(self):
        return self._elements

    @elements.setter
    def elements(self, value):
        self._elements = value
        for i in range(len(self.linked)):
            el = self.linked[i]
            el.update()

    def update(self):
        summation = self.adds[0]
        for obj in self.adds[1:]:
            summation = summation.__add__(obj)
        self._elements = summation.elements

    def __add__(self, other: Indicator):
        new = copy.deepcopy(self)
        self.linked.append(new)
        other.linked.append(new)
        new.adds = [self, other]
        new._elements = {k: self.elements.get(k, 0) + other.elements.get(k, 0) for k in
                         set(self.elements) | set(other.elements)}
        return new

ind1 = Indicator({1: 1, 2: 2, 3: 3})
ind2 = Indicator({1: 1, 2: 2})

new = ind1 + ind2
print('ind1: ', ind1.elements)
print('ind2: ', ind2.elements)
print(new.elements)  # {1: 2, 2: 4, 3: 3}
ind1.elements = {0: 0, 1: 3}
print('Updating ind1: ',new.elements == (ind1+ind2).elements)

ind2.elements = {0: 0, 7: 9}
print('Updating ind2: ',new.elements == (ind1+ind2).elements)
wqsoz72f

wqsoz72f1#

一个非常粗略的想法如何可以做到这一点(有很多工作留给你):

class dict_obs(dict):
    def __init__(self, observer, *args, **kwargs):
        self._observer = observer
        super().__init__(*args, **kwargs)

    def __setitem__(self, key, value):
        self._observer.notify(key)
        super().__setitem__(key, value)

class Indicator:
    def notify(self, key):
        print(f"{key} changed")


observer = Indicator()

d = dict_obs(observer, {1:1,2:2,3:3})

d[2] = 5

图纸

2 changed
bq9c1y66

bq9c1y662#

我将@MichaelButscher的建议和您的更新合并到一段代码中,添加了对象的名称,并将print保留在notify中以供演示;注意,notify中的diff参数意味着它只对数值有效(但是,既然您实现了__add__方法,那么这应该不是问题!)

class Indicator(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.derived=[]
        self.name = kwargs['name']

    def __setitem__(self, key, value):
        self.notify(key, value - self.get(key,0))
        super().__setitem__(key, value)
        
    def __add__(self, other: Indicator):
        new = Indicator({k:self.get(k,0)+other.get(k,0) for k in self.keys()|other.keys()},
                        name=self.name + '+' + other.name)
        self.derived.append(new)
        other.derived.append(new)
        return new
        
    def notify(self, key, diff):
        print(f'Indicator {self.name} : key {key} changed/added')
        for ind in self.derived:
            ind[key] = diff + ind.get(key,0)

示例

a = Indicator({1:1,2:2}, name = 'a')
b=Indicator({0:3,1:7}, name = 'b')
x=a+b
print(x)
# {0: 3, 1: 8, 2: 2, 'name': 'a+b'}

a[1]=10
# Indicator a : key 1 changed/added
# Indicator a+b : key 1 changed/added
print(x)
# {0: 3, 1: 17, 2: 2, 'name': 'a+b'}

y = x + b
a[10]=77
# Indicator a : key 10 changed/added
# Indicator a+b : key 10 changed/added
# Indicator a+b+b : key 10 changed/added
print(x)
# {0: 3, 1: 17, 2: 2, 'name': 'a+b', 10: 77}
print(y)
# {0: 6, 1: 24, 2: 2, 'name': 'a+b+b', 10: 77}
pqwbnv8z

pqwbnv8z3#

您的问题与此行有关:

new.values =  {k: self.elements.get(k, 0) + other.elements.get(k, 0) for k in set(self.elements) | set(other.elements)}

当你真的只需要密钥时,你正在创建k:v对的集合。下面是一个可行的解决方案:

class Indicator:
    def __init__(self, elements={}):
        self.elements = elements
    
    def __add__(self, other):
        keys = set(self.elements.keys()).union(set(other.elements.keys()))
        new = {}
        for k in keys:
            new[k] = self.elements.get(k,0) + other.elements.get(k,0)
        return Indicator(new)

相关问题