回调协议——我们什么时候需要双下划线前缀?

Callback protocols - when do we need the double underscore prefix?

mypy docs

Callback protocols and :py:data:~typing.Callable types can be used interchangeably. Keyword argument names in :py:meth:__call__ <object.__call__> methods must be identical, unless a double underscore prefix is used. For example:

typing_extensions import Protocol

T = TypeVar('T')

class Copy(Protocol):
    def __call__(self, __origin: T) -> T: ...

copy_a: Callable[[T], T]    copy_b: Copy

copy_a = copy_b  # OK    copy_b = copy_a  # Also OK    ```

但是,如果我们删除 __origin 之前的双下划线前缀,这个例子也可以工作。例如

$ cat t.py 
from typing import Callable, TypeVar
from typing_extensions import Protocol

T = TypeVar('T')

class Copy(Protocol):
    def __call__(self, origin: T) -> T: ...

copy_a: Callable[[T], T]
copy_b: Copy

copy_a = copy_b  # OK
copy_b = copy_a  # Also OK
$ mypy t.py 
Success: no issues found in 1 source file

所以,这个例子对我来说不是很清楚。我们什么时候需要双下划线前缀?

可以使用命名参数代替匿名参数,因此协议用作 Callable 的值。第一个赋值 copy_a = copy_b 默默地将 copy_a 提升为 Copy,然后有效地赋值给 copy_b: Copy.

...

copy_a = copy_b  # copy_a is a Copy now!
copy_b = copy_a  # assign copy_a: Copy to copy_b: Copy
reveal_type(copy_a)  # Revealed type is 'aaa_testbed.Copy'

交换赋值意味着 copy_b = copy_a 发生而 copy_a 仍然是匿名类型。这会触发预期的错误:

...

copy_b = copy_a  # Incompatible types in assignment (expression has type "Callable[[T], T]", variable has type "Copy")
copy_a = copy_b