如何在 Python 中创建自己的“参数化”类型(如 `Optional[T]`)?

Posted

技术标签:

【中文标题】如何在 Python 中创建自己的“参数化”类型(如 `Optional[T]`)?【英文标题】:How can I create my own "parameterized" type in Python (like `Optional[T]`)? 【发布时间】:2018-03-05 01:31:15 【问题描述】:

我想在 Python 中创建自己的参数化类型以用于类型提示:

class MaybeWrapped:
    # magic goes here

T = TypeVar('T')

assert MaybeWrapped[T] == Union[T, Tuple[T]]

别介意人为的例子;我该如何实施?我查看了 Union 和 Optional 的源代码,但它看起来像是一些我想避免的相当低级的黑客行为。

文档中唯一的建议来自example re-implementation of Mapping[KT,VT] that inherits from Generic。但这个例子更多的是关于 __getitem__ 方法而不是类本身。

【问题讨论】:

【参考方案1】:

如果您只是想创建通用类或函数,请尝试查看documentation on mypy-lang.org about generic types——它相当全面,比标准库类型文档更详细。

如果您尝试实现您的具体示例,值得指出的是 type aliases work with typevars -- 您可以这样做:

from typing import Union, TypeVar, Tuple

T = TypeVar('T')

MaybeWrapped = Union[T, Tuple[T]]

def foo(x: int) -> MaybeWrapped[str]:
    if x % 2 == 0:
        return "hi"
    else:
        return ("bye",)

# When running mypy, the output of this line is:
# test.py:13: error: Revealed type is 'Union[builtins.str, Tuple[builtins.str]]'
reveal_type(foo(3))

但是,如果您尝试构建具有真正新语义的泛型类型,那么您很可能不走运。您剩下的选择是:

    构建符合 PEP 484 的类型检查器可以理解和使用的某种自定义类/元类。 以某种方式修改您正在使用的类型检查器(例如,mypy 有一个实验性“插件”系统) 请求修改 PEP 484 以包含您的新自定义类型(您可以通过在 typing module repo 中打开问题来完成此操作。

【讨论】:

谢谢,使用TypeVar 这是我所缺少的。泛型像这样“正常工作”真是太神奇了。 出于好奇,您是否知道“构造某种符合 PEP 484 的类型检查器可以理解和使用的自定义类/元类的东西”选项现在是否可行?【参考方案2】:

正是__getitem__ 方法发挥了所有作用。

这就是订阅一个名字时调用的方法,用[]括号。

因此,您需要在您的类的类中使用__getitem__ 方法 - 即它的元类,它将作为参数获取括号内的任何内容。该方法负责动态创建(或检索缓存的副本)您想要生成的任何内容,并将其返回。

我只是无法想象您希望如何将其用于类型提示,因为类型库似乎涵盖了所有合理的情况(我想不出他们尚未涵盖的示例)。但是让我们假设您希望一个类返回其自身的副本,但参数注释为它的type_ 属性:

class MyMeta(type):
    def __getitem__(cls, key):
        new_cls = types.new_class(f"cls.__name___key.__name__", (cls,), , lambda ns: ns.__setitem__("type", key))
        return new_cls

class Base(metaclass=MyMeta): pass

在交互模式下尝试这个,可以做到:

In [27]: Base[int]
Out[27]: types.Base_int

更新:从 Python 3.7 开始,还有专门为此目的而创建的特殊方法 __class_getitem__:它充当类方法并避免仅针对这种情况的需要或元类.在metaclass.__getitem__ 中写入的任何内容都可以直接放入cls.__class_getitem__ 方法中。定义在PEP 560

【讨论】:

谢谢,虽然我仍然不确定这一切是如何运作的。你能谈谈我问题中的例子吗?您回答中的示例似乎与我尝试做的有点不同。 @jsbueno -- 我不认为你的答案有效。您提出的代码当然是一种构建看起来像 PEP 484 类型的东西的方法,但由于它不是,因此符合 PEP 484 的类型检查器将无法理解如何处理您的 MyMetaBase 类。 哇!伟大的!你能解释一下lambda ns: ns.__setitem__("type", key)吗?如果我好了ns 是所谓的“命名空间”,但在这种情况下是什么意思以及在哪里可以找到提供的键“类型”? 知道了!它只是类的 dict 本身,因此可以通过类或实例的属性访问。 b=Base[int]assert b.type is int【参考方案3】:

我想根据@jsbueno 的回答提出改进的解决方案。现在我们的“泛型”可以用于比较和身份检查,它们的行为就像“真正的”泛型一样。我们也可以禁止实例化非类型类本身。而且!我们得到了isinstance免费检查!

还可以满足BaseMetaMixin 类的完美静态类型检查!

import types
from typing import Type, Optional, TypeVar, Union

T = TypeVar('T')


class BaseMetaMixin:
    type: Type


class BaseMeta(type):
    cache = 

    def __getitem__(cls: T, key: Type) -> Union[T, Type[BaseMetaMixin]]:
        if key not in BaseMeta.cache:
            BaseMeta.cache[key] = types.new_class(
                f"cls.__name___key.__name__",
                (cls,),
                ,
                lambda ns: ns.__setitem__("type", key)
            )

        return BaseMeta.cache[key]

    def __call__(cls, *args, **kwargs):
        assert getattr(cls, 'type', None) is not None, "Can not instantiate Base[] generic"
        return super().__call__(*args, **kwargs)


class Base(metaclass=BaseMeta):
    def __init__(self, some: int):
        self.some = some


# identity checking
assert Base[int] is Base[int]
assert Base[int] == Base[int]
assert Base[int].type is int
assert Optional[int] is Optional[int]

# instantiation
# noinspection PyCallByClass
b = Base[int](some=1)
assert b.type is int
assert b.some == 1

try:
    b = Base(1)
except AssertionError as e:
    assert str(e) == 'Can not instantiate Base[] generic'

# isinstance checking
assert isinstance(b, Base)
assert isinstance(b, Base[int])
assert not isinstance(b, Base[float])

exit(0)
# type hinting in IDE
assert b.type2 is not None # Cannot find reference 'type2' in 'Base | BaseMetaMixin'
b2 = Base[2]()  # Expected type 'type', got 'int' instead

【讨论】:

顺便说一句,现在我们有了__class_getitem__(cls, item),在基类中定义它,我们可以在没有元类的情况下做同样的事情。

以上是关于如何在 Python 中创建自己的“参数化”类型(如 `Optional[T]`)?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中创建动态参数化 SQL 查询字符串

为什么在外部参数化类中创建一个数组,其中组件类型是一个被认为是“通用数组创建”的内部类?

(如何)在 boost 几何中创建自己的多边形类型并使用 multi_polygon 类型?

如何在 BigQuery SQL 中安全地参数化表/列名称?

使用python在opencv中创建自己的轮廓

在python中如何创建角色?