我需要所选属性的数据的只读视图(而不是副本)。我知道这可以通过描述符/或属性来解决,但到目前为止我还不知道如何解决。
如果有更好的方法/模式来解决这个问题,我很乐意学习。
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
我知道这可以通过描述符/或属性来解决,但到目前为止我还不知道如何解决。
这是不正确的,实际上。
描述符仅在类上找到时起作用,而在实例上不起作用(属性是描述符的一种类型,因此此处没有区别)。因为您的视图将属性定义为实例数据,所以您无法为这些属性生成属性并将其粘贴到View
实例上。所以setattr(self, a, property(lambda: getattr(data, a)))
不行,不行。
这不是用描述符解决的问题。坚持__getattr__
执行查找,以及__setattr__
防止向视图添加属性的相应方法:
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
我在这里做了一些修改。属性存储为一组,因此可以测试名称是否是构成视图的属性的一部分,我们可以高效地进行操作。我还实施__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__
在类上定义的保留为挂钩点,并且在这两种方法中的任何一个用于绑定它们的属性对象的生成都比此处所需的间接性更多。
如果要创建许多这些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'
因此您可能仍要保留它。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句