Python 3.6 接受泛型的函数的类型提示 class 类型和相同泛型类型的实例类型

Python 3.6 type hinting for a function accepting generic class type and instance type of the same generic type

我有一个具有以下签名的函数:

def wait_for_namespaced_objects_condition(
    obj_type: Type[NamespacedAPIObject],
    obj_condition_fun: Callable[[NamespacedAPIObject], bool],
) -> List[NamespacedAPIObject]:
...

此处重要的部分是 NamespacedAPIObject 参数。此函数将 obj_type 作为类型规范,然后创建该类型 (class) 的对象(实例)。然后将该类型的一些其他对象添加到列表中,然后使用 obj_condition_fun 过滤并作为类型 List[NamespacedAPIObject]. This works fine and also evaluates OK with mypy`.

的结果返回

现在,我想让这个函数成为通用函数,这样就可以在 NamespacedAPIObject 的地方使用它的任何子类型。我的尝试是这样做的:

T = TypeVar("T", bound=NamespacedAPIObject)

def wait_for_namespaced_objects_condition(
    obj_type: Type[T],
    obj_condition_fun: Callable[[T], bool],
) -> List[T]:

但是 Type[T]TypeVar,所以这不是正确的方法。问题是:obj_type 参数的类型应该是什么才能使它起作用?我试过Generic[T],这对我来说似乎是最合理的,但它不起作用。如果我把 obj_type: T 放在那里,mypy 会评估这个 OK,但对我来说似乎是错误的。在我看来,这意味着 obj_type 是 class 的一个实例,它是 NamespacedAPIObject 的子类型,而我想说的是“T 是一个 class 变量表示 NamespacedAPIObject 的子类型。如何解决这个问题?

[编辑] 更好地解释一下(见下面的评论)为什么 Type[T] 对我不起作用:在我的例子中,T 的所有子类型都实现 class 方法 objects(),但是当我尝试写我的代码是这样的:

obj_type.objects()

mypy returns:

pytest_helm_charts/utils.py:36: error: "Type[T]" has no attribute "objects"

为什么 Type[T] 不是正确的选择?在我看来,它与 examples:

之一非常相似

However using Type[] and a type variable with an upper bound we can do much better:

U = TypeVar('U', bound=User)
def new_user(user_class: Type[U]) -> U:
    ...

Now when we call new_user() with a specific subclass of User a type checker will infer the correct type of the result:

joe = new_user(BasicUser)  # Inferred type is BasicUser

如果使用classmethod

from typing import Callable, Type, List, TypeVar

T = TypeVar('T', bound=Base)

class Base:
    @classmethod
    def objects(cls: Type[T]) -> List[T]:
        ...
    
    def run(self):
        ...

class Derived(Base):
    def run(self):
        ...


def foo(d: Derived) -> bool:
    return True


def wait_for_namespaced_objects_condition(
    obj_type: Type[T],
    obj_condition_fun: Callable[[T], bool],
) -> List[T]:
    a = obj_type.objects()
    return a
    
    
wait_for_namespaced_objects_condition(Derived, foo)

But Type[T] is TypeVar, so it's not the way to go.

不,您走在正确的轨道上 - TypeVar 绝对是正确的选择。这里的问题在于 pykube.objects.APIObject class 被包装在 mypy 尚无法处理的装饰器中。为 pykube.objects 添加类型存根将解决该问题。创建目录 _typeshed/pykube 并为 pykube 添加最小类型存根:

  • _typeshed/pykube/__init__.pyi:

    from typing import Any
    
    def __getattr__(name: str) -> Any: ...  # incomplete
    
  • _typeshed/pykube/objects.pyi:

    from typing import Any, ClassVar, Optional
    from pykube.query import Query
    
    def __getattr__(name: str) -> Any: ...  # incomplete
    
    class ObjectManager:
        def __getattr__(self, name: str) -> Any: ...  # incomplete
        def __call__(self, api: Any, namespace: Optional[Any] = None) -> Query: ...
    
    class APIObject:
        objects: ClassVar[ObjectManager]
        def __getattr__(self, name: str) -> Any: ...  # incomplete
    
    class NamespacedAPIObject(APIObject): ...
    

现在运行

$ MYPYPATH=_typeshed mypy pytest_helm_charts/

正确解析 obj_type.objects

T = TypeVar('T', bound=NamespacedAPIObject)


def wait_for_namespaced_objects_condition(obj_type: Type[T]) -> List[T]:
    reveal_type(obj_type.objects)

输出:

pytest_helm_charts/utils.py:29: note: Revealed type is 'pykube.objects.ObjectManager'