跳转至

协议和结构子类型

Python 类型系统支持两种确定两个对象作为类型是否兼容的方法:名义子类型和结构子类型。

名义上的子类型严格基于类层次结构。 如果类“Dog”继承类“Animal”,那么它是“Animal”的子类型。 当需要“Animal”实例时,可以使用“Dog”实例。 这种形式的子类型化是 Python 类型系统主要使用的:它很容易理解并生成清晰简洁的错误消息,并且与本机 {py:func}isinstance 检查的工作方式相匹配 - 基于类层次结构。

结构子类型基于可以对对象执行的操作。 如果类“Dog”具有类“Animal”的所有属性和方法,并且具有兼容的类型,则类“Dog”是类“Animal”的结构子类型。

结构子类型可以看作是鸭子类型的静态等价物,这是 Python 程序员所熟知的。 有关 Python 中协议和结构子类型的详细规范,请参阅 PEP 544

Protocols and structural subtyping

The Python type system supports two ways of deciding whether two objects are compatible as types: nominal subtyping and structural subtyping.

Nominal subtyping is strictly based on the class hierarchy. If class Dog inherits class Animal, it's a subtype of Animal. Instances of Dog can be used when Animal instances are expected. This form of subtyping subtyping is what Python's type system predominantly uses: it's easy to understand and produces clear and concise error messages, and matches how the native isinstance check works -- based on class hierarchy.

Structural subtyping is based on the operations that can be performed with an object. Class Dog is a structural subtype of class Animal if the former has all attributes and methods of the latter, and with compatible types.

Structural subtyping can be seen as a static equivalent of duck typing, which is well known to Python programmers. See PEP 544 for the detailed specification of protocols and structural subtyping in Python.

预定义协议

typing 模块定义了与常见 Python 协议相对应的各种协议类,例如 Iterable[T]。 如果一个类定义了合适的 __iter__ 方法,mypy 就会理解它实现了可迭代协议并且与 Iterable[T] 兼容。 例如,下面的IntList是可迭代的,类型是int值:

from typing import Iterator, Iterable, Optional

class IntList:
    def __init__(self, value: int, next: Optional['IntList']) -> None:
        self.value = value
        self.next = next

    def __iter__(self) -> Iterator[int]:
        current = self
        while current:
            yield current.value
            current = current.next

def print_numbered(items: Iterable[int]) -> None:
    for n, x in enumerate(items):
        print(n + 1, x)

x = IntList(3, IntList(5, None))
print_numbered(x)  # OK
print_numbered([4, 5])  # Also OK

predefined_protocols_reference 列出了 typing 中定义的所有协议以及您需要定义以实现每个协议的相应方法的签名。

Predefined protocols

The typing module defines various protocol classes that correspond to common Python protocols, such as Iterable[T]. If a class defines a suitable __iter__ method, mypy understands that it implements the iterable protocol and is compatible with Iterable[T]. For example, IntList below is iterable, over int values:

from typing import Iterator, Iterable, Optional

class IntList:
    def __init__(self, value: int, next: Optional['IntList']) -> None:
        self.value = value
        self.next = next

    def __iter__(self) -> Iterator[int]:
        current = self
        while current:
            yield current.value
            current = current.next

def print_numbered(items: Iterable[int]) -> None:
    for n, x in enumerate(items):
        print(n + 1, x)

x = IntList(3, IntList(5, None))
print_numbered(x)  # OK
print_numbered([4, 5])  # Also OK

predefined_protocols_reference lists all protocols defined in typing and the signatures of the corresponding methods you need to define to implement each protocol.

简单的自定义协议

您可以通过继承特殊的 Protocol 类来定义自己的协议类:

from typing import Iterable
from typing_extensions import Protocol

class SupportsClose(Protocol):
    #空方法体 (explicit '...')
    def close(self) -> None: ...

class Resource:  # No SupportsClose base class!

    def close(self) -> None:
        self.resource.release()

    # ... 其他方法 ...

def close_all(items: Iterable[SupportsClose]) -> None:
    for item in items:
        item.close()

close_all([Resource(), open('some/file')])  # OK

ResourceSupportsClose 协议的子类型,因为它定义了兼容的 close 方法。 open 返回的常规文件对象同样与协议兼容,因为它们支持 close()

Simple user-defined protocols

You can define your own protocol class by inheriting the special Protocol class:

from typing import Iterable
from typing_extensions import Protocol

class SupportsClose(Protocol):
    # Empty method body (explicit '...')
    def close(self) -> None: ...

class Resource:  # No SupportsClose base class!

    def close(self) -> None:
    self.resource.release()

    # ... other methods ...

def close_all(items: Iterable[SupportsClose]) -> None:
    for item in items:
        item.close()

close_all([Resource(), open('some/file')])  # OK

Resource is a subtype of the SupportsClose protocol since it defines a compatible close method. Regular file objects returned by open are similarly compatible with the protocol, as they support close().

定义子协议和子类化协议

您还可以定义子协议。 可以使用多重继承来扩展和合并现有协议。 例子:

# ... 继续上一个示例

class SupportsRead(Protocol):
    def read(self, amount: int) -> bytes: ...

class TaggedReadableResource(SupportsClose, SupportsRead, Protocol):
    label: str

class AdvancedResource(Resource):
    def __init__(self, label: str) -> None:
        self.label = label

    def read(self, amount: int) -> bytes:
        # some implementation
        ...

resource: TaggedReadableResource
resource = AdvancedResource('handle with care')  # OK

请注意,从现有协议继承不会自动将子类转换为协议 - 它只是创建一个实现给定协议(或多个协议)的常规(非协议)类或 ABC。 如果您定义协议,Protocol 基类必须始终显式存在

class NotAProtocol(SupportsClose):  # 这不是一个协议
    new_attr: int

class Concrete:
new_attr: int = 0

def close(self) -> None:
    ...

# Error: nominal subtyping used by default
# Error: 默认使用的名义子类型
x: NotAProtocol = Concrete()  # Error!

您还可以在协议中包含方法的默认实现。 如果您明确地子类化这些协议,您可以继承这些默认实现。

显式包含协议作为基类也是记录您的类实现特定协议的一种方式,并且它强制 mypy 验证您的类实现实际上与该协议兼容。 特别是,省略属性或方法体的值将使其隐式抽象:

class SomeProto(Protocol):
    attr: int  # 注意,没有右手边 (Note, no right hand side)
    def method(self) -> str: ...  # 从字面上看只是...这里 (Literally just ... here)

class ExplicitSubclass(SomeProto):
    pass

ExplicitSubclass()  # error: Cannot instantiate abstract class 'ExplicitSubclass'
                    # with abstract attributes 'attr' and 'method'
                    # error:无法使用抽象属性“attr”和“method”实例化抽象类“ExplicitSubclass”

类似地,显式分配给协议实例可以是要求类型检查器验证您的类是否实现协议的一种方法:

_proto: SomeProto = cast(ExplicitSubclass, None)

Defining subprotocols and subclassing protocols

You can also define subprotocols. Existing protocols can be extended and merged using multiple inheritance. Example:

# ... continuing from the previous example

class SupportsRead(Protocol):
    def read(self, amount: int) -> bytes: ...

class TaggedReadableResource(SupportsClose, SupportsRead, Protocol):
    label: str

class AdvancedResource(Resource):
    def __init__(self, label: str) -> None:
        self.label = label

    def read(self, amount: int) -> bytes:
        # some implementation
        ...

resource: TaggedReadableResource
resource = AdvancedResource('handle with care')  # OK

Note that inheriting from an existing protocol does not automatically turn the subclass into a protocol -- it just creates a regular (non-protocol) class or ABC that implements the given protocol (or protocols). The Protocol base class must always be explicitly present if you are defining a protocol:

class NotAProtocol(SupportsClose):  # This is NOT a protocol
    new_attr: int

class Concrete:
new_attr: int = 0

def close(self) -> None:
    ...

# Error: nominal subtyping used by default
x: NotAProtocol = Concrete()  # Error!

You can also include default implementations of methods in protocols. If you explicitly subclass these protocols you can inherit these default implementations.

Explicitly including a protocol as a base class is also a way of documenting that your class implements a particular protocol, and it forces mypy to verify that your class implementation is actually compatible with the protocol. In particular, omitting a value for an attribute or a method body will make it implicitly abstract:

class SomeProto(Protocol):
    attr: int  # Note, no right hand side
    def method(self) -> str: ...  # Literally just ... here

class ExplicitSubclass(SomeProto):
    pass

ExplicitSubclass()  # error: Cannot instantiate abstract class 'ExplicitSubclass'
                    # with abstract attributes 'attr' and 'method'

Similarly, explicitly assigning to a protocol instance can be a way to ask the type checker to verify that your class implements a protocol:

_proto: SomeProto = cast(ExplicitSubclass, None)

协议属性的不变性

协议的一个常见问题是协议属性是不变的。 例如:

class Box(Protocol):
    content: object

class IntBox:
    content: int

def takes_box(box: Box) -> None: ...

takes_box(IntBox())  # error: Argument 1 to "takes_box" has incompatible type (不兼容类型) "IntBox"; expected "Box"
                    # note:  Following member(s) of "IntBox" have conflicts:
                    # note:      content: expected "object", got "int"

这是因为 Boxcontent 定义为可变属性。 这就是为什么这是有问题的:

def takes_box_evil(box: Box) -> None:
    box.content = "asdf"  # 这很糟糕,因为 box.content 应该是一个对象

my_int_box = IntBox()
takes_box_evil(my_int_box)
my_int_box.content + 1  # Oops, TypeError!

可以通过使用 @propertyBox 协议中将 content 声明为只读来解决此问题:

class Box(Protocol):
    @property
    def content(self) -> object: ...

class IntBox:
    content: int

def takes_box(box: Box) -> None: ...

takes_box(IntBox(42))  # OK

Invariance of protocol attributes

A common issue with protocols is that protocol attributes are invariant. For example:

class Box(Protocol):
    content: object

class IntBox:
    content: int

def takes_box(box: Box) -> None: ...

takes_box(IntBox())  # error: Argument 1 to "takes_box" has incompatible type "IntBox"; expected "Box"
                    # note:  Following member(s) of "IntBox" have conflicts:
                    # note:      content: expected "object", got "int"

This is because Box defines content as a mutable attribute. Here's why this is problematic:

def takes_box_evil(box: Box) -> None:
    box.content = "asdf"  # This is bad, since box.content is supposed to be an object

my_int_box = IntBox()
takes_box_evil(my_int_box)
my_int_box.content + 1  # Oops, TypeError!

This can be fixed by declaring content to be read-only in the Box protocol using @property:

class Box(Protocol):
    @property
    def content(self) -> object: ...

class IntBox:
    content: int

def takes_box(box: Box) -> None: ...

takes_box(IntBox(42))  # OK

递归协议

协议可以是递归的(自引用)和相互递归的。 这对于声明抽象递归集合(例如树和链表)很有用:

from typing import TypeVar, Optional
from typing_extensions import Protocol

class TreeLike(Protocol):
    value: int

    @property
    def left(self) -> Optional['TreeLike']: ...

    @property
    def right(self) -> Optional['TreeLike']: ...

class SimpleTree:
    def __init__(self, value: int) -> None:
        self.value = value
        self.left: Optional['SimpleTree'] = None
        self.right: Optional['SimpleTree'] = None

root: TreeLike = SimpleTree(0)  # OK

Recursive protocols

Protocols can be recursive (self-referential) and mutually recursive. This is useful for declaring abstract recursive collections such as trees and linked lists:

from typing import TypeVar, Optional
from typing_extensions import Protocol

class TreeLike(Protocol):
    value: int

    @property
    def left(self) -> Optional['TreeLike']: ...

    @property
    def right(self) -> Optional['TreeLike']: ...

class SimpleTree:
    def __init__(self, value: int) -> None:
        self.value = value
        self.left: Optional['SimpleTree'] = None
        self.right: Optional['SimpleTree'] = None

root: TreeLike = SimpleTree(0)  # OK

isinstance() 和协议一起使用

如果使用@runtime_checkable类装饰器装饰协议类,则可以将其与 isinstance 一起使用。 装饰器添加了对运行时结构检查的基本支持:

from typing_extensions import Protocol, runtime_checkable

@runtime_checkable
class Portable(Protocol):
    handles: int

class Mug:
    def __init__(self) -> None:
        self.handles = 1

def use(handles: int) -> None: ...

mug = Mug()
if isinstance(mug, Portable):  # Works at runtime!
use(mug.handles)

isinstance 也适用于 预定义协议typing 中,例如 Iterable

Warning

使用协议的 isinstance 在运行时并不完全安全。 例如,不检查方法的签名。 运行时实现仅检查所有协议成员是否存在,而不检查它们是否具有正确的类型。 使用协议的 issubclass 只会检查方法是否存在。

Note

使用协议的 isinstance 也可能慢得惊人。 在许多情况下,使用 hasattr 来检查属性是否存在会更好。

Using isinstance() with protocols

You can use a protocol class with isinstance() if you decorate it with the @runtime_checkable class decorator. The decorator adds rudimentary support for runtime structural checks:

from typing_extensions import Protocol, runtime_checkable

@runtime_checkable
class Portable(Protocol):
    handles: int

class Mug:
    def __init__(self) -> None:
        self.handles = 1

def use(handles: int) -> None: ...

mug = Mug()
if isinstance(mug, Portable):  # Works at runtime!
use(mug.handles)

isinstance() also works with the predefined protocols in typing such as Iterable.

Warning

使用协议的 isinstance 在运行时并不完全安全。 例如,不检查方法的签名。 运行时实现仅检查所有协议成员是否存在,而不检查它们是否具有正确的类型。 使用协议的 issubclass 只会检查方法是否存在。

Note

使用协议的 isinstance 也可能慢得惊人。 在许多情况下,使用 hasattr 来检查属性是否存在会更好。

回调协议

协议可用于定义灵活的回调类型,这些类型很难(甚至不可能)使用 [Callable[...]](https://docs.python.org/3/library/typing.html#types.Callable)语法,例如可变参数、重载和复杂的泛型回调。 它们是用特殊的 __call__ 成员定义的:

from typing import Optional, Iterable
from typing_extensions import Protocol

class Combiner(Protocol):
    def __call__(self, *vals: bytes, maxlen: Optional[int] = None) -> list[bytes]: ...

def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes:
    for item in data:
        ...

def good_cb(*vals: bytes, maxlen: Optional[int] = None) -> list[bytes]:
    ...
def bad_cb(*vals: bytes, maxitems: Optional[int]) -> list[bytes]:
    ...

batch_proc([], good_cb)  # OK
batch_proc([], bad_cb)   # Error! Argument 2 has incompatible type because of
                        # different name and kind in the callback

回调协议和 typing.Callable 大部分类型可以互换使用。 __call__ 方法中的参数名称必须相同,除非使用双下划线前缀。 例如:

from typing import Callable, TypeVar
from typing_extensions import Protocol

T = TypeVar('T')

class Copy(Protocol):
    def __call__(self, __origin: T) -> T: ...

copy_a: Callable[[T], T]
copy_b: Copy

copy_a = copy_b  # OK
copy_b = copy_a  # Also OK

Callback protocols

Protocols can be used to define flexible callback types that are hard (or even impossible) to express using the Callable[...] syntax, such as variadic, overloaded, and complex generic callbacks. They are defined with a special __call__ member:

from typing import Optional, Iterable
from typing_extensions import Protocol

class Combiner(Protocol):
    def __call__(self, *vals: bytes, maxlen: Optional[int] = None) -> list[bytes]: ...

def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes:
    for item in data:
        ...

def good_cb(*vals: bytes, maxlen: Optional[int] = None) -> list[bytes]:
    ...
def bad_cb(*vals: bytes, maxitems: Optional[int]) -> list[bytes]:
    ...

batch_proc([], good_cb)  # OK
batch_proc([], bad_cb)   # Error! Argument 2 has incompatible type because of
                        # different name and kind in the callback

Callback protocols and typing.Callable types can be used mostly interchangeably. Argument names in __call__ methods must be identical, unless a double underscore prefix is used. For example:

from typing import Callable, TypeVar
from typing_extensions import Protocol

T = TypeVar('T')

class Copy(Protocol):
    def __call__(self, __origin: T) -> T: ...

copy_a: Callable[[T], T]
copy_b: Copy

copy_a = copy_b  # OK
copy_b = copy_a  # Also OK

预定义协议参考

迭代协议

Iteration protocols

迭代协议在许多情况下都很有用。 例如,它们允许在 for 循环中迭代对象。

The iteration protocols are useful in many contexts. For example, they allow iteration of objects in for loops.

可迭代泛型

Iterable[T]

上面的例子有一个 __iter__ 方法的简单实现。

def __iter__(self) -> Iterator[T]

另请参阅typing.Iterable

The example above has a simple implementation of an __iter__ method.

def __iter__(self) -> Iterator[T]

See also typing.Iterable.

迭代器泛型

Iterator[T]

def __next__(self) -> T
def __iter__(self) -> Iterator[T]

也可以看看 typing.Iterator.

def __next__(self) -> T
def __iter__(self) -> Iterator[T]

See also typing.Iterator.

集合协议

Collection protocols

其中许多是通过内置容器类型实现的,例如 list 和 [dict](https:// docs.python.org/3/library/stdtypes.html#dict),这些对于用户定义的集合对象也很有用。

Collection protocols

Many of these are implemented by built-in container types such as list and dict, and these are also useful for user-defined collection objects.

Sized

这是支持 len(x) 的对象类型。

def __len__(self) -> int

也可以看看 typing.Sized.

This is a type for objects that support len(x).

def __len__(self) -> int

See also typing.Sized.

泛型容器

Container[T]

这是支持 in 运算符的对象类型。

def __contains__(self, x: object) -> bool

同样参考 Container.

This is a type for objects that support the in operator.

def __contains__(self, x: object) -> bool

See also Container.

集合泛型

Collection[T]

def __len__(self) -> int
def __iter__(self) -> Iterator[T]
def __contains__(self, x: object) -> bool

同样参考 Collection.

def __len__(self) -> int
def __iter__(self) -> Iterator[T]
def __contains__(self, x: object) -> bool

See also Collection.

一次性协议

One-off protocols

这些协议通常仅适用于单个标准库函数或类。

These protocols are typically only useful with a single standard library function or class.

倒序泛型

Reversible[T]

这是支持 reversed(x) 的对象类型。

def __reversed__(self) -> Iterator[T]

同样参考 Reversible.

This is a type for objects that support reversed(x).

def __reversed__(self) -> Iterator[T]

See also Reversible.

绝对值泛型

SupportsAbs[T]

这是支持 abs(x) 的对象类型。 Tabs(x) 返回的值的类型。

def __abs__(self) -> T

同样参考 SupportsAbs.

This is a type for objects that support abs(x). T is the type of value returned by abs(x).

def __abs__(self) -> T

See also SupportsAbs.

支持字节

SupportsBytes

这是支持 bytes(x) 的对象类型。

def __bytes__(self) -> bytes

另请参阅 SupportsBytes

This is a type for objects that support bytes(x).

def __bytes__(self) -> bytes

See also SupportsBytes.

支持复数

SupportsComplex

这是支持 complex(x) 的对象类型。 请注意,不支持算术运算。

def __complex__(self) -> complex

另请参阅 SupportsComplex.

This is a type for objects that support complex(x). Note that no arithmetic operations are supported.

def __complex__(self) -> complex

See also SupportsComplex.

支持浮点数

SupportsFloat

这是支持 float(x) 的对象类型。 请注意,不支持算术运算。

def __float__(self) -> float

另请参阅 SupportsFloat.

This is a type for objects that support float(x). Note that no arithmetic operations are supported.

def __float__(self) -> float

See also SupportsFloat.

支持整数

SupportsInt

这是支持 int(x) 的对象类型。 请注意,不支持算术运算。

def __int__(self) -> int

另请参阅 SupportsInt.

This is a type for objects that support int(x). Note that no arithmetic operations are supported.

def __int__(self) -> int

See also SupportsInt.

支持Round泛型

SupportsRound[T]

这是支持 round(x) 的对象类型。

def __round__(self) -> T

另请参阅 SupportsRound.

This is a type for objects that support round(x).

def __round__(self) -> T

See also SupportsRound.

异步协议

Async protocols

这些协议在异步代码中很有用。 请参阅 async-and-await 了解更多信息。

These protocols can be useful in async code. See async-and-await for more information.

可等待泛型对象

Awaitable[T]

def __await__(self) -> Generator[Any, None, T]

另请参阅 Awaitable.

def __await__(self) -> Generator[Any, None, T]

See also Awaitable.

异步可迭代泛型对象

AsyncIterable[T]

def __aiter__(self) -> AsyncIterator[T]

另请参阅 AsyncIterable.

def __aiter__(self) -> AsyncIterator[T]

See also AsyncIterable.

异步迭代器泛型对象

AsyncIterator[T]

def __anext__(self) -> Awaitable[T]
def __aiter__(self) -> AsyncIterator[T]

另请参阅 AsyncIterator.

def __anext__(self) -> Awaitable[T]
def __aiter__(self) -> AsyncIterator[T]

See also AsyncIterator.

上下文管理器协议

Context manager protocols

上下文管理器有两种协议——一种用于常规上下文管理器,另一种用于异步上下文管理器。 这些允许定义可在withasync with语句中使用的对象。

There are two protocols for context managers -- one for regular context managers and one for async ones. These allow defining objects that can be used in with and async with statements.

上下文管理器泛型

ContextManager[T]

def __enter__(self) -> T
def __exit__(self,
            exc_type: Optional[Type[BaseException]],
            exc_value: Optional[BaseException],
            traceback: Optional[TracebackType]) -> Optional[bool]

另请参阅 ContextManager.

def __enter__(self) -> T
def __exit__(self,
            exc_type: Optional[Type[BaseException]],
            exc_value: Optional[BaseException],
            traceback: Optional[TracebackType]) -> Optional[bool]

See also ContextManager.

异步上下文泛型管理器

AsyncContextManager[T]

def __aenter__(self) -> Awaitable[T]
def __aexit__(self,
            exc_type: Optional[Type[BaseException]],
            exc_value: Optional[BaseException],
            traceback: Optional[TracebackType]) -> Awaitable[Optional[bool]]

另请参阅 AsyncContextManager

def __aenter__(self) -> Awaitable[T]
def __aexit__(self,
            exc_type: Optional[Type[BaseException]],
            exc_value: Optional[BaseException],
            traceback: Optional[TracebackType]) -> Awaitable[Optional[bool]]

See also AsyncContextManager.


最后更新: 2023年7月29日
创建日期: 2023年7月6日