【问题标题】:Type-annotate but skip class attributes in an auto_attribs class类型注释但跳过 auto_attribs 类中的类属性
【发布时间】:2021-09-13 10:12:21
【问题描述】:

我目前正在使用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()

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

@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的冻结,我需要通过后门潜入Child.parent属性。以前我把它作为自定义工厂的类属性,但后来它变成了attrs-attribute,它真的不应该是。例如。我不希望parent 出现在散列、比较、str 表示等方面。

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

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

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

【问题讨论】:

    标签: python python-typing python-attrs


    【解决方案1】:

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

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

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

    附:你现在可以写@attr.frozenauto_attribs 默认为 True。见https://www.attrs.org/en/stable/api.html#next-generation-apis

    【讨论】:

    • object.__setattr__object.__dict__ = ... 在我看来是两种不同的表达方式。两者都有一个问题,即静态类型检查器不会检测到存在类型为Parent.parent() 属性。 _deepclone 转换器的目的正是在构造时清除父引用。因为我可能会给Child 已经附加到另一个根节点的对象。我实际上已经使用evolve 进行克隆 - 示例中没有包含这个:-)
    • 那你只想要parent: Parent = attr.field(init=False)
    • __setattr__ 有使用插槽类的好处 btw :))
    • 我以前就是这样。但这使得 parent 出现在 str 表示中,比较,asdict(),... - 这让我觉得可能有更好的解决方案。唔。也许应该只关闭 auto_attribs 并明确标记实际 attr.ibs ....
    • 嗯,不能帮助 asdict(你可以阻止在那里),但其余的你也可以设置为 False。
    【解决方案2】:

    我确定的解决方案:(感谢@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 可以完美理解。

    【讨论】:

      猜你喜欢
      • 2020-01-18
      • 1970-01-01
      • 2019-03-08
      • 1970-01-01
      • 2014-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-24
      相关资源
      最近更新 更多