如何正确 override/overload 输入 __call__

How to correctly override/overload typing of __call__

我需要说明类型,统一工厂返回。我在单独的文件中有单例工厂,如下所示:

from typing import Any, Type


class SingletonFactory:
    __slots__ = ('singleton_instance', )

    def __init__(self, singleton_class: Type[object], **singleton_init_params: Any):
        self.singleton_instance: object = singleton_class(**singleton_init_params)  # type: ignore[call-arg]  # noqa: E501

    def __call__(self) -> object:
        return self.singleton_instance

然后在其他文件中。

选项 1:

from typing import Callable, overload

class Client:
   pass  # IRL have init params


client_factory: Callable[[], Client] = SingletonFactory(
    Client
)

client = client_factory()  # pyCharm see it as instance of object - expected Client

MyPy 错误:

error: Incompatible types in assignment (expression has type "SingletonFactory", variable has type "Callable[[], Client]")  [assignment]
note: "SingletonFactory.__call__" has type "Callable[[], object]"

选项 2:

from typing import Callable, overload

class Client:
   pass  # IRL have init params

@overload  # type: ignore[misc]
def client_factory() -> Client:
    ...


client_factory = SingletonFactory(
    Client
)

client = client_factory()  # pyCharm see it as instance of Client - what I expect

有效,但 MyPy 错误与上面相同加上:error: Single overload definition, multiple required [misc].

选项 3:

from typing import Callable, overload

class Client:
   pass  # IRL have init params


class ClientFactory(SingletonFactory):

    @overload  # type: ignore[misc]
    def __call__() -> Client:
        ...


client_factory = ClientFactory(
    Client
)

client = client_factory()  # pyCharm see it as instance of Client - what I expect

少了一个MyPy错误,但是继承SingletonFactory只是为了重载使得代码很繁琐。

有没有办法完全满足这种情况下的MyPy?

您正在寻找 generics,以描述代码中不同变量类型之间的关系;下面的代码演示了一种这样的实现。

from typing import Callable, Generic, ParamSpec, TypeVar

P = ParamSpec("P")
T = TypeVar("T")


class SingletonFactory(Generic[T]):
    __slots__ = ('singleton_instance', )

    singleton_instance: T

    def __init__(
        self,
        singleton_class: Callable[P, T],
        *singleton_args: P.args,
        **singleton_kwargs: P.kwargs
    ):
        self.singleton_instance = singleton_class(*singleton_args, **singleton_kwargs)

    def __call__(self) -> T:
        return self.singleton_instance

现在 __call__ 的 return 类型基于 __init__ 的第一个参数的类型:

class Thing: pass


t: Thing = SingletonFactory(Thing)()  # OK

或者,您可以通过提供通用类型在某处显式指定特定类型的工厂:

def my_func(factory: SingletonFactory[int]) -> int:
    return factory()

应该注意的是,不是使用 Type[T],而是使用 Callable[P, T]ParamSpec(在 Python 3.10 中实现)。当程序是这样写的时候,它也允许检查参数,基于 singleton_class:

class OtherThing:
    def __init__(self, foo: int, bar: str):
        self.foo = foo
        self.bar = bar


ot: OtherThing = SingletonFactory(OtherThing)()  # error: Missing positional arguments "foo", "bar" in call to "SingletonFactory"

ot = SingletonFactory(OtherThing, 123, bar="baz")()  # OK

...您不再需要 type: ignore[call-arg].

MyPy playground