代码之家  ›  专栏  ›  技术社区  ›  Charles Duffy

当装饰器被包装为类方法时,类型提示丢失了吗?

  •  1
  • Charles Duffy  · 技术社区  · 10 月前

    考虑以下代码:

    from typing import Any, Callable, Coroutine
    
    class Cache[**P, R]:
        @classmethod
        def decorate(cls, **params):
            def decorator(f: Callable[P, Coroutine[Any, Any, R]]) -> Callable[P, Coroutine[Any, Any, R]]:
                return f # in the real world, we instantiate a Cache here
            return decorator
    
    @Cache.decorate()
    async def some_function(i: int) -> int:
        return i + 1
    
    cached_function = Cache.decorate()(some_function)
    

    如果我问pyright的类型 Cache.decorate 之前 这个 @classmethod 包装(检查单词 decorate 在上面的代码中),它返回:

    (method) def decorate(
        cls: type[Self@Cache[P@Cache, R@Cache]],
        **params: Unknown
    ) -> ((f: ((**P@Cache) -> (Coroutine[Any, Any, R@Cache])) -> ((**P@Cache) -> Coroutine[Any, Any, R@Cache]))
    

    在我看来,它似乎明白这一点 P (参数类型)和 R (返回类型)已正确插入。

    然而,如果我要求它反省 缓存.记录 在它被用作装饰器的上下文中,它返回:

    (method) def decorate(**params: Unknown) -> ((f: ((...) -> Coroutine[Any, Any, Unknown])) -> ((...) -> Coroutine[Any, Any, Unknown]))
    

    …也就是说,输入类型和输出类型之间的关系已经被完全抛弃了!

    1 回复  |  直到 10 月前
        1
  •  2
  •   InSync Christian C. Salvadó    10 月前

    decorator 取决于两个上下文类型变量: P R Cache 类,这些未知,但假设Pyright在调用时已知。

    然而,当 Cache.decorate() 被调用时,Pyright没有获得足够的信息来解析 P R (没有显式类型参数,也没有参数),因此这两个参数被解析为 Unknown .

    一个简单的解决方法是参数化 隐藏物 明确地:

    ( playground )

    @Cache[[int], int].decorate()
    async def some_function(i: int) -> int:
        return i + 1
    
    reveal_type(some_function)  # (int) -> Coroutine[Any, Any, int]
    

    然而,这并不能让你找到问题的根源,那就是你没有正确地指定你想要什么。

    隐藏物 是通用的 P R ,因此必须存在一种方法,使Pyright能够推断出与这些参数对应的类型。通常,这些是通过将参数传递给创建该类的函数来处理的,通常 __new__ / __init__ 和工厂 @classmethod s

    # Hypothetical usages
    Cache(return_value, *args, **kwargs)
    Cache.factory(return_value, *args, **kwargs)
    
    # This is also possible, as shown, but rarer
    Cache[[int], int]()
    

    decorate() 是一个工厂方法,但它不创建 隐藏物 靠自己。这需要一些争论,但这些并不影响装饰的类型 some_function() 也不是要创建的类型 隐藏物 .

    相反, 装饰() 旨在创建装饰器,这些装饰器自己创建以下对象的实例 隐藏物 . 您要参数化的是这些装饰器 ,因为他们是接受装饰功能并负责创作的人 隐藏物 s

    ( playground )

    class Cache[**P, R]:
    
        @classmethod
        def decorate(cls, **params: Any):
            def decorator[**P2, R2](f: Callable[P2, Coroutine[Any, Any, R2]]) -> ...:
                #        ^^^^^^^^^^
                return f
            
            return decorator
    
    reveal_type(Cache.decorate())
    # (f: (**P2@decorator) -> Coroutine[Any, Any, R2@decorator]) -> ((**P2@decorator) -> Coroutine[Any, Any, R2@decorator])
    
    reveal_type(some_function)
    # (i: int) -> Coroutine[Any, Any, int]