2

我目前正在使用attrs. 这是一个树状结构,我需要一个父反向链接用于某些目的。OTOH,我想使用冻结的类(有缓存的属性,它们依赖于只读状态)。

这意味着树必须自下而上,在父级之前的子级实例化。为了解决这个问题,我发明了一种特殊的 weakref 对象,它可以被惰性绑定一次。

class LateRef(Generic[T]):
    def __init__(self):
        self.ref: Optional[T] = None

    def set(self, obj: T):
        if self.ref is not None:
            raise RuntimeError('Reference can only be set once.')
        self.ref = weakref.ref(obj)

    def __call__(self) -> Optional[T]:
        if self.ref is None:
            return None
        return self.ref()

有自定义初始化代码在子级中创建一个未绑定的引用,并将它们绑定在父级中:

@attr.s(auto_attribs=True, frozen=True)
class Child:
    some_attribute:str = "whatever"
   
    def __attrs_post_init__(self):
        self.__dict__['parent'] = LateRef()

@attr.s(auto_attribs=True, frozen=True)
class Parent:
    children: List[Child] = attr.ib(converter=_deepclone) # off-screen :-)
    
    def __attrs_post_init__(self):
        for child in self.children:
            child.parent.set(self)

Child.parent由于child. _ 以前我把它作为一个自定义工厂的类属性,但后来它变成了一个-属性attrs,它真的不应该是。例如,我不希望parent在散列、比较、str 表示等中出现。

代码已经可以工作了,但是:如您所见,我想尽可能地做正确的事情并进行类型注释。直接parent放入__dict__意味着通常的工具不知道预期的类型。事实上,他们甚至可能会抱怨该parent属性根本不存在。

长话短说:有没有办法parent用类型注释声明为类属性,同时告诉attr.s跳过/忽略它?还是我忽略了另一种解决方案?

编辑:即如何使静态类型检查器识别parent属性的类型,而不使其成为attr.ib.

4

2 回答 2

1

我确定的解决方案:(感谢@hynek 的讨论和您的出色工作!)

@attr.frozen(slots=False)
class Child:
    some_attribute:str = "whatever"
   
    @functools.cached_property
    def parent(self) -> LateRef["Parent"]:
        return LateRef()

# Parent class stays the same

工作方式几乎相同(在首次访问时parent存储__dict__["parent"]),并且至少被 VSCode 完美理解。

于 2021-07-01T13:01:11.823 回答
1

如果您唯一关心的是绕过冻结类的冻结性,我建议采用attrs与其__init__方法相同的路线并object.__setattr__直接使用。attrs课程总是被冻结,所以我们也必须解决它。

可能不是最优雅的方式,但遗憾的是 Python 并不是真正为不变性而设计的,所以我们必须使用我们所拥有的。

也就是说,您的_deepclone转换器似乎表明您不需要/想要将原始对象添加到父对象?在这种情况下,attr.evolve()它是您的完美工具。

PS你@attr.frozen现在可以写了。auto_attribs默认为真。见https://www.attrs.org/en/stable/api.html#next-generation-apis

于 2021-07-01T11:16:26.250 回答