python-3.x 如何在T的生命周期早期从泛型[T]示例访问T?

luaexgnf  于 2023-02-10  发布在  Python
关注(0)|答案(2)|浏览(111)

我知道我能做到:

import typing

T = typing.TypeVar("T")

class MyGenericClass(Generic[T]):
    def a_method(self):
        print(self.__orig_class__)

MyOtherGeneric[SomeBaseClass]().a_method()

打印SomeBaseClass。可能,我会坚持使用这种能力来实现我最终要做的事情(基于T修改功能),但我现在一直想知道这一切是如何工作的。
最初,我希望在对象被示例化时,或者在示例化后不久,而不是在其生命周期的后期,从类内部访问基类型信息(T的值)。
作为一个具体的例子,在下面的代码中,我想用一些东西来替换?n?中的任何一个,这样我就可以在对象生命周期的早期得到SomeOtherBaseClass的值,也许还有一些代码需要在其中一行之上。

import typing

T = typing.TypeVar("T")

class MyOtherGenericClass(Generic[T]):
    def __init__(self, *args, **kwargs):
        print(?1?)
    
    def __new__(klass, *args, **kwargs):
        print(?2?)

MyOtherGenericClass[SomeOtherBaseClass]()

我试图在示例化时(或者,不知何故,在示例化后不久)基于T的值设置一些示例变量。考虑到typing模块,特别是泛型,似乎仍处于不稳定的开发阶段,我正在重新考虑我的方法。
那么......这是可能的吗?一个用户指出,至少在Python 3.8中,__orig_class__typing._GenericAlias.__call__期间被设置,但是__call__方法是如何被调用的呢?什么时候发生的呢?
相关阅读:

bf1o4zei

bf1o4zei1#

我不知道,看起来好像你想有一个Self类型,这是在即将到来的Python 3.11的PEP 673中的一个东西。
尚未实现的Self-Type的当前解决方法是:

from typing import TypeVar

TShape = TypeVar("TShape", bound="Shape")

class Shape:
    def set_scale(self: TShape, scale: float) -> TShape:
        self.scale = scale
        return self

class Circle(Shape):
    def set_radius(self, radius: float) -> Circle:
        self.radius = radius
        return self

Circle().set_scale(0.5).set_radius(2.7)  # => Circle

其中self参数和返回类型都使用上界类型变量TShape而不是Self类型进行提示。

mo49yndu

mo49yndu2#

您有两/三个选项,从最脏到最脏:

黑客类型。通用机制:

并检查呼叫帧

import inspect
import types
from typing import Generic
from typing import Type
from typing import TypeVar

T = TypeVar('T')

class A(Generic[T]):
    _t: Type[T]
    def __class_getitem__(cls, key_t) -> type:
        g = typing._GenericAlias(cls, key_t)  # <-- undocumented
        return g
    
    def __new__(cls):
        prevfr = inspect.currentframe().f_back
        t = inspect.getargvalues(prevfr).locals['self'].__args__[0]
        o = super().__new__(cls)
        o._t = t
        return o

    def __init__(self) -> None:
        print(f"{self._t=}")

print(A[str]) # >>> "<types.A[str] at 0x11b52c550>"
print(A[str]()) # >>> "self._t=<class 'str'>"

具体化泛型类

T = TypeVar('T')

class A(Generic[T]):
    __concrete__ = {}
    _t: Type[T]
    def __class_getitem__(cls, key_t: type):
        cache = cls.__concrete__
        if (c := cache.get(key_t, None)): return c
        cache[key_t] = c = types.new_class(
            f"{cls.__name__}[{key_t.__name__}]", (cls,), {}, lambda ns: ns.update(_t=key_t))
        return c
    def __init__(self) -> None:
        print(f"{self._t=}")

A[str]()

用元类隐藏你可耻的私处

T = TypeVar('T')

class AMeta(type):
    __concrete__ = {}
    def __getitem__(cls, key_t: type):
        cache = cls.__concrete__
        if (c := cache.get(key_t, None)): return c
        cache[key_t] = c = types.new_class(
            f"{cls.__name__}[{key_t.__name__}]", (cls,), {}, lambda ns: ns.update(_t=key_t))
        return c

class A(Generic[T], metaclass=AMeta):
    _t: Type[T]
    def __init__(self) -> None:
        print(f"{self._t=}")

A[str]()

注意,这增加了大量的示例化开销。这两种解决方案都不优雅。Python明智地(恕我直言,Typescript正在考虑你)决定使用现有的Python构造或创建尽可能少的新构造来实现类型支持。他们正在缓慢地添加运行时支持,重点是静态类型。因此
除了类型提示之外,不鼓励在任何类上使用class_getitem()。
但是Python固有的动态特性最终会取得胜利,我们将及时进行全面的运行时反思。
已在Python 3.10.8中测试

相关问题