暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

类型注释进化篇

与C同行 2022-06-20
519

类型注释是python完善自身重要的一部分,以前对类型注释的理解浅尝即止,今天我们升华一下,更进一步。

语法语句

如果知道类型注释,那么一定知道最简单也是官方推荐的类型注释方法是在变量名后面用: 类型
的方式标记,并且考虑到对python原有语法改动最小的原则,并没有引入新的语法,比如: List[int]
代表的是元素为int类型的列表,这里并没有引入新的语法,而是使用了:,[int]两种原有的语法。不知道大家看到这样的语法会不会产生一种不合适的感觉,其中使用了[int]这种索引方式,仔细想一想怎么感觉这种索引从来没看过,确实有点怪异,怪异并不可怕,只要理解了为什么可以这样,所有疑惑都会迎刃而解,让我们来看看这个类型注释背后的真面目。

我们找到typing
源码,看这些内容:

    def _tp_cache(func=None, , *, typed=False):
    """Internal wrapper caching __getitem__ of generic types with a fallback to
    original function for non-hashable arguments.
    """
    def decorator(func):
    cached = functools.lru_cache(typed=typed)(func)
    _cleanups.append(cached.cache_clear)


    @functools.wraps(func)
    def inner(*args, **kwds):
    try:
    return cached(*args, **kwds)
    except TypeError:
    pass # All real errors (not unhashable args) are raised below.
    return func(*args, **kwds)
    return inner


    if func is not None:
    return decorator(func)


    return decorator

    class _Final:
    "
    ""Mixin to prohibit subclassing"""


    __slots__ = ('__weakref__',)


    def __init_subclass__(self, , *args, **kwds):
    if '_root' not in kwds:
    raise TypeError("Cannot subclass special typing classes")

    class _SpecialForm(_Final, _root=True):
    __slots__ = ('_name', '__doc__', '_getitem')


    def __init__(self, getitem):
    self._getitem = getitem
    self._name = getitem.__name__
    self.__doc__ = getitem.__doc__


    def __mro_entries__(self, bases):
    raise TypeError(f"Cannot subclass {self!r}")


    def __repr__(self):
    return 'typing.' + self._name


    def __reduce__(self):
    return self._name


    def __call__(self, *args, **kwds):
    raise TypeError(f"Cannot instantiate {self!r}")


    def __instancecheck__(self, obj):
    raise TypeError(f"{self} cannot be used with isinstance()")


    def __subclasscheck__(self, cls):
    raise TypeError(f"{self} cannot be used with issubclass()")


    @_tp_cache
    def __getitem__(self, parameters):
    return self._getitem(self, parameters)

    来梳理一下逻辑,首先_tp_cache函数是一个装饰器,主要提供了一个缓存功能,可以理解为把一些使用过的函数缓存起来备下次使用,然后_Final类提供了一个继承子类方法,继承子类必须提供_root参数,最后_SpecialForm类继承了_Final类,其中初始化方法中提供了self._getitem
    方法,并且__getitem__方法使用了self._getitem方法。看到这里,诸位是不是有点明白了,原来通过这种手段自定义了索引方式。然后,我们找一个例子验证。比如Final,源码如下:

      @_SpecialForm
      def Final(self, parameters):
      """Special typing construct to indicate final names to type checkers.


      A final name cannot be re-assigned or overridden in a subclass.
      For example:


      MAX_SIZE: Final = 9000
      MAX_SIZE += 1 # Error reported by type checker


      class Connection:
      TIMEOUT: Final[int] = 10


      class FastConnector(Connection):
      TIMEOUT = 1 # Error reported by type checker


      There is no runtime checking of these properties.
      """
      item = _type_check(parameters, f'{self} accepts only single type.')
      return _GenericAlias(self, (item,))

      Final使用了_SpecialForm
      类进行装饰,所以Final是_SpecialForm的实例,因此Final使用索引就是使用初始化的self._getitem,初始化的self._getitem也就是上面的函数,这里做了两件事,第一,判断索引参数是不是只有一个类型,第二,返回一个_GenericAlias类实例。到这里,我们大致明白了类型注释是个什么东西了,它就是一个对象使用索引,做出错误判断,给出新的对象。

      常用类型注释

      接下来总结一下常用的类型注释:

      Any:任何类型
      NoReturn:函数无返回值的类型
      ClassVar:类属性类型
      Final:不可变变量类型
      Union:联合类型,起或的作用
      Optional:None的类型,只支持一种类型
      Literal:可选的几个值
      TypeVar:自定义类型,可结合多个类型
      Callable:可调用的变量类型,比如函数
      Tuple:元组,固定长度不同类型元素|可变长度的同一类型|可变长度的不同类型
      List:列表,同一类型|不同类型
      Dict:字典,key-value

      很多都比较简单,这里简单说一些需要注意的东西,Any可以注释任意类型,不能带索引,NoReturn注释无返回值,同样不能带索引,ClassVar注释类成员,只能带一个索引,Final注释不可变变量类型,只能带一个索引,Union注释联合类型,至少带一个索引,Optional注释可选类型,只能带一个类型,Literal注释几个值,可变索引,Callable注释可调用函数,必须包含两个索引(参数和返回值),Dict注释字典,两个索引(key,value),List注释列表,一个索引,Tuple注释元组,可变索引。

      TypeVar可以定义新的类型,特别说明一下,它主要的作用是把多个类型组成一个新的类型,比如A = TypeVar('A', str, bytes)
      ,其中在使用具体基本类型时还原,并且多个基本类型不能混用,类似于其他编程语言的泛型类型。

      特殊类型注释

      为了更广泛的使用性,python还添加了其它一些东西,我们接着了解,类似于其他编程语言的接口,python类型注释也有类似的实现。我们看一下源码提供的例子:

         Base class for protocol classes.


        Protocol classes are defined as::


        class Proto(Protocol):
        def meth(self) -> int:
        ...


        Such classes are primarily used with static type checkers that recognize
        structural subtyping (static duck-typing), for example::


        class C:
        def meth(self) -> int:
        return 0


        def func(x: Proto) -> int:
        return x.meth()


        func(C()) # Passes static type check

        还有泛型,例子如下:

          A generic type is typically declared by inheriting from
          this class parameterized with one or more type variables.
          For example, a generic mapping type might be defined as::


          class Mapping(Generic[KT, VT]):
          def __getitem__(self, key: KT) -> VT:
          ...
          # Etc.


          This class can then be used as follows::


          def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:
          try:
          return mapping[key]
          except KeyError:
          return default

          另外,可以给类型添加描述的Annotated类,其中,第一个索引是类型,其余是描述,比如Annotated[int, runtime_check.Unsigned]
          。同时还提供了SupportsInt,SupportsFloat这样的接口和IO,TextIO这样的泛型类。

          文章转载自与C同行,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论