我想创建一个包含多个设置的数据结构,这些设置将用于计算硬件设备的寄存器值。为了避免重新配置硬件设备的所有设置,我希望让数据结构内的每个变量记住它是否已更改。然后我会调用所有变量来查看哪些变量被更改,然后只写入连接的寄存器。 我可以创建一个类来记住其内部存储值是否发生任何更改,但是我在返回和重置
has_changed
变量方面遇到了困难。这是由于 __get__
函数的重载禁止在类内使用其他函数。
在简化的示例中,我创建了一个名为
Table
的类(其中应包含变量,例如:height
、width
、length
,...)当前的实现具有类 TrackedValidatedInteger
,它检查是否更改有效。
我希望变量属性 has_changed
可以从类内部获取和重置 Table
。
class TrackedValidatedInteger():
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
self.has_changed = False
self.value = None
def __get__(self, obj, objecttype=None):
return self.value
def __set__(self, obj, value):
if self.validate_set(value):
self.value = value
self.has_changed = True
return 1
return 0
def get_has_changed(self):
return self.has_changed
def reset_has_changed(self):
self.has_changed = False
def validate_set(self, value):
if self.min_value:
if self.min_value > value:
print("Value should be between " + str(self.min_value) + " and " + str(self.max_value))
return 0
if self.max_value:
if self.max_value < value:
print("Value should be between " + str(self.min_value) + " and " + str(self.max_value))
return 0
return 1
class Table():
length = TrackedValidatedInteger(min_value=0, max_value=3)
height = TrackedValidatedInteger(min_value=0, max_value=6)
width = TrackedValidatedInteger(min_value=0, max_value=7)
def __init__(self, length=0, height=0, width=0):
self.length = length
self.height = height
self.width = width
def reset_has_changed_1(self):
self.length.has_changed = False
self.height.has_changed = False
self.width.has_changed = False
def reset_has_changed_2(self):
self.length.reset_has_changed()
self.height.reset_has_changed()
self.width.reset_has_changed()
p = Table()
p.length = 3 # will set the variable
p.length = 9 # will not set the variable
# p.length.get_has_changed() # This does not work as the p.length will call __get__ resulting in an integer which does not have get_has_changed()
# p.reset_has_changed_1() # This does not work for the same reason
# p.reset_has_changed_2() # This does not work for the same reason
我发现的问题是,每当我尝试访问
__get__
类的任何其他部分时,都会自动调用 TrackedValidatedInteger
函数。我可以通过其他方式访问其他变量和函数吗?如果有任何关于如何以另一种方式实现相同结果的建议,我将很高兴听到。我个人希望保留变量的简单设置(p.length = 3
),如果不可能的话可以更改。
任何帮助将不胜感激。
我会将验证和更改跟踪逻辑提升到表中,如下所示:
class Table:
_fields = {}
def __init__(self, **kwargs):
self._data = {}
self._changed = set()
for name, value in kwargs.items():
setattr(self, name, value)
self._changed.clear() # do not consider the initial values as changed
def __repr__(self):
return f"<{self.__class__.__name__} {self._data}>"
def __getattr__(self, item):
return self._data[item]
def __setattr__(self, key, value):
rules = self._fields.get(key)
if rules is not None: # existing field
if "min_value" in rules and value < rules["min_value"]:
raise ValueError(f'{key} should be greater than {rules["min_value"]}')
if "max_value" in rules and value > rules["max_value"]:
raise ValueError(f'{key} should be less than {rules["max_value"]}')
self._data[key] = value
self._changed.add(key)
return
if not key.startswith("_"):
raise AttributeError(f"Cannot set attribute {key}")
super().__setattr__(key, value)
def reset_has_changed(self):
self._changed.clear()
@property
def changed(self):
return tuple(self._changed)
class HardwareTable(Table):
_fields = {
"reg1": {"min_value": 0, "max_value": 100},
"reg2": {"min_value": 0, "max_value": 100},
}
p = HardwareTable(reg1=30)
print(f"{p=} / {p.changed=}")
p.reg2 = 13
print(f"{p=} / {p.changed=}")
打印出来
p=<HardwareTable {'reg1': 30}> / p.changed=()
p=<HardwareTable {'reg1': 30, 'reg2': 13}> / p.changed=('reg2',)
有很多问题需要解决:
TrackedValidatedInteger
的实例耦合到Table
,而不是它的实例,
所以你不应该将 value
存储在 TrackedValidatedInteger
实例上。 (尝试创建两个 Table
实例,
在一个实例上设置这些属性之一,然后看到它反映在另一个实例上。)min_value
或 max_value
是 0
,则不进行验证。请使用 if self.min_value is not None
来代替。None
,您将打印类似 Value should be between None and 7
class TrackedValidatedInteger:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
def __get__(self, obj, objecttype=None):
return getattr(obj, self.private_name)
def __set__(self, obj, value):
self.validate(value)
obj.has_changed.add(self.public_name)
setattr(obj, self.private_name, value)
def __set_name__(self, owner, name):
self.public_name = name
self.private_name = '_' + name
def validate(self, value):
if (self.min_value is not None and value < self.min_value or
self.max_value is not None and value > self.max_value):
if self.min_value is None:
comparison = f'at most {self.max_value}'
elif self.max_value is None:
comparison = f'at least {self.min_value}'
else:
comparison = f'between {self.min_value} and {self.max_value}'
raise ValueError(f'{self.public_name} should be {comparison}')
class Table():
length = TrackedValidatedInteger(min_value=0, max_value=3)
height = TrackedValidatedInteger(min_value=0, max_value=6)
width = TrackedValidatedInteger(min_value=0, max_value=7)
def __init__(self, length=0, height=0, width=0):
# this needs to happen first:
self.reset_has_changed()
self.length = length
self.height = height
self.width = width
def reset_has_changed(self):
self.has_changed = set()
p = Table()
p.length = 3 # will set the variable
try:
p.length = 9 # will not set the variable
except ValueError as e:
print(e)
print('length' in p.has_changed)
__set_name__
方法知道其绑定的属性名称这一事实,并使用它来维护目标对象上的属性:
class TrackedValidatedInteger:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
self.has_changed = False
self.value = None
def __set_name__(self, obj, name):
self.name = name
setattr(obj, f"{self.name}_changed", False)
def __get__(self, obj, objecttype=None):
return self.value
def __set__(self, obj, value):
if (self.min_value is not None and value < self.min_value) or (
self.max_value is not None and value > self.max_value
):
raise ValueError(
f"{value} must be >= {self.min_value} and <= {self.max_value}"
)
self.value = value
setattr(obj, f"{self.name}_changed", True)
鉴于上述实现,我们可以创建一个类
Example
,如下所示:
class Example:
v1 = TrackedValidatedInteger()
v2 = TrackedValidatedInteger()
然后观察以下行为:
>>> e = Example()
>>> e.v1_changed
False
>>> e.v1 = 42
>>> e.v1_changed
True
>>> e.v2_changed
False
>>> e.v2 = 0
>>> e.v2_changed
True
您可以维护已更改属性的
<name>_changed
,而不是维护每个属性 set
变量:
class TrackedValidatedInteger:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
self.has_changed = False
self.value = None
def __set_name__(self, obj, name):
self.name = name
if not hasattr(obj, "_changed_attributes"):
setattr(obj, "_changed_attributes", set())
def __get__(self, obj, objecttype=None):
return self.value
def __set__(self, obj, value):
if (self.min_value is not None and value < self.min_value) or (
self.max_value is not None and value > self.max_value
):
raise ValueError(
f"{value} must be >= {self.min_value} and <= {self.max_value}"
)
self.value = value
obj._changed_attributes.add(self.name)
在这种情况下,我们得到:
>>> e = Example()
>>> e._changed_attributes
set()
>>> e.v1 = 1
>>> e._changed_attributes
{'v1'}
>>> e.v2 = 1
>>> e._changed_attributes
{'v1', 'v2'}
这很好,因为如果您需要记录所有更改的值,您可以迭代
e._changed_attributes
。