Python 3.8中的新类型注释(协议,最终,TypedDict,文字)

Python 3.8今晚问世 ,类型注释具有新功能:


  • 通讯协定
  • 打字词典
  • 最终说明者
  • 固定值匹配

如果您还不熟悉类型注释,建议您注意我以前的文章( begincontinuation
虽然每个人都在担心海象,但我想简要地谈一谈打字模块中的最新知识


通讯协定


Python使用鸭子类型,并且不需要像某些其他语言那样从某个接口继承类。
不幸的是,在3.8版之前,我们无法使用类型注释来表达对对象的必要要求。
PEP 544旨在解决此问题。


诸如“迭代器协议”或“描述符协议”之类的术语已经很熟悉并且已经使用了很长时间。
现在,您可以以代码形式描述协议,并在静态分析阶段检查其符合性。


值得注意的是,从Python 3.6开始,键入模块已经包含了几种标准协议。
例如, SupportsInt (需要__int__方法), SupportsBytes (需要__bytes__ )等。


协议说明


协议被描述为从协议继承的常规类。 它可以具有方法(包括具有实现的方法)和字段。
实现该协议的实际类可以从该协议继承,但这不是必需的。


 from abc import abstractmethod from typing import Protocol, Iterable class SupportsRoar(Protocol): @abstractmethod def roar(self) -> None: raise NotImplementedError class Lion(SupportsRoar): def roar(self) -> None: print("roar") class Tiger: def roar(self) -> None: print("roar") class Cat: def meow(self) -> None: print("meow") def roar_all(bigcats: Iterable[SupportsRoar]) -> None: for t in bigcats: t.roar() roar_all([Lion(), Tiger()]) # ok roar_all([Cat()]) # error: List item 0 has incompatible type "Cat"; expected "SupportsRoar" 

我们可以使用继承来组合协议,创建新协议。
但是,在这种情况下,您还必须显式指定Protocol作为父类。


 class BigCatProtocol(SupportsRoar, Protocol): def purr(self) -> None: print("purr") 

泛型,自类型,可调用


协议与常规类一样,可以是泛型的。 无需指定ProtocolGeneric[T, S,...]作为父代Generic[T, S,...]只需指定Protocol[T, S,...]


协议的另一重要类型是自类型的(请参阅PEP 484 )。 举个例子


 C = TypeVar('C', bound='Copyable') class Copyable(Protocol): def copy(self: C) -> C: class One: def copy(self) -> 'One': ... 

另外,可Callable注释语法不够时可以使用协议。
只需使用所需签名的__call__方法描述协议


运行时检查


尽管这些协议主要是为静态分析器设计的,但是有时还是需要检查该类是否属于所需的协议。
为此,请将@runtime_checkable装饰器应用于协议, isinstance / issubclass检查将开始检查对协议的符合性


但是,此功能有许多使用限制。 特别是,不支持泛型


打字词典


通常使用类(特别是数据类 )或命名元组来表示结构化数据。
但是有时,例如,在使用json结构描述的情况下,使用带有某些键的字典会很有用。
PEP 589引入了TypedDict的概念,该概念以前在mypy的扩展中可用


像数据类或类型化元组一样,有两种声明类型化字典的方法。 通过继承或使用工厂:


 class Book(TypedDict): title: str author: str AlsoBook = TypedDict("AlsoBook", {"title": str, "author": str}) # same as Book book: Book = {"title": "Fareneheit 481", "author": "Bradbury"} # ok other_book: Book = {"title": "Highway to Hell", "artist": "AC/DC"} # error: Extra key 'artist' for TypedDict "Book" another_book: Book = {"title": "Fareneheit 481"} # error: Key 'author' missing for TypedDict "Book" 

类型字典支持继承:


 class BookWithDesc(Book): desc: str 

默认情况下,所有字典键都是必需的,但是您可以在创建类时通过传递total=False来禁用此功能。
这仅适用于当前票房中描述的键,并不影响继承的键


 class SimpleBook(TypedDict, total=False): title: str author: str simple_book: SimpleBook = {"title": "Fareneheit 481"} # ok 

使用TypedDict有许多限制。 特别是:


  • 不支持通过isinstance在运行时检查
  • 键必须是文字或最终值

此外,此类字典禁止使用.cleardel这样不安全的操作。
也可以禁止对非文字键进行操作,因为在这种情况下无法确定期望的值类型


最终修饰符


PEP 591出于多种目的引入了最终修饰符(作为装饰器和注释)


  • 指定无法继承的类:

 from typing import final @final class Childfree: ... class Baby(Childfree): # error: Cannot inherit from final class "Childfree" ... 

  • 禁止覆盖的方法的名称:

 from typing import final class Base: @final def foo(self) -> None: ... class Derived(Base): def foo(self) -> None: # error: Cannot override final attribute "foo" (previously declared in base class "Base") ... 

  • 禁止重新分配变量(功能参数。类字段)的名称。

 ID: Final[float] = 1 ID = 2 # error: Cannot assign to final name "ID" SOME_STR: Final = "Hello" SOME_STR = "oops" # error: Cannot assign to final name "SOME_STR" letters: Final = ['a', 'b'] letters.append('c') # ok class ImmutablePoint: x: Final[int] y: Final[int] # error: Final name must be initialized with a value def __init__(self) -> None: self.x = 1 # ok ImmutablePoint().x = 2 # error: Cannot assign to final attribute "x" 

在这种情况下,形式为self.id: Final = 123的代码,但仅在__init__方法中


文字


Literal需要在字面上检查特定值时,使用PEP 586中定义Literal类型


例如, Literal[42]表示只应将42作为值。
重要的是,不仅要检查值的相等性,还要检查其类型(例如,如果期望为0,则将无法使用False)。


 def give_me_five(x: Literal[5]) -> None: pass give_me_five(5) # ok give_me_five(5.0) # error: Argument 1 to "give_me_five" has incompatible type "float"; expected "Literal[5]" give_me_five(42) # error: Argument 1 to "give_me_five" has incompatible type "Literal[42]"; expected "Literal[5]" 

在这种情况下,可以在括号中传送几个值,这等效于使用“联合”(值的类型可能不一致)。


表达式(例如Literal[1+2] )或可变类型的值不能用作值。


作为一个有用的示例,使用Literalopen()函数,该函数需要特定的mode值。


运行时的类型处理


如果您想在程序运行时处理各种类型的信息(例如 ),
get_origin和get_args函数现在可用。


因此,对于类型为X[Y, Z,...]类型X[Y, Z,...]类型X将作为原点返回,而(Y, Z, ...)作为参数返回
值得注意的是,如果X是内置类型的别名或collections模块中的类型的别名,则它将替换为原始类型。


 assert get_origin(Dict[str, int]) is dict assert get_args(Dict[int, str]) == (int, str) assert get_origin(Union[int, str]) is Union assert get_args(Union[int, str]) == (int, str) 

不幸的是, __parameters__函数没有


参考文献


Source: https://habr.com/ru/post/zh-CN470774/


All Articles