实现一个 Jedi 可以理解的惰性 属性

Implementing a lazy property that Jedi can understand

我使用 lazy-属性 库 (https://pypi.org/project/lazy-property/) 有一段时间了。它工作得很好,但在我的编辑器中,这些惰性属性不提供任何自动完成。

我的设置是 Atom,使用 ide-python 包 (https://github.com/lgeiger/ide-python), which is driven by the python-language-server (https://github.com/palantir/python-language-server), which uses Jedi (https://github.com/davidhalter/jedi) 进行自动完成。

基本上,这个问题应该可以在任何基于 Jedi 的自动完成上下文中重现。

我一直想知道是否有一种方法可以重写 lazy-属性 中的代码(可能使用类型提示等等),这样 Jedi 就可以理解来自 lazy-属性-装饰方法应该和没有装饰器一样。

这个装饰器的实现其实超级简单,基本上就是:

class lazy_property(property):
    def __init__(self, method, fget=None, fset=None, fdel=None, doc=None):

        self.method = method
        self.cache_name = f"_{self.method.__name__}"

        doc = doc or method.__doc__
        super().__init__(fget=fget, fset=fset, fdel=fdel, doc=doc)

        update_wrapper(self, method)

    def __get__(self, instance, owner):

        if instance is None:
            return self

        if hasattr(instance, self.cache_name):
            result = getattr(instance, self.cache_name)
        else:
            if self.fget is not None:
                result = self.fget(instance)
            else:
                result = self.method(instance)

            setattr(instance, self.cache_name, result)

        return result

有没有人知道 ide我如何重构这个 class 以使 Jedi 明白它应该假定装饰器不会改变 [=31] 的类型=] 值?

非常感谢任何帮助,干杯。

你的问题是 Jedi 无法真正对付 super().__init__(fget=fget, fset=fset, fdel=fdel, doc=doc)。它并不真正理解你在那里做什么。如果您在该行之后写 self.fget = fget,Jedi 会理解您的示例。

为了更深入地挖掘,Jedi 尝试了解分支。在您的情况下,它认为 result = self.fget(instance) 的结果是它必须推断的结果,因为 self.fget is not None 推断为 True。它推断为 True,因为 propertyself.fget 在类型化存根中定义为 def fget(self) -> Any: ...,这基本上意味着它肯定存在。因此,这些存根基本上与您的情况下的实际情况有所不同(它们可能在那里略有错误)。

不过也请注意,写入缓存属性是以前做过的事情。最好的实现之一是 @cached_property in Django,因此您也可以从那里复制:

https://docs.djangoproject.com/en/2.2/_modules/django/utils/functional/#cached_property