由于这是一个XY Problem,我将从X的解开始.Y的答案更低.
解
不需要手动呼叫cls.__new__
,然后在Singleton.__call__
中呼叫cls.__init__
.你可以直接拨打super().__call__
,就像@Grismar拨打his answer一样.
如果您想要做的只是以类型安全的方式设置单例模式,那么也根本不需要定制Singleton.__new__
方法.
要在您的类中有_instance = None
的回退,只需在元类上定义并分配该属性即可.
以下是所需的最低设置:
from __future__ import annotations
from typing import Any
class Singleton(type):
_instance: Singleton | None = None
def __call__(cls, *args: Any, **kwargs: Any) -> Singleton:
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class Foo(metaclass=Singleton):
pass
如果超过mypy --strict
,则不会产生任何PyCharm警告.你说你想把_instance
保存在每个class中,而不是meta class中.这里就是这种情况.try 以下操作:
foo1 = Foo()
foo2 = Foo()
print(foo1 is foo2) # True
print(foo1 is Foo._instance) # True
print(Singleton._instance) # None
如果您do想要一个定制的元类__new__
方法,由于type
构造函数的过载,它需要更多的样板文件才能以类型安全的方式设置.但这里有一个在所有情况下都应该有效的模板:
from __future__ import annotations
from typing import Any, TypeVar, overload
T = TypeVar("T", bound=type)
class Singleton(type):
_instance: Singleton | None = None
@overload
def __new__(mcs, o: object, /) -> type: ...
@overload
def __new__(
mcs: type[T],
name: str,
bases: tuple[type, ...],
namespace: dict[str, Any],
/,
**kwargs: Any,
) -> T: ...
def __new__(
mcs,
name: Any,
bases: Any = (),
namespace: Any = None,
/,
**kwargs: Any,
) -> type:
if namespace is None:
namespace = {}
# do other things here...
return type.__new__(mcs, name, bases, namespace, **kwargs)
def __call__(cls, *args: Any, **kwargs: Any) -> Singleton:
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
__new__
的超载与typeshed stubs的重载非常相似.
但同样,这对于singleton-via-metaclass%的模式来说并不是必要的.
更深入地挖掘你的类型推理问题让我陷入了困境,所以解释变得更长了.但我认为提前展示actual个问题的解决方案更有用(因为它避免了错误).因此,如果您对这些解释感兴趣,请继续阅读.
Why does cls.__new__
expect type[Singleton]
?
对于您的代码,我从Mypy那里得到了相同的错误:
Argument 1 to "__new__" of "Singleton" has incompatible type "Singleton"; expected "Type[Singleton]"
回想一下,__call__
方法是instance方法.在Singleton.__call__
的上下文中,第一个参数的类型(在本例中命名为cls
)被推断为Singleton
.
由于您定义了自己的Singleton.__new__
方法,它的(隐式)签名将反映在cls.__new__
中.您没有注释Singleton.__new__
,但是对于特殊情况,如方法的第一个参数,类型判断器通常会退回到"标准"推论.
__new__
是一个static方法,它将一个类作为第一个参数,并返回它的一个实例.因此,Singleton.__new__
的第一个自变量预计是the type type[Singleton]
,而不是Singleton
的an instance.
因此,从类型判断器的Angular 来看,通过调用cls.__new__(cls, ...)
,您传递的是Singleton
的instance作为参数,其中预期的是类型Singleton
本身(或子类型).
Side note:
这种区别可能非常令人困惑,这就是为什么最佳实践之一是将第一个参数命名为__new__
differently,而不是将第一个参数命名为"正常"方法.
在常规类中(不是从type
继承),普通方法的第一个参数应该调用self
,而__new__
的第一个参数应该调用cls
.
然而,在meta个类中,约定并不普遍,但常识表明,普通方法的第一个参数应该被称为cls
,而__new__
的第一个参数应该被称为mcs
(或mcls
或类似的东西).强调第一个论点性质上的区别是非常有用的.但这些当然都是约定,解释器并不关心这两种方式.
这种cls.__new__
等于Singleton.__new__
的推论是否合理或合理,是值得商榷的.
由于该上下文中的cls
将始终是周围(元)类的instance,我认为期望cls.__new__
解析为所述(元)类的__new__
方法是没有意义的.
事实上,除非类cls
本身定义了一个定制的__new__
方法,否则它将回退到object.__new__
,而不是Singleton.__new__
:
class Singleton(type):
def __call__(cls, *args, **kwargs):
print(cls.__new__)
print(cls.__new__ is object.__new__)
print(cls.__new__ is Singleton.__new__)
class Foo(metaclass=Singleton):
pass
foo1 = Foo()
输出:
<built-in method __new__ of type object at 0x...>
True
False
object.__new__
实际上does接受Singleton
作为它的第一个参数,因为它是一个类,返回类型将是它的一个实例.因此,据我所知,你拨打cls.__new__
的方式没有任何错误或不安全之处.
如果我们将自定义__new__
添加到Singleton
并通过类型判断器运行它,我们可以更清楚地看到类型判断器做出的错误类型推断:
from __future__ import annotations
from typing import Any
class Singleton(type):
def __new__(mcs, name: str, bases: Any = None, attrs: Any = None) -> Singleton:
return type.__new__(mcs, name, bases, attrs)
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
reveal_type(cls.__new__)
...
Mypy错误地推断出类型如下:
Revealed type is "def (mcs: Type[Singleton], name: builtins.str, bases: Any =, attrs: Any =) -> Singleton"
因此,它显然预计cls.__new__
是Singleton.__new__
,尽管它实际上是object.__new__
.
据我所知,实际的方法解析与类型判断器推断的解析之间的差异是由于__new__
方法的特殊行为造成的.这也可能只是与类型判断器对元类的支持不佳有关.但也许更有见识的人可以澄清这一点.(或者我会咨询问题跟踪器.)
That pesky __init__
call
当然,关于PyCharm的消息是无稽之谈.这个问题似乎归结为同样的错误推理,即cls.__init__
与type.__init__
,而不是object.__init__
.
Mypy有一个完全不同的问题,它抱怨实例上显式使用__init__
,并出现以下错误:
Accessing "__init__" on an instance is unsound, since instance.__init__ could be from an incompatible subclass
Mypy故意将__init__
方法排除在LSP个一致性要求之外,这意味着显式调用它是technically不安全的.
没什么可说的了.避免调用该调用,除非您确定__init__
的MRO链上的所有重写都是类型安全的;然后使用# type: ignore[misc]
.
综上所述,我认为这两个警告/错误都是假阳性.