如何在 Python 中创建自己的 "parameterized" 类型(如 `Optional[T]`)?
How can I create my own "parameterized" type in Python (like `Optional[T]`)?
我想在 Python 中创建我自己的参数化类型以用于类型提示:
class MaybeWrapped:
# magic goes here
T = TypeVar('T')
assert MaybeWrapped[T] == Union[T, Tuple[T]]
别在意人为的例子;我该如何实施?我查看了 Union 和 Optional 的源代码,但它看起来像是我想避免的一些相当低级的 hackery。
文档中唯一的建议来自 example re-implementation of Mapping[KT,VT]
that inherits from Generic。但是这个例子更多的是关于 __getitem__
方法而不是 class 本身。
正是 __getitem__
方法发挥了所有魔力。
这是订阅一个带[
和]
括号的名字时调用的方法
因此,您需要在 class 的 class 中使用一个 __getitem__
方法 - 即它的元 class,它将作为参数获取其中的任何内容括号。该方法负责动态创建(或检索缓存副本)您想要生成的任何内容,并且 return 它。
我只是无法想象您如何使用它来进行类型提示,因为类型库似乎涵盖了所有合理的情况(我想不出它们尚未涵盖的示例)。但是,假设您想要一个 class 到 return 自身的副本,但参数被注释为它的 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
update:从 Python 3.7 开始,还有一个专门为此目的创建的特殊方法 __class_getitem__
:它充当 class 方法并避免了这种情况下的需要或 metaclass 。 metaclass.__getitem__
中的任何内容都可以直接放入 cls.__class_getitem__
方法中。在 PEP 560
中定义
如果您只是想创建通用 类 或函数,请尝试查看 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 的类型检查器 可以 理解和使用的自定义 class/metaclass 东西。
- 以某种方式修改您正在使用的类型检查器(例如,mypy 有一个实验性 "plugin" 系统)
- 请求修改 PEP 484 以包含您的新自定义类型(您可以通过在 typing module repo 中打开一个问题来完成此操作)。
我想根据@jsbueno 的回答提出改进的解决方案。现在我们的“泛型”可以用于比较和身份检查,它们的行为就像打字时的“真正”泛型。我们也可以禁止 non-typed class 本身的实例化。而且!我们 isinstance
免费检查!
同时满足 BaseMetaMixin
class 完美的静态类型检查!
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
我想在 Python 中创建我自己的参数化类型以用于类型提示:
class MaybeWrapped:
# magic goes here
T = TypeVar('T')
assert MaybeWrapped[T] == Union[T, Tuple[T]]
别在意人为的例子;我该如何实施?我查看了 Union 和 Optional 的源代码,但它看起来像是我想避免的一些相当低级的 hackery。
文档中唯一的建议来自 example re-implementation of Mapping[KT,VT]
that inherits from Generic。但是这个例子更多的是关于 __getitem__
方法而不是 class 本身。
正是 __getitem__
方法发挥了所有魔力。
这是订阅一个带[
和]
括号的名字时调用的方法
因此,您需要在 class 的 class 中使用一个 __getitem__
方法 - 即它的元 class,它将作为参数获取其中的任何内容括号。该方法负责动态创建(或检索缓存副本)您想要生成的任何内容,并且 return 它。
我只是无法想象您如何使用它来进行类型提示,因为类型库似乎涵盖了所有合理的情况(我想不出它们尚未涵盖的示例)。但是,假设您想要一个 class 到 return 自身的副本,但参数被注释为它的 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
update:从 Python 3.7 开始,还有一个专门为此目的创建的特殊方法 __class_getitem__
:它充当 class 方法并避免了这种情况下的需要或 metaclass 。 metaclass.__getitem__
中的任何内容都可以直接放入 cls.__class_getitem__
方法中。在 PEP 560
如果您只是想创建通用 类 或函数,请尝试查看 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 的类型检查器 可以 理解和使用的自定义 class/metaclass 东西。
- 以某种方式修改您正在使用的类型检查器(例如,mypy 有一个实验性 "plugin" 系统)
- 请求修改 PEP 484 以包含您的新自定义类型(您可以通过在 typing module repo 中打开一个问题来完成此操作)。
我想根据@jsbueno 的回答提出改进的解决方案。现在我们的“泛型”可以用于比较和身份检查,它们的行为就像打字时的“真正”泛型。我们也可以禁止 non-typed class 本身的实例化。而且!我们 isinstance
免费检查!
同时满足 BaseMetaMixin
class 完美的静态类型检查!
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