python - 使用可变泛型进行方法返回专门化

标签 python generics type-hinting typing variadic

目标是让以下伪代码在 Python 3.7+ 中有效,并让静态分析工具理解它。

class VariadicType(MaybeASpecialBaseClass, metaclass=MaybeASpecialMetaClass):
    @classmethod
    def method(cls)->Union[???]:
        pass  # some irrelevant code

assert(VariadicType[Type1, Type2, Type3, Type4].method.__annotations__["return"] == Union[Type1, Type2, Type3, Type4])
assert(VariadicType[Type1, Type2, Type3, Type4, Type5].method.__annotations__["return"] == Union[Type1, Type2, Type3, Type4, Type5])

是否可以支持某种class VariadicType(Generic[...])但然后获取所有传递的泛型类型?

我正在考虑采用 C# 方法

class VariadicType(Generic[T1]):
...
class VariadicType(Generic[T1, T2]):
...
class VariadicType(Generic[T1, T2, T3]):
...
class VariadicType(Generic[T1, T2, T3, T4]):
...
class VariadicType(Generic[T1, T2, T3, T4, T5]):
...

但这不是有效的代码 - VariadicType只能定义一次。

PS。代码的不相关部分应该检查 __annotations__["return"]并返回相应的结果。它正在应用 mixin。如果返回类型不是所有应用的 mixins 的并集,则静态分析会提示缺少字段和方法。具有非提示代码,其中类型作为方法参数给出,但返回类型为 Any这是最后的手段。

最佳答案

我已经遇到了这个问题,所以也许我可以解决这个问题。

问题

假设我们有下一个类定义:

T = TypeVar('T')
S = TypeVar('S')
class VaradicType(Generic[T, S]):
    pass

问题是 VaradicType[T, S] 调用 VaradicType.__class_getitem__((T, S)) ,它返回类 _GenericAlias< 的对象.

然后,如果您执行 cls = VaradicType[int, float],您可以内省(introspection)用作索引的参数 cls.__args__

但是,如果您实例化像 obj = cls() 这样的对象,则无法执行 obj.__class__.__args__
这是因为 _GenericAlias 实现了 __call__ 方法,该方法直接返回 VaradicType 的对象,该对象的 MRO 中没有任何包含有关参数信息的类提供。

class VaradicType(Generic[T, S]):
    pass
cls = VaradicType[int, float]().__class__
print('__args__' in cls) # False

一种解决方案

解决此问题的一种可能方法是在实例化 VaradicType 类的对象时添加有关通用参数的信息。

首先(按照前面的代码片段),我们将向 VaradicType 添加一个元类:

class VaradicType(Generic[T, S], metaclass=GenericMixin):
    pass

我们可以利用这样一个事实:如果__getitem__定义在元类上,则其优先级高于__class_getitem__,以便绕过Generic.__class_getitem__

class GenericMixin(type):
    def __getitem__(cls, items):
        return GenericAliasWrapper(cls.__class_getitem__(items))

现在,VaradicType[int, float] 相当于 GenericMixin.__getitem__(VaradicType, (int, float)) 并且它将返回类 的对象code>GenericAliasWrapper (它用于“包装”typing._GenericAlias 实例):

class GenericAliasWrapper:
    def __init__(self, x):
        self.wrapped = x

    def __call__(self, *args, **kwargs):
        obj = self.wrapped.__call__(*args, **kwargs)
        obj.__dict__['__args__'] = self.wrapped.__args__
        return obj

现在,如果您有 cls=VaradicType[int, float],则代码 cls() 将相当于
GenericAliasWrapper( VaradicType .__class_getitem__((int, float)) ).__call__()
创建类 VaradicType 的新实例,并添加属性 __args__ > 到它的字典。

例如:

VaradicType[int, float]().__args__ # (<class int>, <class float>)

关于python - 使用可变泛型进行方法返回专门化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57128053/

相关文章:

java - 返回类型之前泛型的目的是什么

PHP 接口(interface)实现拒绝参数的子类

python - 带有描述符的类型提示

Python Flask 导出和下载 CSV 错误

java - 如何制作一个使用两种不同泛型类型扩展 Iterable 的 Java 接口(interface)?

c++ - 无法在 C++ 中嵌入的 Python 代码中修改 time.time() 返回的值

generics - 与泛型相关的类型错误取决于函数的位置

python - 在 Python 中,如何输入提示 'has attribute' ?

python - 设置标题和轴标签

python - 逐行读取文件——对磁盘的影响?