我想要所选属性的数据的只读 View (而不是副本)。我知道这可以通过描述符/或属性来解决,但到目前为止我不知道如何解决。
如果有更好的方法/模式来解决这个问题,我很乐意了解。
class Data:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
class View:
def __init__(self, data, attributes):
self.attributes = attributes
self.data = data
for a in attributes:
#setattr(self, a, lambda: getattr(data, a))
setattr(self, a, property(lambda: getattr(data, a)))
#@property
#def b(self):
# return self.data.b
def __getattr__(self, item):
if item in self.attributes:
return getattr(self.data, item)
raise AttributeError("can't get attribute")
def test_view():
data = Data(1, 2, 3)
mydata = View(data, ['b', 'c']) # but not a!
assert mydata.b == 2
data.b = 9
assert mydata.b == 9
with pytest.raises(AttributeError, match="can't set attribute"):
mydata.b = 10
最佳答案
I understand that this is possible to solve with a descriptor / or property but so far I could not figure out how.
事实上,这是不正确的。
描述符仅在类上找到时才起作用,而不是在实例上(属性是描述符的一种类型,因此这里没有什么不同)。由于您的 View 将属性定义为实例数据,因此您无法为这些属性生成属性并将它们粘贴到您的View
实例上。所以 setattr(self, a, property(lambda: getattr(data, a)))
不起作用,不。
这不是描述符可以解决的问题。坚持使用 __getattr__
进行查找,并使用相应的 __setattr__
方法来防止向 View 添加属性:
class View:
def __init__(self, source, *attrs):
self._attrs = set(attrs)
self._source = source
def __getattr__(self, name):
if name in self._attrs:
return getattr(self._source, name)
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
def __setattr__(self, name, value):
# special case setting self._attrs, as it may not yet exist
if name == "_attrs" or name not in self._attrs:
return super().__setattr__(name, value)
raise AttributeError("can't set attribute")
def __dir__(self):
return self._attrs
我在这里做了一些修改。属性作为一个集合存储,因此可以有效地测试名称是否是形成 View 的属性的一部分。我还实现了__dir__
因此 dir(mydata)
返回可用的属性。
请注意,我还稍微更改了 API,使 View()
采用任意数量的参数来定义属性名称。这将使您的测试看起来像这样:
data = Data(1, 2, 3)
mydata = View(data, 'b', 'c') # but not a!
assert mydata.b == 2
data.b = 9
assert mydata.b == 9
with pytest.raises(AttributeError, match="can't set attribute"):
mydata.b = 10
事实上,即使使用元类,您也无法为此动态生成描述符,因为实例的属性查找不会引用 __getattribute__
或 __getattr__
> 在元类上(这是一种优化,请参阅 Special method lookup )。只有在类上定义的 __getattributes__
或 __getattr__
仍保留为 Hook 点,并且在这些方法中的任何一个中生成属性对象只是为了绑定(bind)它们,这比此处所需的更加间接。
如果您要创建大量这样的 View
对象,您可能确实想使用 __slots__
。如果您使用它,则不会为您的类创建 __dict__ 描述符,因此实例不具有任意属性。相反,您可以命名它可以具有的每个属性,Python 将为这些属性创建特殊的描述符并为其值保留空间。因为为实例提供任意属性所需的 __dict__ 字典比为已知数量的属性保留的固定空间占用更多的内存空间,所以这可以节省内存。它还具有副作用,您无法向 View
实例添加任何新属性,从而不需要 __setattr__
方法:
class View:
__slots__ = ("_attrs", "_source")
def __init__(self, source, *attrs):
self._attrs = set(attrs)
self._source = source
def __getattr__(self, name):
if name in self._attrs:
return getattr(self._source, name)
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
def __dir__(self):
return self._attrs
但是,如果没有 __setattr__
,当您尝试设置属性时抛出的 AttributeError
消息确实会有所改变:
>>> mydata.b = 10
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'View' object has no attribute 'b'
所以你可能还是想保留它。
关于python - 属性子集的只读数据 View (不是副本),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58804494/