元类 Singleton 的 Python 类型

ale*_*xcs 3 python metaclass mypy python-typing

我有一个单例的 Python (3.8) 元类,如下所示

我尝试添加如下类型:

from typing import Dict, Any, TypeVar, Type

_T = TypeVar("_T", bound="Singleton")


class Singleton(type):
    _instances: Dict[Any, _T] = {}

    def __call__(cls: Type[_T], *args: Any, **kwargs: Any) -> _T:
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]
Run Code Online (Sandbox Code Playgroud)

在行中:

_instances: Dict[Any, _T] = {}
Run Code Online (Sandbox Code Playgroud)

MyPy 警告:

Mypy: Type variable "utils.singleton._T" is unbound

我已经尝试了不同的迭代,但没有成功;我很难弄清楚如何输入这个字典。

此外,该行:

_instances: Dict[Any, _T] = {}
Run Code Online (Sandbox Code Playgroud)

生产:

Mypy: The erased type of self "Type[golf_ml.utils.singleton.Singleton]" is not a supertype of its class "golf_ml.utils.singleton.Singleton"

我怎样才能正确输入这个?

dro*_*oze 5

这应该有效:

from __future__ import annotations

import typing as t


_T = t.TypeVar("_T")


class Singleton(type, t.Generic[_T]):

    _instances: dict[Singleton[_T], _T] = {}

    def __call__(cls, *args: t.Any, **kwargs: t.Any) -> _T:
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]
Run Code Online (Sandbox Code Playgroud)

粗略解释:

  1. _T = TypeVar("_T", bound="Singleton")不正确 -Singletontype(type(obj))哪里obj: _T = Singleton.__call__(...)。在正确的使用中, 的参数bound=只能是type(obj)或一些联合类型构造,而不是type(type(obj)
  2. Type variable "_T" is unbound表示您需要使绑定Singleton变得通用。_T_T
  3. The erased type of self ...错误消息告诉您您已经“删除”了类型检查器的推断类型* cls。从技术上讲,__call__元类上的方法与任何其他实例方法相同 - 第一个参数只是所属类的类型。然而,在当前的静态类型系统中,元类的实例方法的第一个参数与type[...].

*推断的类型明确Self如下:

import typing as t

Self = t.TypeVar("Self", bound="A")

class A:
    def instancemethod(arg: Self) -> None:
        pass
    @classmethod
    def classmethod_(arg: type[Self]) -> None:
        pass
Run Code Online (Sandbox Code Playgroud)

运行时也很重要,因此最终的健全性检查是确保您实际上已经使用此元类实现了单例:

class Logger(metaclass=Singleton):
    pass

>>> print(Logger() is Logger())
True
Run Code Online (Sandbox Code Playgroud)

  • @alexcs 类型检查器对元类的代码分析非常原始(但它们是一项正在进行的工作,因此期待改进)。但是,如果您在“--strict”模式下运行“mypy”,则“mypy”应该会出现一个与元类无关的问题。尝试再次将 `bound="Singleton"` 放在 `--strict` 下,您应该会收到有关缺少类型变量的投诉。 (2认同)