我想在Python中创建一个类,它实现一个__add__
对象方法,允许对同一类的两个对象求和。我们称这个类为Indicator
,这两个物体是ind1
和ind2
的和。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.adds
和self.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)
一个非常粗略的想法如何做到这一点(有很多工作留给你):
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
我将@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}
你的问题是这一行:
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)