我想使用新的Python 3.12 generic type signature语法来了解该类的classmethod内即将实例化的类的类型.

例如,我想在这个例子中打印具体类型T:

class MyClass[T]:
    kind: type[T]

    ...

    @classmethod
    def make_class[T](cls) -> "MyClass[T]":
        print("I am type {T}!")
        return cls()

我已经使用了下面StackOverFlow问题中的建议来具体化__new____init__中的类型,但我还没有找到一种巧妙的方法来在静态或类方法(最好是类方法)中做到这一点.

我的目标是拥有以下API:

>>> MyClass[int].make_class()
"I am type int!"

或者这个API(我认为在语法上还不可能):

>>> MyClass.make_class[int]()
"I am type int!"

无论哪种情况,返回的实例都会将int绑定到类变量,以便我以后可以使用它.

MyClass[int].make_class().kind is int == True

我对"黑客攻击"持开放态度(包括大量使用inspect).

推荐答案

如果您阅读typing.py的源代码,您会看到Generic的类型参数存储为Base Class _BaseGenericAlias__args__属性.请注意,这是一个未记录的实现细节.

然后,当该方法用classmethod子类装饰(标记了特殊属性的函数)时,我们只需要修补其代理属性getter _BaseGenericAlias.__getattr__,以将额外的关键字参数注入到具有包装器函数的方法调用中:

class TypeArgsClassMethod(classmethod):
    def __get__(self, obj, obj_type=None):
        method = super().__get__(obj, obj_type)
        method.__func__._inject_type_args = True
        return method

def __getattr__(self, name):
    if hasattr(obj := orig_getattr(self, name), '_inject_type_args'):
        @wraps(obj)
        def wrapper(*args, **kwargs):
            return obj(*args, __type_args__=self.__args__, **kwargs)
        return wrapper
    return obj

if _BaseGenericAlias.__getattr__ is not __getattr__:
    orig_getattr = _BaseGenericAlias.__getattr__
    _BaseGenericAlias.__getattr__ = __getattr__

以便:

class MyClass[T]:
    kind: type[T]

    @TypeArgsClassMethod
    def make_class(cls, __type_args__) -> "MyClass[T]":
        print(__type_args__)
        return cls()

MyClass[int].make_class()

输出:

(<class 'int'>,)

演示 here

或者,您可以将自定义类方法重新绑定到Generic类型,以便类方法的第一个参数将成为Generic类型.内置types.MethodType不允许更新__self__以进行重新绑定,因此您必须定义Python-equivalent version:

from functools import update_wrapper
from typing import _BaseGenericAlias

class MethodType:
    def __init__(self, func, obj):
        self.__func__ = func
        self.__self__ = obj

    def __call__(self, *args, **kwargs):
        func = self.__func__
        obj = self.__self__
        return func(obj, *args, **kwargs)

class GenericClassMethod:
    def __init__(self, f):
        self.f = f
        update_wrapper(self, f)

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        method = MethodType(self.f, cls)
        method._generic_classmethod = True
        return method

def __getattr__(self, name):
    if hasattr(obj := orig_getattr(self, name), '_generic_classmethod'):
        obj.__self__ = self
    return obj

if _BaseGenericAlias.__getattr__ is not __getattr__:
    orig_getattr = _BaseGenericAlias.__getattr__
    _BaseGenericAlias.__getattr__ = __getattr__

以便您可以访问类型参数的__args__属性,以及原始类的__origin__属性:

class MyClass[T]:
    kind: type[T]

    @GenericClassMethod
    def make_class(cls) -> "MyClass[T]":
        print(cls)
        print(cls.__origin__)
        print(cls.__args__)

MyClass[int].make_class()

This 输出:

__main__.MyClass[int]
<class '__main__.MyClass'>
(<class 'int'>,)

演示 here

Python相关问答推荐

Image Font生成带有条形码Code 128的条形码时出现枕头错误OSErsor:无法打开资源

Polars:使用列值引用when / then表达中的其他列

在Python和matlab中显示不同 colored颜色 的图像

PywinAuto在Windows 11上引发了Memory错误,但在Windows 10上未引发

如何让剧作家等待Python中出现特定cookie(然后返回它)?

将图像拖到另一个图像

用合并列替换现有列并重命名

如何在Python脚本中附加一个Google tab(已经打开)

pyscript中的压痕问题

在Django admin中自动完成相关字段筛选

考虑到同一天和前2天的前2个数值,如何估算电力时间序列数据中的缺失值?

将scipy. sparse矩阵直接保存为常规txt文件

如何在PySide/Qt QColumbnView中删除列

将标签移动到matplotlib饼图中楔形块的开始处

如何求相邻对序列中元素 Select 的最小代价

如何合并具有相同元素的 torch 矩阵的行?

Django Table—如果项目是唯一的,则单行

有没有办法在不先将文件写入内存的情况下做到这一点?

当输入是字典时,`pandas. concat`如何工作?

为什么后跟inplace方法的`.rename(Columns={';b';:';b';},Copy=False)`没有更新原始数据帧?