使用可变参数泛型进行方法返回特化

Posted

技术标签:

【中文标题】使用可变参数泛型进行方法返回特化【英文标题】:Use variadic generics for method return specialization 【发布时间】:2019-11-29 08:51:18 【问题描述】:

目标是让以下伪代码在 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 应该只定义一次。

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

【问题讨论】:

如果你正在构建一个静态工具来检查它,你想做一些类似于库 mypy 所做的事情。它读取代码并构建 AST 结构而不执行它。 目标远不止于此。请注意,我提到了对静态工具和运行时的支持。 【参考方案1】:

我已经遇到过这个问题,所以也许我可以说明一下。

问题

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

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],您可以通过以下方式反省用作索引的参数 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 类的对象时添加有关通用参数的信息。

首先(按照前面的代码sn-ps),我们将添加一个元类到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)),它将返回一个 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>)

【讨论】:

试一试。不知道元类__getitem__

以上是关于使用可变参数泛型进行方法返回特化的主要内容,如果未能解决你的问题,请参考以下文章

java泛型中可变参数的是使用

Kotlin泛型 ② ( 可变参数 vararg 关键字与泛型结合使用 | 使用 [] 运算符获取指定可变参数对象 )

如何使通用可变参数函数中先前声明的函数的返回类型成功进行上下文推断?

Java实训笔记——-抽象类-接口-泛型-集合

JAVA中,关于可变参数和泛型的问题。

如何创建可变参数泛型 lambda?