创建一个通用类型的 Dict 导数

Creating a generic typed Dict derivative

我正在尝试用 setdefault() 代替 get() 创建一个类似 dict 的类型,并使用预定义的默认类型。但它也不应该一直是指向同一个对象的指针。

问题是,我不明白如何在给定相应 TypeVar 的情况下实例化 var。

伪代码如下:

F = TypeVar('F')
T = TypeVar('T')


class AutoExpandingDict(Dict[F, T]):

    def __getitem__(self, key: F) -> T:
        if key not in self:
            self[key] = new T()
        return super(AutoExpandingDict, self).__getitem__(key)

如何让 new T() 工作?

预期用途是:

stats = AutoExpandingDict[str, AutoExpandingDict[str, RequestTimer]]()

其中 RequestTimer 是一个 class 记录一些统计信息,然后

def _api_request(method = 'GET', endpoint = None):
    ...
    with stats[method][endpoint]:
        ...
        
        response = self.session.request(method, f'{self.base_url}{api_path}{url_params_encoded}', ...)
        ...
    ...

除了 RequestTimer 之外还有其他用途,我不想复制粘贴很多只是名称不同的 class 或者用魔法重复 setdefault每次参数(如果它是一个普通的dict)。

Python 中的通用类型参数在运行时被“擦除”,因此您无法访问 T 的值。考虑一下:

A = TypeVar("A", covariant=True)

class Foo(Generic[A]):
    def __init__(self, something: A) -> None:
        self._something = something

    def do_something(self):
        [a_type] = get_params_somehow(self)
        print(a_type)

class X:
    pass

class Y(X):
    pass

foo1: Foo[X] = Foo(Y())
foo2: Foo[Y] = Foo(Y())

x = some().com().pu().ta() + tion()
foo3 = Foo(x)

foo1foo2 是以相同的方式创建的,他们无法访问有关其类型的信息(不借助 inspect 进行黑客攻击)。

使用 foo3x 的类型将由类型检查器 推断 ,因此 foo3 无法真正知道是什么它是在运行时,类型可能与 mypypyrightpyre 等不同。同样,类型(不是运行时类型,而是类型提示的东西)只是“在类型检查器的头部。

不过,如果您只打算以 AutoExpandingDict[str, AutoExpandingDict[str, RequestTimer]]() 方式构建 dict,则可以覆盖 __class_getitem__ 或制作自定义元类。但这不是很直观,可能有点矫枉过正


你可能想要做的是提供一个工厂,类似于 collections.defaultdict:

class AutoExpandingDict(Dict[F, T]):
    def __init__(self, factory: Callable[[], T]):
        self._factory = factory

    def __getitem__(self, key: F) -> T:
        if key not in self:
            self[key] = self._factory()
        return super().__getitem__(key)

例如:

stats: AutoExpandingDict[str, AutoExpandingDict[str, RequestTimer]] = 
AutoExpandingDict(lambda: AutoExpandingDict(RequestTimer))

也许你只是想要一个 defaultdict