# 18.4.`selectors` — 高层 I/O 多路复用

3.4 版新加入。

源码︰Lib/selectors.py

## 18.4.1. 介绍

这个模块允许高层高效的 I/O 多路复用，建立在 `选择` 模块原函数基础之上。鼓励用户使用此模块，除非他们想精确控制系统级别原函数的使用。

它定义了 `BaseSelectorr` 抽象基类，以及几种具体的实现 （`KqueueSelectorr`， `EpollSelectorr`...），可用多个文件对象上等待 I/O 准备就绪的通知。在下文中，"文件对象"指任何有 `fileno()` 方法或原始文件描述符的对象。请参阅 文件对象。

`DefaultSelector` 是当前平台上可用的最有效实现的别名︰ 这应该是大多数用户的默认选择。

注意

支持的文件对象的类型取决于平台︰ 在 Windows 上，支持套接字，但不支持管道，而在 Unix 上，两者都受支持 （某些其他类型可能也支持，比如 fifo 或特殊文件设备）。

令请参阅

`select`
  低层 I/O 多路复用模块。

## 18.4.2. 类

类层次结构︰

    BaseSelector
    +-- SelectSelector
    +-- PollSelector
    +-- EpollSelector
    +-- DevpollSelector
    +-- KqueueSelector

在下文中，事件 是指那些等待I/O 事件的给定的文件对象的位掩码。它可以是下面的模块常量的组合︰

> Constant | Meaning
> --- | ---
> `EVENT_READ` | Available for read
> `EVENT_WRITE` | Available for write

class `selectors.``SelectorKey`

  `SelectorKey` 是一个用来将文件对象关联到其底层文件描述符，选定的事件掩码和附加的数据的 `namedtuple`。它由 `BaseSelector` 的几种方法返回。

  `fileobj`

    注册的文件对象。

  `fd`

    底层的文件描述符。

  `events`

    该文件对象必须等待的事件。

  `data`

    可选的与此文件对象相关联的不透明数据︰ 例如，这可以用来存储每个客户端的会话 id。

class `selectors.``BaseSelector`

  `BaseSelector` 用来等待多个文件对象的I/O 事件的准备。它支持文件流注册、 注销和带可选超时值的等待这些流的 I/O 事件的方法。它是一个抽象的基类，因此不能实例化。使用 `DefaultSelector`来替代 或者用 `SelectSelector`，`KqueueSelector` 等。如果您想要明确使用一种实现，和您的平台支持它。`BaseSelector` 和其具体的实现支持 上下文管理器 协议。

  abstractmethod `register`(fileobj, events, data=None)

    注册一个文件对象到选择器来监视它的 I/O 事件。

    fileobj 是要监视的文件对象。它可能是一个整型文件描述符或有 `fileno()` 方法的对象。events 是要监视的事件的位掩码。data 是不透明的对象。

    这返回一个新的 `SelectorKey` 实例，或者因无效的事件掩码或文件描述符抛出 `ValueError` 错误，或者如果文件对象已注册则抛出 `KeyError` 错误。

  abstractmethod `unregister`(fileobj)

    从选择器注销一个文件对象并移除对它的监视。文件对象被注销前应先关闭。

    fileobj 必须是先前注册过的文件对象。

    这返回关联的 `SelectorKey` 实例，或者如果 fileobj未注册则抛出 `KeyError`错误。如果 fileobj 无效则抛出`ValueError`错误（例如，它没有 `fileno()` 方法或其 `fileno()` 方法有无效的返回值）。

  `modify`(fileobj, events, data=None)

    更改已注册的文件对象的监视事件或附加数据。

    这就相当于先 `BaseSelector.unregister(fileobj)()` 再 `BaseSelector.register (fileobj, events, data)()`，只是，它可以更有效地执行。

    这返回一个新的 `SelectorKey` 实例，或者因无效的事件掩码或文件描述符抛出 `ValueError` 错误，或者如果文件对象已注册则抛出 `KeyError` 错误。

  abstractmethod `select`(timeout=None)

    等到一些已注册的文件对象准备好或者超时。

    如果 `timeout > 0`，会指定最长的等待时间，以秒为单位。如果 `timeout < = 0`，调用不会阻塞并会报告目前准备好的文件对象。如果 timeout 是 `None`，调用将会阻塞直到一个监视的文件对象准备好。

    这将返回一个以 `(key, events)` 元组为元素的列表，每一个元祖代表一个准备好的文件对象。

    key 是对应于一个准备好的文件对象的`SelectorKey` 实例。events 是该文件对象上准备好的事件位掩码。

    注意

    如果当前进程接收到信号，此方法可以在超时前或任何文件对象准备好前返回：在这种情况下，将返回一个空列表。

    3.5 版本中的更改︰当被一个信号中断时，如果信号处理程序不抛出异常 (见 PEP 475 原因)，现在选择器再次重新计算超时，而不是在超时前返回一个空的事件列表。

  `close`()

    关闭选择器。

    这个必须调用以确保所有底层资源被释放。一旦选择器被关闭就不能再使用。

  `get_key`(fileobj)

    返回一个与已注册的文件对象关联的键值。

    这返回关联此文件对象的 `SelectorKey` 实例，或如果文件对象未注册则抛出 `KeyError`错误。

  abstractmethod `get_map`()

    返回文件对象到选择器键值的映射。

    这将返回一个已注册的文件对象到与其关联的 `SelectorKey` 实例的映射的 `Mapping` 实例。

class `selectors.``DefaultSelector`

  默认选择器类使用在当前平台上可用的最有效的实现。这应该是大多数用户的默认选择。

class `selectors.``SelectSelector`

  基于`select.select()`的选择器。

class `selectors.``PollSelector`

  基于`select.poll()`的选择器。

class `selectors.``EpollSelector`

  基于`select.epoll()`的选择器。

  `fileno`()

    这将返回底层的 `select.epoll()` 对象所使用的文件描述符。

class `selectors.``DevpollSelector`

  基于`select.devpoll()`的选择器。

  `fileno`()

    这将返回底层的 `select.devpoll()` 对象所使用的文件描述符。

  3.5版本中新加入。

class `selectors.``KqueueSelector`

  基于`select.kqueue()`的选择器。

  `fileno`()

    这将返回底层的 `select.kqueue()` 对象所使用的文件描述符。

## 18.4.3. 示例

这里是一个简单的回显服务器的实现︰

    import selectors
    import socket

    sel = selectors.DefaultSelector()

    def accept(sock, mask):
        conn, addr = sock.accept()  # Should be ready
        print('accepted', conn, 'from', addr)
        conn.setblocking(False)
        sel.register(conn, selectors.EVENT_READ, read)

    def read(conn, mask):
        data = conn.recv(1000)  # Should be ready
        if data:
            print('echoing', repr(data), 'to', conn)
            conn.send(data)  # Hope it won't block
        else:
            print('closing', conn)
            sel.unregister(conn)
            conn.close()

    sock = socket.socket()
    sock.bind(('localhost', 1234))
    sock.listen(100)
    sock.setblocking(False)
    sel.register(sock, selectors.EVENT_READ, accept)

    while True:
        events = sel.select()
        for key, mask in events:
            callback = key.data
            callback(key.fileobj, mask)