Python 3.8今晚问世 ,类型注释具有新功能:
如果您还不熟悉类型注释,建议您注意我以前的文章( begin , continuation )
虽然每个人都在担心海象,但我想简要地谈一谈打字模块中的最新知识
通讯协定
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()])
我们可以使用继承来组合协议,创建新协议。
但是,在这种情况下,您还必须显式指定Protocol作为父类。
class BigCatProtocol(SupportsRoar, Protocol): def purr(self) -> None: print("purr")
泛型,自类型,可调用
协议与常规类一样,可以是泛型的。 无需指定Protocol
和Generic[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})
类型字典支持继承:
class BookWithDesc(Book): desc: str
默认情况下,所有字典键都是必需的,但是您可以在创建类时通过传递total=False
来禁用此功能。
这仅适用于当前票房中描述的键,并不影响继承的键
class SimpleBook(TypedDict, total=False): title: str author: str simple_book: SimpleBook = {"title": "Fareneheit 481"}
使用TypedDict
有许多限制。 特别是:
- 不支持通过isinstance在运行时检查
- 键必须是文字或最终值
此外,此类字典禁止使用.clear
或del
这样不安全的操作。
也可以禁止对非文字键进行操作,因为在这种情况下无法确定期望的值类型
最终修饰符
PEP 591出于多种目的引入了最终修饰符(作为装饰器和注释)
from typing import final @final class Childfree: ... class Baby(Childfree):
from typing import final class Base: @final def foo(self) -> None: ... class Derived(Base): def foo(self) -> None:
ID: Final[float] = 1 ID = 2
在这种情况下,形式为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)
在这种情况下,可以在括号中传送几个值,这等效于使用“联合”(值的类型可能不一致)。
表达式(例如Literal[1+2]
)或可变类型的值不能用作值。
作为一个有用的示例,使用Literal
是open()
函数,该函数需要特定的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__
函数没有
参考文献