代码之家  ›  专栏  ›  技术社区  ›  shadowtalker

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

  •  18
  • shadowtalker  · 技术社区  · 8 年前

    我想用Python创建自己的参数化类型,用于类型暗示:

    class MaybeWrapped:
        # magic goes here
    
    T = TypeVar('T')
    
    assert MaybeWrapped[T] == Union[T, Tuple[T]]
    

    example re-implementation of Mapping[KT,VT] that inherits from Generic . 但这个例子更多的是关于 __getitem__ 方法而不是类本身。

    3 回复  |  直到 8 年前
        1
  •  14
  •   Michael0x2a    8 年前

    如果您只是尝试创建泛型类或函数,请查看 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))
    

    然而,如果您试图用真正新的语义构造泛型类型,那么很可能是运气不好。您剩下的选择是:

    1. 构造某种符合PEP 484的类型检查器的自定义类/元类 可以
    2. 申请修改PEP 484以包括新的自定义类型(您可以通过在 typing module repo
        2
  •  6
  •   jsbueno    5 年前

    __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
    

    使现代化 __class_getitem__ 它就是为了这个目的而创建的:它作为一个类方法,避免了仅在这种情况下就需要一个元类。无论在一个 metaclass.__getitem__ 可以放在 cls.__class_getitem__ PEP 560

        3
  •  3
  •   Anton Ovsyannikov    5 年前

    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