Python 键入:通过基本 class 参数缩小输出 subclass 实例的范围

Python typing: Narrowing the scope of output subclass instance by base class parameter

如果没有例子,我发现这很难描述。

from typing import List, Type, Optional, cast

class Base():
    kind = 'base'
class Child1(Base):
    kind = 'child1'
class Child2(Base):
    kind = 'child2'
class Child3(Base):
    kind = 'child3'

def find_in_list(lst: List[Base], SearchClass: Type[Base]): # Return Optional[??]
    for obj in lst: # type: Base
        if obj.kind == SearchClass.kind:
          return obj
    return None

lst = [Child1(), Child2(), Child3()]
def func2(c: Child2) -> None:
    assert isinstance(c, Child2)
res2_opt: Optional[Child2] = find_in_list(lst, Child2)
if res2_opt:
    func2(res2_opt)


def func3(c: Child3) -> None:
    assert isinstance(c, Child3)
res3_opt: Optional[Child3] = find_in_list(lst, Child2) # Should be Error!
if res3_opt:
    func3(res3_opt) # Is AssertionError

因为find_in_list的return类型太开放了,mypy不认为这有什么问题,但它在运行时正确地命中了断言错误。我试过 -> Optional[SearchClass],但它(正确地)没有将其识别为类型。

应该可以缩小 find_in_list 的 return 类型的范围,以与 SearchClass 相同或相同的方式进行参数化,这样如果你通过in Child2 作为参数,可以限制输出为Optional[Child2]。如何做到这一点?

您应该使用 TypeVar 的通用函数并转换 return 值:

from typing import TypeVar, cast
T = TypeVar('T', bound=Base)
def find_in_list(lst: List[Base], SearchClass: Type[T]) -> Optional[T]:
    for obj in lst:
        if obj.kind == SearchClass.kind:
            return cast(T, obj)
    return None

然后:

res3_opt: Optional[Child3] = find_in_list(lst, Child2)
# Mypy: Incompatible types in assignment (expression has type "Optional[Child2]", 
# variable has type "Optional [Child3]")