python - 属性子集的只读数据 View (不是副本)

标签 python python-3.x

我想要所选属性的数据的只读 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/

相关文章:

python - 如何避免在这个特定的 python 代码片段中使用循环?

python - 如何将包含 7 位毫秒数的日期字符串转换为 Python 中的日期

python - 类对象的属性 `get_context_data`

Python Regex - 如何获取匹配的位置和值

python - 如何使用滚动/移动平均值插入 csv 文件中的数据? (Python)

Python 3 - 解码包含十六进制和 unicode 混合的字节

python - 有向图中的循环

python - 从字典更新多个标签

python - 在 PyQt5 中通过对象 #id 设置样式

python - 情绪分析中的否定处理