支持子类的 Callable 的类型提示?

Mar*_*man 5 python python-typing

我想类型提示一个方法,该方法可能会接收对 A 或 A 的子类进行操作的 Callable:

def do(f: Callable[[A], None])
Run Code Online (Sandbox Code Playgroud)

我也尝试过:

def do(f: Callable[[typing.Type[A]], None])
Run Code Online (Sandbox Code Playgroud)

完整的虚拟示例如下所示:

class A:
    n = 1

class B(A):
    n = 2

def something_a(x: A):
    print(x.n)

def something_b(x: B):
    print(x.n)

def do(f: Callable[[A], None]):
    f(B())

do(something_a)  # ok
do(something_b)  # PyCharm type check warnning
Run Code Online (Sandbox Code Playgroud)

Mar*_*man 3

正如宫城先生指出的那样 - 类型检查警告是正确的。

B继承自A,因此something_a(x: A)可以同时接受AB,而something_b(x: B)只能接受B。(协方差

如果我们传递something_b(x: B)do(f: Callable[[A], None]),我们允许something_b(x: B)接受A- 但它不能。所以something_b(x: B)不能替代something_a(x: A)。另一方面,传递something_a(x: A)给需要 a 的函数Callable[[B], None]就可以了。(逆变

该怎么办?- 使用泛型

AType = TypeVar('AType', bound=A)

def do_fixed(f: Callable[[AType], None]):
    pass

do_fixed(something_a)  # ok
do_fixed(something_b)  # ok
Run Code Online (Sandbox Code Playgroud)

虽然我们确实收到了传递其他类型的可调用对象的警告:

class C:  # not derived from A
    n = 1

def something_c(x: C):
    print(x.n)

do_fixed(something_c)  # type check warning
Run Code Online (Sandbox Code Playgroud)

但我们仍然需要确保我们不会传递Asomething_b,这可以通过以下方式实现:

def do_safer(f: Callable[[AType], None], v: AType):
    f(v)
    
do_safer(something_a, A())  # ok
do_safer(something_a, B())  # ok
do_safer(something_b, A())  # type check warning
do_safer(something_b, B())  # ok
Run Code Online (Sandbox Code Playgroud)