Python-Standard Library


Python 标准库

  • 并发执行
    • threading —- 基于线程的并行
    • multiprocessing —- 基于进程的并行
    • multiprocessing.shared_memory —- 可从进程直接访问的共享内存
    • concurrent
    • concurrent.futures —- 启动并行任务
    • subprocess —- 子进程管理
    • sched —- 事件调度器
    • queue —- 一个同步的队列类
    • contextvars —- 上下文变量
    • _thread —- 底层多线程 API
  • 网络和进程间通信
    • asyncio —- 异步 I/O
    • socket —- 底层网络接口
    • ssl —- 套接字对象的 TLS/SSL 包装器
    • select —- 等待 I/O 完成
    • selectors —- 高级 I/O 复用库
    • asyncore —- 异步套接字处理器
    • asynchat —- 异步套接字指令/响应处理程序
    • signal —- 设置异步事件处理程序
    • mmap —- 内存映射文件支持
  • 互联网数据处理
    • email —- 电子邮件与 MIME 处理包
    • json —- JSON 编码和解码器
    • mailcap —- Mailcap 文件处理
    • mailbox —- 操作多种格式的邮箱
    • mimetypes —- 映射文件名到 MIME 类型
    • base64 —- Base16, Base32, Base64, Base85 数据编码
    • binhex —- 对binhex4文件进行编码和解码
    • binascii —- 二进制和 ASCII 码互转
    • quopri —- 编码与解码经过 MIME 转码的可打印数据
    • uu —- 对 uuencode 文件进行编码与解码
  • 结构化标记处理工具
    • html —- 超文本标记语言支持
    • html.parser —- 简单的 HTML 和 XHTML 解析器
    • html.entities —- HTML 一般实体的定义
    • XML处理模块
    • xml.etree.ElementTree —- ElementTree XML API
    • xml.dom —- 文档对象模型 API
    • xml.dom.minidom —- 最小化的 DOM 实现
    • xml.dom.pulldom —- 支持构建部分 DOM 树
    • xml.sax —- 支持 SAX2 解析器
    • xml.sax.handler —- SAX 处理句柄的基类
    • xml.sax.saxutils —- SAX 工具集
    • xml.sax.xmlreader —- 用于 XML 解析器的接口
    • xml.parsers.expat —- 使用 Expat 的快速 XML 解析
  • 互联网协议和支持
    • webbrowser —- 方便的 Web 浏览器控制工具
    • cgi —- 通用网关接口支持
    • cgitb —- 用于 CGI 脚本的回溯管理器
    • wsgiref —- WSGI 工具和参考实现
    • urllib —- URL 处理模块
    • urllib.request —- 用于打开 URL 的可扩展库
    • urllib.response —- urllib 使用的 Response 类
    • urllib.parse 用于解析 URL
    • urllib.error —- urllib.request 引发的异常类
    • urllib.robotparser —- robots.txt 语法分析程序
    • http —- HTTP 模块
    • http.client —- HTTP 协议客户端
    • ftplib —- FTP 协议客户端
    • poplib —- POP3 协议客户端
    • imaplib —- IMAP4 协议客户端
    • nntplib —- NNTP protocol client
    • smtplib —-SMTP协议客户端
    • smtpd —- SMTP 服务器
    • telnetlib — Telnet 客户端
    • uuid —- UUID objects according to RFC 4122
    • socketserver —- A framework for network servers
    • http.server —- HTTP 服务器
    • http.cookies —- HTTP状态管理
    • http.cookiejar —— HTTP 客户端的 Cookie 处理
    • xmlrpc —- XMLRPC 服务端与客户端模块
    • xmlrpc.client —- XML-RPC 客户端访问
    • xmlrpc.server —- 基本 XML-RPC 服务器
    • ipaddress —- IPv4/IPv6 操作库

并发执行

本章中描述的模块支持并发执行代码。 适当的工具选择取决于要执行的任务(CPU密集型或IO密集型)和偏好的开发风格(事件驱动的协作式多任务或抢占式多任务处理)。 这是一个概述:

  • threading —- 基于线程的并行
    • 线程本地数据
    • 线程对象
    • 锁对象
    • 递归锁对象
    • 条件对象
    • 信号量对象
      • Semaphore 例子
    • 事件对象
    • 定时器对象
    • 栅栏对象
    • with 语句中使用锁、条件和信号量
  • multiprocessing —- 基于进程的并行
    • 概述
      • Process
      • 上下文和启动方法
      • 在进程之间交换对象
      • 进程间同步
      • 进程间共享状态
      • 使用工作进程
    • 参考
      • Process 和异常
      • 管道和队列
      • 杂项
      • 连接对象(Connection)
      • 同步原语
      • 共享 ctypes 对象
        • multiprocessing.sharedctypes 模块
      • 管理器
        • 自定义管理器
        • 使用远程管理器
      • 代理对象
        • 清理
      • 进程池
      • 监听器及客户端
        • 地址格式
      • 认证密码
      • 日志记录
      • multiprocessing.dummy 模块
    • 编程指导
      • 所有start方法
      • spawnforkserver 启动方式
    • 例子
  • multiprocessing.shared_memory —- 可从进程直接访问的共享内存
  • concurrent
  • concurrent.futures —- 启动并行任务
    • Executor 对象
    • ThreadPoolExecutor
      • ThreadPoolExecutor 例子
    • ProcessPoolExecutor
      • ProcessPoolExecutor 例子
    • Future 对象
    • 模块函数
    • Exception 类
  • subprocess —- 子进程管理
    • 使用 subprocess 模块
      • 常用参数
      • Popen 构造函数
      • 异常
    • 安全考量
    • Popen 对象
    • Windows Popen 助手
      • Windows 常数
    • 较旧的高阶 API
    • 使用 subprocess 模块替换旧函数
      • 替代 /bin/sh shell 命令替换
      • 替代 shell 管道
      • 替代 os.system()
      • 替代 os.spawn 函数族
      • 替代 os.popen(), os.popen2(), os.popen3()
      • 来自 popen2 模块的替代函数
    • 旧式的 Shell 发起函数
    • 备注
      • 在 Windows 上将参数列表转换为一个字符串
  • sched —- 事件调度器
    • 调度器对象
  • queue —- 一个同步的队列类
    • Queue对象
    • SimpleQueue 对象
  • contextvars —- 上下文变量
    • 上下文变量
    • 手动上下文管理
    • asyncio 支持

以下是上述某些服务的支持模块:

  • _thread —- 底层多线程 API

threading —- 基于线程的并行

源代码:Lib/threading.py


这个模块在较低级的模块 _thread 基础上建立较高级的线程接口。

在 3.7 版更改: 这个模块曾经为可选项,但现在总是可用。

注解

在 Python 2.x 系列中,此模块包含有某些方法和函数 camelCase 形式的名称。 它们在 Python 3.10 中已弃用,但为了与 Python 2.5 及更旧版本的兼容性而仍受到支持。

CPython implementation detail: 在 CPython 中,由于存在 全局解释器锁,同一时刻只有一个线程可以执行 Python 代码(虽然某些性能导向的库可能会去除此限制)。 如果你想让你的应用更好地利用多核心计算机的计算资源,推荐你使用 multiprocessingconcurrent.futures.ProcessPoolExecutor。 但是,如果你想要同时运行多个 I/O 密集型任务,则多线程仍然是一个合适的模型。

这个模块定义了以下函数:

threading.active_count()

返回当前存活的 Thread 对象的数量。 返回值与 enumerate() 所返回的列表长度一致。

函数 activeCount 是此函数的已弃用别名。

threading.current_thread()

返回当前对应调用者的控制线程的 Thread 对象。如果调用者的控制线程不是利用 threading 创建,会返回一个功能受限的虚拟线程对象。

函数 currentThread 是此函数的已弃用别名。

threading.excepthook(args, /)

处理由 Thread.run() 引发的未捕获异常。

args 参数具有以下属性:

  • exc_type: 异常类型
  • exc_value: 异常值,可以是 None.
  • exc_traceback: 异常回溯,可以是 None.
  • thread: 引发异常的线程,可以为 None

如果 exc_typeSystemExit,则异常会被静默地忽略。 在其他情况下,异常将被打印到 sys.stderr

如果此函数引发了异常,则会调用 sys.excepthook() 来处理它。

threading.excepthook() 可以被重载以控制由 Thread.run() 引发的未捕获异常的处理方式。

使用定制钩子存放 exc_value 可能会创建引用循环。 它应当在不再需要异常时被显式地清空以打破引用循环。

如果一个对象正在被销毁,那么使用自定义的钩子储存 thread 可能会将其复活。请在自定义钩子生效后避免储存 thread,以避免对象的复活。

参见

sys.excepthook() 处理未捕获的异常。

3.8 新版功能.

threading.__excepthook__

保存 threading.excepthook() 的原始值。 它被保存以便在原始值碰巧被已损坏或替代对象所替换的情况下可被恢复。

3.10 新版功能.

threading.get_ident()

返回当前线程的 “线程标识符”。它是一个非零的整数。它的值没有直接含义,主要是用作 magic cookie,比如作为含有线程相关数据的字典的索引。线程标识符可能会在线程退出,新线程创建时被复用。

3.3 新版功能.

threading.get_native_id()

返回内核分配给当前线程的原生集成线程 ID。 这是一个非负整数。 它的值可被用来在整个系统中唯一地标识这个特定线程(直到线程终结,在那之后该值可能会被 OS 回收再利用)。

可用性: Windows, FreeBSD, Linux, macOS, OpenBSD, NetBSD, AIX。

3.8 新版功能.

threading.enumerate()

返回当前所有存活的 Thread 对象的列表。 该列表包括守护线程以及 current_thread() 创建的空线程。 它不包括已终结的和尚未开始的线程。 但是,主线程将总是结果的一部分,即使是在已终结的时候。

threading.main_thread()

返回主 Thread 对象。一般情况下,主线程是Python解释器开始时创建的线程。

3.4 新版功能.

threading.settrace(func)

为所有 threading 模块开始的线程设置追踪函数。在每个线程的 run() 方法被调用前,func 会被传递给 sys.settrace()

threading.gettrace()

返回由 settrace() 设置的跟踪函数。

3.10 新版功能.

threading.setprofile(func)

为所有 threading 模块开始的线程设置性能测试函数。在每个线程的 run() 方法被调用前,func 会被传递给 sys.setprofile()

threading.getprofile()

返回由 setprofile() 设置的性能分析函数。

3.10 新版功能.

threading.stack_size([size])

返回创建线程时使用的堆栈大小。可选参数 size 指定之后新建的线程的堆栈大小,而且一定要是0(根据平台或者默认配置)或者最小是32,768(32KiB)的一个正整数。如果 size 没有指定,默认是0。如果不支持改变线程堆栈大小,会抛出 RuntimeError 错误。如果指定的堆栈大小不合法,会抛出 ValueError 错误并且不会修改堆栈大小。32KiB是当前最小的能保证解释器有足够堆栈空间的堆栈大小。需要注意的是部分平台对于堆栈大小会有特定的限制,例如要求大于32KiB的堆栈大小或者需要根据系统内存页面的整数倍进行分配 - 应当查阅平台文档有关详细信息(4KiB页面比较普遍,在没有更具体信息的情况下,建议的方法是使用4096的倍数作为堆栈大小)。

适用于: Windows,具有 POSIX 线程的系统。

这个模块同时定义了以下常量:

threading.TIMEOUT_MAX

阻塞函数( Lock.acquire(), RLock.acquire(), Condition.wait(), …)中形参 timeout 允许的最大值。传入超过这个值的 timeout 会抛出 OverflowError 异常。

3.2 新版功能.

这个模块定义了许多类,详见以下部分。

该模块的设计基于 Java的线程模型。 但是,在Java里面,锁和条件变量是每个对象的基础特性,而在Python里面,这些被独立成了单独的对象。 Python 的 Thread 类只是 Java 的 Thread 类的一个子集;目前还没有优先级,没有线程组,线程还不能被销毁、停止、暂停、恢复或中断。 Java 的 Thread 类的静态方法在实现时会映射为模块级函数。

下述方法的执行都是原子性的。

线程本地数据

线程本地数据是特定线程的数据。管理线程本地数据,只需要创建一个 local (或者一个子类型)的实例并在实例中储存属性:

mydata = threading.local()
mydata.x =1

在不同的线程中,实例的值会不同。

classthreading.local

一个代表线程本地数据的类。

更多相关细节和大量示例,参见 _threading_local 模块的文档。

线程对象

The Thread class represents an activity that is run in a separate thread of control. There are two ways to specify the activity: by passing a callable object to the constructor, or by overriding the run() method in a subclass. No other methods (except for the constructor) should be overridden in a subclass. In other words, only override the __init__() and run() methods of this class.

当线程对象一但被创建,其活动一定会因调用线程的 start() 方法开始。这会在独立的控制线程调用 run() 方法。

一旦线程活动开始,该线程会被认为是 ‘存活的’ 。当它的 run() 方法终结了(不管是正常的还是抛出未被处理的异常),就不是’存活的’。 is_alive() 方法用于检查线程是否存活。

其他线程可以调用一个线程的 join() 方法。这会阻塞调用该方法的线程,直到被调用 join() 方法的线程终结。

线程有名字。名字可以传递给构造函数,也可以通过 name 属性读取或者修改。

如果 run() 方法引发了异常,则会调用 threading.excepthook() 来处理它。 在默认情况下,threading.excepthook() 会静默地忽略 SystemExit

一个线程可以被标记成一个“守护线程”。 这个标识的意义是,当剩下的线程都是守护线程时,整个 Python 程序将会退出。 初始值继承于创建线程。 这个标识可以通过 daemon 特征属性或者 daemon 构造器参数来设置。

注解

守护线程在程序关闭时会突然关闭。他们的资源(例如已经打开的文档,数据库事务等等)可能没有被正确释放。如果你想你的线程正常停止,设置他们成为非守护模式并且使用合适的信号机制,例如: Event

有个 “主线程” 对象;这对应Python程序里面初始的控制线程。它不是一个守护线程。

“虚拟线程对象” 是可以被创建的。这些是对应于“外部线程”的线程对象,它们是在线程模块外部启动的控制线程,例如直接来自C代码。虚拟线程对象功能受限;他们总是被认为是存活的和守护模式,不能被 join() 。因为无法检测外来线程的终结,它们永远不会被删除。

classthreading.Thread(group=None, target=None, name=None, args=(), kwargs={}, **, daemon=None*)

调用这个构造函数时,必需带有关键字参数。参数如下:

group 应该为 None;为了日后扩展 ThreadGroup 类实现而保留。

target 是用于 run() 方法调用的可调用对象。默认是 None,表示不需要调用任何方法。

name 是线程名称。 在默认情况下,会以 “Thread-N“ 的形式构造唯一名称,其中 N 为一个较小的十进制数值,或是 “Thread-N (target)” 的形式,其中 “target” 为 target.__name__,如果指定了 target 参数的话。

args 是用于调用目标函数的参数元组。默认是 ()

kwargs 是用于调用目标函数的关键字参数字典。默认是 {}

如果不是 Nonedaemon 参数将显式地设置该线程是否为守护模式。 如果是 None (默认值),线程将继承当前线程的守护模式属性。

如果子类型重载了构造函数,它一定要确保在做任何事前,先发起调用基类构造器(Thread.__init__())。

在 3.10 版更改: 使用 target 名称,如果 name 参数被省略的话。

在 3.3 版更改: 加入 daemon 参数。

  • start()

    开始线程活动。

    它在一个线程里最多只能被调用一次。它安排对象的 run() 方法在一个独立的控制进程中调用。

    如果同一个线程对象中调用这个方法的次数大于一次,会抛出 RuntimeError

  • run()

    代表线程活动的方法。

    你可以在子类型里重载这个方法。 标准的 run() 方法会对作为 target 参数传递给该对象构造器的可调用对象(如果存在)发起调用,并附带从 argskwargs 参数分别获取的位置和关键字参数。

  • join(timeout=None)

    等待,直到线程终结。这会阻塞调用这个方法的线程,直到被调用 join() 的线程终结 — 不管是正常终结还是抛出未处理异常 — 或者直到发生超时,超时选项是可选的。

    timeout 参数存在而且不是 None 时,它应该是一个用于指定操作超时的以秒为单位的浮点数或者分数。因为 join() 总是返回 None ,所以你一定要在 join() 后调用 is_alive() 才能判断是否发生超时 — 如果线程仍然存活,则 join() 超时。

    timeout 参数不存在或者是 None ,这个操作会阻塞直到线程终结。

    一个线程可以被 join() 很多次。

    如果尝试加入当前线程会导致死锁, join() 会引起 RuntimeError 异常。如果尝试 join() 一个尚未开始的线程,也会抛出相同的异常。

  • name

    只用于识别的字符串。它没有语义。多个线程可以赋予相同的名称。 初始名称由构造函数设置。

  • getName()

    setName()

    已被弃用的 name 的取值/设值 API;请改为直接以特征属性方式使用它。

    3.10 版后已移除.

  • ident

    这个线程的 ‘线程标识符’,如果线程尚未开始则为 None 。这是个非零整数。当一个线程退出而另外一个线程被创建,线程标识符会被复用。即使线程退出后,仍可得到标识符。

  • native_id

    此线程的线程 ID (TID),由 OS (内核) 分配。 这是一个非负整数,或者如果线程还未启动则为 None。 这个值可被用来在全系统范围内唯一地标识这个特定线程 (直到线程终结,在那之后该值可能会被 OS 回收再利用)。

    注解

    类似于进程 ID,线程 ID 的有效期(全系统范围内保证唯一)将从线程被创建开始直到线程被终结。

    可用性: 需要 get_native_id() 函数。

    3.8 新版功能.

  • is_alive()

    返回线程是否存活。

    run() 方法刚开始直到 run() 方法刚结束,这个方法返回 True 。模块函数 enumerate() 返回包含所有存活线程的列表。

  • daemon

    一个表示这个线程是(True)否(False)守护线程的布尔值。一定要在调用 start() 前设置好,不然会抛出 RuntimeError 。初始值继承于创建线程;主线程不是守护线程,因此主线程创建的所有线程默认都是 daemon = False

    当没有存活的非守护线程时,整个Python程序才会退出。

  • isDaemon()

    setDaemon()

    已被弃用的 daemon 的取值/设值 API;请改为直接以特征属性方式使用它。

    3.10 版后已移除.

锁对象

原始锁是一个在锁定时不属于特定线程的同步基元组件。在Python中,它是能用的最低级的同步基元组件,由 _thread 扩展模块直接实现。

原始锁处于 “锁定” 或者 “非锁定” 两种状态之一。它被创建时为非锁定状态。它有两个基本方法, acquire()release() 。当状态为非锁定时, acquire() 将状态改为 锁定 并立即返回。当状态是锁定时, acquire() 将阻塞至其他线程调用 release() 将其改为非锁定状态,然后 acquire() 调用重置其为锁定状态并返回。 release() 只在锁定状态下调用; 它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发 RuntimeError 异常。

锁同样支持 上下文管理协议。

当多个线程在 acquire() 等待状态转变为未锁定被阻塞,然后 release() 重置状态为未锁定时,只有一个线程能继续执行;至于哪个等待线程继续执行没有定义,并且会根据实现而不同。

所有方法的执行都是原子性的。

classthreading.Lock

实现原始锁对象的类。一旦一个线程获得一个锁,会阻塞随后尝试获得锁的线程,直到它被释放;任何线程都可以释放它。

需要注意的是 Lock 其实是一个工厂函数,返回平台支持的具体锁类中最有效的版本的实例。

  • acquire(blocking=True, timeout=- 1)

    可以阻塞或非阻塞地获得锁。

    当调用时参数 blocking 设置为 True (缺省值),阻塞直到锁被释放,然后将锁锁定并返回 True

    在参数 blocking 被设置为 False 的情况下调用,将不会发生阻塞。如果调用时 blocking 设为 True 会阻塞,并立即返回 False ;否则,将锁锁定并返回 True

    当浮点型 timeout 参数被设置为正值调用时,只要无法获得锁,将最多阻塞 timeout 设定的秒数。timeout 参数被设置为 -1 时将无限等待。当 blocking 为 false 时,timeout 指定的值将被忽略。

    如果成功获得锁,则返回 True,否则返回 False (例如发生 超时 的时候)。

    在 3.2 版更改: 新的 timeout 形参。

    在 3.2 版更改: 现在如果底层线程实现支持,则可以通过POSIX上的信号中断锁的获取。

  • release()

    释放一个锁。这个方法可以在任何线程中调用,不单指获得锁的线程。

    当锁被锁定,将它重置为未锁定,并返回。如果其他线程正在等待这个锁解锁而被阻塞,只允许其中一个允许。

    当在未锁定的锁上发起调用时,会引发 RuntimeError

    没有返回值。

  • locked()

    如果获得了锁则返回真值。

递归锁对象

重入锁是一个可以被同一个线程多次获取的同步基元组件。在内部,它在基元锁的锁定/非锁定状态上附加了 “所属线程” 和 “递归等级” 的概念。在锁定状态下,某些线程拥有锁 ; 在非锁定状态下, 没有线程拥有它。

若要锁定锁,线程调用其 acquire() 方法;一旦线程拥有了锁,方法将返回。若要解锁,线程调用 release() 方法。 acquire()/release() 对可以嵌套;只有最终 release() (最外面一对的 release() ) 将锁解开,才能让其他线程继续处理 acquire() 阻塞。

递归锁也支持 上下文管理协议。

classthreading.RLock

此类实现了重入锁对象。重入锁必须由获取它的线程释放。一旦线程获得了重入锁,同一个线程再次获取它将不阻塞;线程必须在每次获取它时释放一次。

需要注意的是 RLock 其实是一个工厂函数,返回平台支持的具体递归锁类中最有效的版本的实例。

  • acquire(blocking=True, timeout=- 1)

    可以阻塞或非阻塞地获得锁。

    当无参数调用时: 如果这个线程已经拥有锁,递归级别增加一,并立即返回。否则,如果其他线程拥有该锁,则阻塞至该锁解锁。一旦锁被解锁(不属于任何线程),则抢夺所有权,设置递归等级为一,并返回。如果多个线程被阻塞,等待锁被解锁,一次只有一个线程能抢到锁的所有权。在这种情况下,没有返回值。

    当发起调用时将 blocking 参数设为真值,则执行与无参数调用时一样的操作,然后返回 True

    当发起调用时将 blocking 参数设为假值,则不进行阻塞。 如果一个无参数调用将要阻塞,则立即返回 False;在其他情况下,执行与无参数调用时一样的操作,然后返回 True

    当发起调用时将浮点数的 timeout 参数设为正值时,只要无法获得锁,将最多阻塞 timeout 所指定的秒数。 如果已经获得锁则返回 True,如果超时则返回假值。

    在 3.2 版更改: 新的 timeout 形参。

  • release()

    释放锁,自减递归等级。如果减到零,则将锁重置为非锁定状态(不被任何线程拥有),并且,如果其他线程正被阻塞着等待锁被解锁,则仅允许其中一个线程继续。如果自减后,递归等级仍然不是零,则锁保持锁定,仍由调用线程拥有。

    只有当前线程拥有锁才能调用这个方法。如果锁被释放后调用这个方法,会引起 RuntimeError 异常。

    没有返回值。

条件对象

条件变量总是与某种类型的锁对象相关联,锁对象可以通过传入获得,或者在缺省的情况下自动创建。当多个条件变量需要共享同一个锁时,传入一个锁很有用。锁是条件对象的一部分,你不必单独地跟踪它。

条件变量遵循 上下文管理协议 :使用 with 语句会在它包围的代码块内获取关联的锁。 acquire()release() 方法也能调用关联锁的相关方法。

其它方法必须在持有关联的锁的情况下调用。 wait() 方法释放锁,然后阻塞直到其它线程调用 notify() 方法或 notify_all() 方法唤醒它。一旦被唤醒, wait() 方法重新获取锁并返回。它也可以指定超时时间。

The notify() method wakes up one of the threads waiting for the condition variable, if any are waiting. The notify_all() method wakes up all threads waiting for the condition variable.

注意: notify() 方法和 notify_all() 方法并不会释放锁,这意味着被唤醒的线程不会立即从它们的 wait() 方法调用中返回,而是会在调用了 notify() 方法或 notify_all() 方法的线程最终放弃了锁的所有权后返回。

使用条件变量的典型编程风格是将锁用于同步某些共享状态的权限,那些对状态的某些特定改变感兴趣的线程,它们重复调用 wait() 方法,直到看到所期望的改变发生;而对于修改状态的线程,它们将当前状态改变为可能是等待者所期待的新状态后,调用 notify() 方法或者 notify_all() 方法。例如,下面的代码是一个通用的无限缓冲区容量的生产者-消费者情形:

# Consume one item
with cv:
whilenot an_item_is_available():
        cv.wait()
    get_an_available_item()
# Produce one item
with cv:
    make_an_item_available()
    cv.notify()

使用 while 循环检查所要求的条件成立与否是有必要的,因为 wait() 方法可能要经过不确定长度的时间后才会返回,而此时导致 notify() 方法调用的那个条件可能已经不再成立。这是多线程编程所固有的问题。 wait_for() 方法可自动化条件检查,并简化超时计算。

# Consume an item
with cv:
    cv.wait_for(an_item_is_available)
    get_an_available_item()

选择 notify() 还是 notify_all() ,取决于一次状态改变是只能被一个还是能被多个等待线程所用。例如在一个典型的生产者-消费者情形中,添加一个项目到缓冲区只需唤醒一个消费者线程。

classthreading.Condition(lock=None)

实现条件变量对象的类。一个条件变量对象允许一个或多个线程在被其它线程所通知之前进行等待。

如果给出了非 Nonelock 参数,则它必须为 Lock 或者 RLock 对象,并且它将被用作底层锁。否则,将会创建新的 RLock 对象,并将其用作底层锁。

在 3.3 版更改: 从工厂函数变为类。

  • acquire(\args*)

    请求底层锁。此方法调用底层锁的相应方法,返回值是底层锁相应方法的返回值。

  • release()

    释放底层锁。此方法调用底层锁的相应方法。没有返回值。

  • wait(timeout=None)

    等待直到被通知或发生超时。如果线程在调用此方法时没有获得锁,将会引发 RuntimeError 异常。

    这个方法释放底层锁,然后阻塞,直到在另外一个线程中调用同一个条件变量的 notify()notify_all() 唤醒它,或者直到可选的超时发生。一旦被唤醒或者超时,它重新获得锁并返回。

    当提供了 timeout 参数且不是 None 时,它应该是一个浮点数,代表操作的超时时间,以秒为单位(可以为小数)。

    当底层锁是个 RLock ,不会使用它的 release() 方法释放锁,因为当它被递归多次获取时,实际上可能无法解锁。相反,使用了 RLock 类的内部接口,即使多次递归获取它也能解锁它。 然后,在重新获取锁时,使用另一个内部接口来恢复递归级别。

    返回 True ,除非提供的 timeout 过期,这种情况下返回 False

    在 3.2 版更改: 很明显,方法总是返回 None

  • wait_for(predicate, timeout=None)

    等待,直到条件计算为真。 predicate 应该是一个可调用对象而且它的返回值可被解释为一个布尔值。可以提供 timeout 参数给出最大等待时间。

    这个实用方法会重复地调用 wait() 直到满足判断式或者发生超时。返回值是判断式最后一个返回值,而且如果方法发生超时会返回 False

    忽略超时功能,调用此方法大致相当于编写:

    whilenot predicate():
        cv.wait()

    因此,规则同样适用于 wait() :锁必须在被调用时保持获取,并在返回时重新获取。 随着锁定执行判断式。

    3.2 新版功能.

  • notify(n=1)

    默认唤醒一个等待这个条件的线程。如果调用线程在没有获得锁的情况下调用这个方法,会引发 RuntimeError 异常。

    这个方法唤醒最多 n 个正在等待这个条件变量的线程;如果没有线程在等待,这是一个空操作。

    当前实现中,如果至少有 n 个线程正在等待,准确唤醒 n 个线程。但是依赖这个行为并不安全。未来,优化的实现有时会唤醒超过 n 个线程。

    注意:被唤醒的线程实际上不会返回它调用的 wait() ,直到它可以重新获得锁。因为 notify() 不会释放锁,只有它的调用者应该这样做。

  • notify_all()

    唤醒所有正在等待这个条件的线程。这个方法行为与 notify() 相似,但并不只唤醒单一线程,而是唤醒所有等待线程。如果调用线程在调用这个方法时没有获得锁,会引发 RuntimeError 异常。

    notifyAll 方法是此方法的已弃用别名。

信号量对象

这是计算机科学史上最古老的同步原语之一,早期的荷兰科学家 Edsger W. Dijkstra 发明了它。(他使用名称 P()V() 而不是 acquire()release() )。

一个信号量管理一个内部计数器,该计数器因 acquire() 方法的调用而递减,因 release() 方法的调用而递增。 计数器的值永远不会小于零;当 acquire() 方法发现计数器为零时,将会阻塞,直到其它线程调用 release() 方法。

信号量对象也支持 上下文管理协议 。

classthreading.Semaphore(value=1)

该类实现信号量对象。信号量对象管理一个原子性的计数器,代表 release() 方法的调用次数减去 acquire() 的调用次数再加上一个初始值。如果需要, acquire() 方法将会阻塞直到可以返回而不会使得计数器变成负数。在没有显式给出 value 的值时,默认为1。

可选参数 value 赋予内部计数器初始值,默认值为 1 。如果 value 被赋予小于0的值,将会引发 ValueError 异常。

在 3.3 版更改: 从工厂函数变为类。

  • acquire(blocking=True, timeout=None)

    获取一个信号量。

    在不带参数的情况下调用时:

    • 如果在进入时内部计数器的值大于零,则将其减一并立即返回 True
    • 如果在进入时内部计数器的值为零,则将会阻塞直到被对 release() 的调用唤醒。 一旦被唤醒(并且计数器的值大于 0),则将计数器减 1 并返回 True。 每次对 release() 的调用将只唤醒一个线程。 线程被唤醒的次序是不可确定的。

    当发起调用时将 blocking 设为假值,则不进行阻塞。 如果一个无参数调用将要阻塞,则立即返回 False;在其他情况下,执行与无参数调用时一样的操作,然后返回 True

    当发起调用时如果 timeout 不为 None,则它将阻塞最多 timeout 秒。 请求在此时段时未能成功完成获取则将返回 False。 在其他情况下返回 True

    在 3.2 版更改: 新的 timeout 形参。

  • release(n=1)

    释放一个信号量,将内部计数器的值增加 n。 当进入时值为零且有其他线程正在等待它再次变为大于零时,则唤醒那 n 个线程。

    在 3.9 版更改: 增加了 n 形参以一次性释放多个等待线程。

classthreading.BoundedSemaphore(value=1)

该类实现有界信号量。有界信号量通过检查以确保它当前的值不会超过初始值。如果超过了初始值,将会引发 ValueError 异常。在大多情况下,信号量用于保护数量有限的资源。如果信号量被释放的次数过多,则表明出现了错误。没有指定时, value 的值默认为1。

在 3.3 版更改: 从工厂函数变为类。

Semaphore 例子

信号量通常用于保护数量有限的资源,例如数据库服务器。在资源数量固定的任何情况下,都应该使用有界信号量。在生成任何工作线程前,应该在主线程中初始化信号量。

maxconnections =5
# ...
pool_sema =BoundedSemaphore(value=maxconnections)

工作线程生成后,当需要连接服务器时,这些线程将调用信号量的 acquire 和 release 方法:

with pool_sema:
    conn = connectdb()
try:
# ... use connection ...
finally:
        conn.close()

使用有界信号量能减少这种编程错误:信号量的释放次数多于其请求次数。

事件对象

这是线程之间通信的最简单机制之一:一个线程发出事件信号,而其他线程等待该信号。

一个事件对象管理一个内部标识,调用 set() 方法可将其设置为 true ,调用 clear() 方法可将其设置为 false ,调用 wait() 方法将进入阻塞直到标识为 true 。

classthreading.Event

实现事件对象的类。事件对象管理一个内部标识,调用 set() 方法可将其设置为true。调用 clear() 方法可将其设置为 false 。调用 wait() 方法将进入阻塞直到标识为true。这个标识初始时为 false 。

在 3.3 版更改: 从工厂函数变为类。

  • is_set()

    当且仅当内部标识为 true 时返回 True

    isSet 方法是此方法的已弃用别名。

  • set()

    将内部标识设置为 true 。所有正在等待这个事件的线程将被唤醒。当标识为 true 时,调用 wait() 方法的线程不会被被阻塞。

  • clear()

    将内部标识设置为 false 。之后调用 wait() 方法的线程将会被阻塞,直到调用 set() 方法将内部标识再次设置为 true 。

  • wait(timeout=None)

    阻塞线程直到内部变量为 true 。如果调用时内部标识为 true,将立即返回。否则将阻塞线程,直到调用 set() 方法将标识设置为 true 或者发生可选的超时。

    当提供了timeout参数且不是 None 时,它应该是一个浮点数,代表操作的超时时间,以秒为单位(可以为小数)。

    当且仅当内部旗标在等待调用之前或者等待开始之后被设为真值时此方法将返回 True,也就是说,它将总是返回 True 除非设定了超时且操作发生了超时。

    在 3.1 版更改: 很明显,方法总是返回 None

定时器对象

此类表示一个操作应该在等待一定的时间之后运行 —- 相当于一个定时器。 Timer 类是 Thread 类的子类,因此可以像一个自定义线程一样工作。

与线程一样,通过调用 start() 方法启动定时器。而 cancel() 方法可以停止计时器(在计时结束前), 定时器在执行其操作之前等待的时间间隔可能与用户指定的时间间隔不完全相同。

例如:

def hello():
print("hello, world")
t =Timer(30.0, hello)
t.start()# after 30 seconds, "hello, world" will be printed

classthreading.Timer(interval, function, args=None, kwargs=None)

创建一个定时器,在经过 interval 秒的间隔事件后,将会用参数 args 和关键字参数 kwargs 调用 function*。如果 *argsNone (默认值),则会使用一个空列表。如果 kwargsNone (默认值),则会使用一个空字典。

在 3.3 版更改: 从工厂函数变为类。

  • cancel()

    停止定时器并取消执行计时器将要执行的操作。仅当计时器仍处于等待状态时有效。

栅栏对象

3.2 新版功能.

栅栏类提供一个简单的同步原语,用于应对固定数量的线程需要彼此相互等待的情况。线程调用 wait() 方法后将阻塞,直到所有线程都调用了 wait() 方法。此时所有线程将被同时释放。

栅栏对象可以被多次使用,但进程的数量不能改变。

这是一个使用简便的方法实现客户端进程与服务端进程同步的例子:

b =Barrier(2, timeout=5)
def server():
    start_server()
    b.wait()
whileTrue:
        connection = accept_connection()
        process_server_connection(connection)
def client():
    b.wait()
whileTrue:
        connection = make_connection()
        process_client_connection(connection)

classthreading.Barrier(parties, action=None, timeout=None)

创建一个需要 parties 个线程的栅栏对象。如果提供了可调用的 action 参数,它会在所有线程被释放时在其中一个线程中自动调用。 timeout 是默认的超时时间,如果没有在 wait() 方法中指定超时时间的话。

  • wait(timeout=None)

    冲出栅栏。当栅栏中所有线程都已经调用了这个函数,它们将同时被释放。如果提供了 timeout 参数,这里的 timeout 参数优先于创建栅栏对象时提供的 timeout 参数。

    函数返回值是一个整数,取值范围在0到 parties — 1,在每个线程中的返回值不相同。可用于从所有线程中选择唯一的一个线程执行一些特别的工作。例如:

    i = barrier.wait()
    if i ==0:
    # Only one thread needs to print this
    print("passed the barrier")

    如果创建栅栏对象时在构造函数中提供了 action 参数,它将在其中一个线程释放前被调用。如果此调用引发了异常,栅栏对象将进入损坏态。

    如果发生了超时,栅栏对象将进入破损态。

    如果栅栏对象进入破损态,或重置栅栏时仍有线程等待释放,将会引发 BrokenBarrierError 异常。

  • reset()

    重置栅栏为默认的初始态。如果栅栏中仍有线程等待释放,这些线程将会收到 BrokenBarrierError 异常。

    请注意使用此函数时,如果存在状态未知的其他线程,则可能需要执行外部同步。 如果栅栏已损坏则最好将其废弃并新建一个。

  • abort()

    使栅栏处于损坏状态。 这将导致任何现有和未来对 wait() 的调用失败并引发 BrokenBarrierError。 例如可以在需要中止某个线程时使用此方法,以避免应用程序的死锁。

    更好的方式是:创建栅栏时提供一个合理的超时时间,来自动避免某个线程出错。

  • parties

    冲出栅栏所需要的线程数量。

  • n_waiting

    当前时刻正在栅栏中阻塞的线程数量。

  • broken

    一个布尔值,值为 True 表明栅栏为破损态。

exceptionthreading.BrokenBarrierError

异常类,是 RuntimeError 异常的子类,在 Barrier 对象重置时仍有线程阻塞时和对象进入破损态时被引发。

with 语句中使用锁、条件和信号量

这个模块提供的带有 acquire()release() 方法的对象,可以被用作 with 语句的上下文管理器。当进入语句块时 acquire() 方法会被调用,退出语句块时 release() 会被调用。因此,以下片段:

with some_lock:
# do something...

相当于:

some_lock.acquire()
try:
# do something...
finally:
    some_lock.release()

现在 LockRLockConditionSemaphoreBoundedSemaphore 对象可以用作 with 语句的上下文管理器。

multiprocessing —- 基于进程的并行

源代码 Lib/multiprocessing/


概述

multiprocessing 是一个支持使用与 threading 模块类似的 API 来产生进程的包。 multiprocessing 包同时提供了本地和远程并发操作,通过使用子进程而非线程有效地绕过了 全局解释器锁。 因此,multiprocessing 模块允许程序员充分利用给定机器上的多个处理器。 它在 Unix 和 Windows 上均可运行。

multiprocessing 模块还引入了在 threading 模块中没有的API。一个主要的例子就是 Pool 对象,它提供了一种快捷的方法,赋予函数并行化处理一系列输入值的能力,可以将输入数据分配给不同进程处理(数据并行)。下面的例子演示了在模块中定义此类函数的常见做法,以便子进程可以成功导入该模块。这个数据并行的基本例子使用了 Pool

from multiprocessing import Pool
def f(x):
    return x*x
if __name__ == '__main__':
    with Pool(5) as p:
        print(p.map(f, [1, 2, 3]))

将在标准输出中打印

[1, 4, 9]

Process

multiprocessing 中,通过创建一个 Process 对象然后调用它的 start() 方法来生成进程。 Processthreading.Thread API 相同。 一个简单的多进程程序示例是:

from multiprocessing import Process
def f(name):
    print('hello', name)
if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

要显示所涉及的各个进程ID,这是一个扩展示例:

from multiprocessing import Process
import os
def info(title):
    print(title)
    print('module name:', __name__)
    print('parent process:', os.getppid())
    print('process id:', os.getpid())
def f(name):
    info('function f')
    print('hello', name)
if __name__ == '__main__':
    info('main line')
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()

关于为什么 if __name__ == '__main__' 部分是必需的解释。

上下文和启动方法

根据不同的平台, multiprocessing 支持三种启动进程的方法。这些 启动方法

spawn

父进程会启动一个全新的 python 解释器进程。 子进程将只继承那些运行进程对象的 run() 方法所必需的资源。 特别地,来自父进程的非必需文件描述符和句柄将不会被继承。 使用此方法启动进程相比使用 forkforkserver 要慢上许多。

可在Unix和Windows上使用。 Windows上的默认设置。

fork

父进程使用 os.fork() 来产生 Python 解释器分叉。子进程在开始时实际上与父进程相同。父进程的所有资源都由子进程继承。请注意,安全分叉多线程进程是棘手的。

只存在于Unix。Unix中的默认值。

forkserver

程序启动并选择* forkserver * 启动方法时,将启动服务器进程。从那时起,每当需要一个新进程时,父进程就会连接到服务器并请求它分叉一个新进程。分叉服务器进程是单线程的,因此使用 os.fork() 是安全的。没有不必要的资源被继承。

可在Unix平台上使用,支持通过Unix管道传递文件描述符。

在 3.8 版更改: 对于 macOS,spawn 启动方式是默认方式。 因为 fork 可能导致subprocess崩溃,被认为是不安全的,查看 bpo-33725

在 3.4 版更改: 在所有unix平台上添加支持了 spawn ,并且为一些unix平台添加了 forkserver 。在Windows上子进程不再继承所有可继承的父进程句柄。

在 Unix 上通过 spawnforkserver 方式启动多进程会同时启动一个 资源追踪 进程,负责追踪当前程序的进程产生的、并且不再被使用的命名系统资源(如命名信号量以及 SharedMemory 对象)。当所有进程退出后,资源追踪会负责释放这些仍被追踪的的对象。通常情况下是不会有这种对象的,但是假如一个子进程被某个信号杀死,就可能存在这一类资源的“泄露”情况。(泄露的信号量以及共享内存不会被释放,直到下一次系统重启,对于这两类资源来说,这是一个比较大的问题,因为操作系统允许的命名信号量的数量是有限的,而共享内存也会占据主内存的一片空间)

要选择一个启动方法,你应该在主模块的 if __name__ == '__main__' 子句中调用 set_start_method() 。例如:

import multiprocessing as mp
def foo(q):
    q.put('hello')
if __name__ == '__main__':
    mp.set_start_method('spawn')
    q = mp.Queue()
    p = mp.Process(target=foo, args=(q,))
    p.start()
    print(q.get())
    p.join()

在程序中 set_start_method() 不应该被多次调用。

或者,你可以使用 get_context() 来获取上下文对象。上下文对象与 multiprocessing 模块具有相同的API,并允许在同一程序中使用多种启动方法。:

import multiprocessing as mp
def foo(q):
    q.put('hello')
if __name__ == '__main__':
    ctx = mp.get_context('spawn')
    q = ctx.Queue()
    p = ctx.Process(target=foo, args=(q,))
    p.start()
    print(q.get())
    p.join()

请注意,对象在不同上下文创建的进程间可能并不兼容。 特别是,使用 fork 上下文创建的锁不能传递给使用 spawnforkserver 启动方法启动的进程。

想要使用特定启动方法的库应该使用 get_context() 以避免干扰库用户的选择。

警告

'spawn''forkserver' 启动方法当前不能在Unix上和“冻结的”可执行内容一同使用(例如,有类似 PyInstallercx_Freeze 的包产生的二进制文件)。 'fork' 启动方法可以使用。

在进程之间交换对象

multiprocessing 支持进程之间的两种通信通道:

队列

Queue 类是一个近似 queue.Queue 的克隆。 例如:

from multiprocessing import Process, Queue
def f(q):
    q.put([42, None, 'hello'])
if __name__ == '__main__':
    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    print(q.get())    # prints "[42, None, 'hello']"
    p.join()

队列是线程和进程安全的。

管道

Pipe() 函数返回一个由管道连接的连接对象,默认情况下是双工(双向)。例如:

from multiprocessing import Process, Pipe
def f(conn):
    conn.send([42, None, 'hello'])
    conn.close()
if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())   # prints "[42, None, 'hello']"
    p.join()

返回的两个连接对象 Pipe() 表示管道的两端。每个连接对象都有 send()recv() 方法(相互之间的)。请注意,如果两个进程(或线程)同时尝试读取或写入管道的 同一 端,则管道中的数据可能会损坏。当然,在不同进程中同时使用管道的不同端的情况下不存在损坏的风险。

进程间同步

multiprocessing 包含来自 threading 的所有同步原语的等价物。例如,可以使用锁来确保一次只有一个进程打印到标准输出:

from multiprocessing import Process, Lock
def f(l, i):
    l.acquire()
    try:
        print('hello world', i)
    finally:
        l.release()
if __name__ == '__main__':
    lock = Lock()
    for num in range(10):
        Process(target=f, args=(lock, num)).start()

不使用锁的情况下,来自于多进程的输出很容易产生混淆。

进程间共享状态

如上所述,在进行并发编程时,通常最好尽量避免使用共享状态。使用多个进程时尤其如此。

但是,如果你真的需要使用一些共享数据,那么 multiprocessing 提供了两种方法。

共享内存

可以使用 ValueArray 将数据存储在共享内存映射中。例如,以下代码:

from multiprocessing import Process, Value, Array
def f(n, a):
    n.value = 3.1415927
    for i in range(len(a)):
        a[i] = -a[i]
if __name__ == '__main__':
    num = Value('d', 0.0)
    arr = Array('i', range(10))
    p = Process(target=f, args=(num, arr))
    p.start()
    p.join()
    print(num.value)
    print(arr[:])

将打印

3.1415927
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

创建 numarr 时使用的 'd''i' 参数是 array 模块使用的类型的 typecode : 'd' 表示双精度浮点数, 'i' 表示有符号整数。这些共享对象将是进程和线程安全的。

为了更灵活地使用共享内存,可以使用 multiprocessing.sharedctypes 模块,该模块支持创建从共享内存分配的任意ctypes对象。

服务进程

Manager() 返回的管理器对象控制一个服务进程,该进程保存Python对象并允许其他进程使用代理操作它们。

Manager() 返回的管理器支持类型: listdictNamespaceLockRLockSemaphoreBoundedSemaphoreConditionEventBarrierQueueValueArray 。例如

from multiprocessing import Process, Manager
def f(d, l):
    d[1] = '1'
    d['2'] = 2
    d[0.25] = None
    l.reverse()
if __name__ == '__main__':
    with Manager() as manager:
        d = manager.dict()
        l = manager.list(range(10))
        p = Process(target=f, args=(d, l))
        p.start()
        p.join()
        print(d)
        print(l)

将打印

{0.25: None, 1: '1', '2': 2}
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

使用服务进程的管理器比使用共享内存对象更灵活,因为它们可以支持任意对象类型。此外,单个管理器可以通过网络由不同计算机上的进程共享。但是,它们比使用共享内存慢。

使用工作进程

Pool 类表示一个工作进程池。它具有允许以几种不同方式将任务分配到工作进程的方法。

例如:

from multiprocessing import Pool, TimeoutError
import time
import os
def f(x):
    return x*x
if __name__ == '__main__':
    # start 4 worker processes
    with Pool(processes=4) as pool:
        # print "[0, 1, 4,..., 81]"
        print(pool.map(f, range(10)))
        # print same numbers in arbitrary order
        for i in pool.imap_unordered(f, range(10)):
            print(i)
        # evaluate "f(20)" asynchronously
        res = pool.apply_async(f, (20,))      # runs in *only* one process
        print(res.get(timeout=1))             # prints "400"
        # evaluate "os.getpid()" asynchronously
        res = pool.apply_async(os.getpid, ()) # runs in *only* one process
        print(res.get(timeout=1))             # prints the PID of that process
        # launching multiple evaluations asynchronously *may* use more processes
        multiple_results = [pool.apply_async(os.getpid, ()) for i in range(4)]
        print([res.get(timeout=1) for res in multiple_results])
        # make a single worker sleep for 10 secs
        res = pool.apply_async(time.sleep, (10,))
        try:
            print(res.get(timeout=1))
        except TimeoutError:
            print("We lacked patience and got a multiprocessing.TimeoutError")
        print("For the moment, the pool remains available for more work")
    # exiting the 'with'-block has stopped the pool
    print("Now the pool is closed and no longer available")

请注意,进程池的方法只能由创建它的进程使用。

注解

这个包中的功能要求子进程可以导入 __main__ 模块。虽然这在 编程指导 中有描述,但还是需要提前说明一下。这意味着一些示例在交互式解释器中不起作用,比如 multiprocessing.pool.Pool 示例。例如:

>>> from multiprocessing import Pool
>>> p = Pool(5)
>>> def f(x):
...     return x*x
...
>>> with p:
...   p.map(f, [1,2,3])
Process PoolWorker-1:
Process PoolWorker-2:
Process PoolWorker-3:
Traceback (most recent call last):
AttributeError: 'module' object has no attribute 'f'
AttributeError: 'module' object has no attribute 'f'
AttributeError: 'module' object has no attribute 'f'

(如果尝试执行上面的代码,它会以一种半随机的方式将三个完整的堆栈内容交替输出,然后你只能以某种方式停止父进程。)

参考

multiprocessing 包主要复制了 threading 模块的API。

Process 和异常

class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, **, daemon=None*)

进程对象表示在单独进程中运行的活动。 Process 类拥有和 threading.Thread 等价的大部分方法。

应始终使用关键字参数调用构造函数。 group 应该始终是 None ;它仅用于兼容 threading.Threadtarget 是由 run() 方法调用的可调用对象。它默认为 None ,意味着什么都没有被调用。 name 是进程名称。 args 是目标调用的参数元组。 kwargs 是目标调用的关键字参数字典。如果提供,则键参数 daemon 将进程 daemon 标志设置为 TrueFalse 。如果是 None (默认值),则该标志将从创建的进程继承。

默认情况下,不会将任何参数传递给 target

如果子类重写构造函数,它必须确保它在对进程执行任何其他操作之前调用基类构造函数( Process.__init__() )。

在 3.3 版更改: 加入 daemon 参数。

  • run()

    表示进程活动的方法。

    你可以在子类中重载此方法。标准 run() 方法调用传递给对象构造函数的可调用对象作为目标参数(如果有),分别从 argskwargs 参数中获取顺序和关键字参数。

  • start()

    启动进程活动。

    这个方法每个进程对象最多只能调用一次。它会将对象的 run() 方法安排在一个单独的进程中调用。

  • join([timeout])

    如果可选参数 timeoutNone (默认值),则该方法将阻塞,直到调用 join() 方法的进程终止。如果 timeout 是一个正数,它最多会阻塞 timeout 秒。请注意,如果进程终止或方法超时,则该方法返回 None 。检查进程的 exitcode 以确定它是否终止。

    一个进程可以被 join 多次。

    进程无法join自身,因为这会导致死锁。尝试在启动进程之前join进程是错误的。

  • name

    进程的名称。该名称是一个字符串,仅用于识别目的。它没有语义。可以为多个进程指定相同的名称。

    初始名称由构造器设定。 如果没有为构造器提供显式名称,则会构造一个形式为 ‘Process-N1:N2:…:Nk’ 的名称,其中每个 Nk 是其父亲的第 N 个孩子。

  • is_alive()

    返回进程是否还活着。

    粗略地说,从 start() 方法返回到子进程终止之前,进程对象仍处于活动状态。

  • daemon

    进程的守护标志,一个布尔值。这必须在 start() 被调用之前设置。

    初始值继承自创建进程。

    当进程退出时,它会尝试终止其所有守护进程子进程。

    请注意,不允许在守护进程中创建子进程。这是因为当守护进程由于父进程退出而中断时,其子进程会变成孤儿进程。 另外,这些 不是 Unix 守护进程或服务,它们是正常进程,如果非守护进程已经退出,它们将被终止(并且不被合并)。

除了 threading.Thread API ,Process 对象还支持以下属性和方法:

  • pid

    返回进程ID。在生成该进程之前,这将是 None

  • exitcode

    子进程的退出代码。如果进程尚未终止,这将是 None 。负值 -N 表示子进程被信号 N 终止。

  • authkey

    进程的身份验证密钥(字节字符串)。

    multiprocessing 初始化时,主进程使用 os.urandom() 分配一个随机字符串。

    当创建 Process 对象时,它将继承其父进程的身份验证密钥,尽管可以通过将 authkey 设置为另一个字节字符串来更改。

  • sentinel

    系统对象的数字句柄,当进程结束时将变为 “ready” 。

    如果要使用 multiprocessing.connection.wait() 一次等待多个事件,可以使用此值。否则调用 join() 更简单。

    在Windows上,这是一个操作系统句柄,可以与 WaitForSingleObjectWaitForMultipleObjects 系列API调用一起使用。在Unix上,这是一个文件描述符,可以使用来自 select 模块的原语。

    3.3 新版功能.

  • terminate()

    终止进程。 在Unix上,这是使用 SIGTERM 信号完成的;在Windows上使用 TerminateProcess() 。 请注意,不会执行退出处理程序和finally子句等。

    请注意,进程的后代进程将不会被终止 —— 它们将简单地变成孤立的。

    警告

    如果在关联进程使用管道或队列时使用此方法,则管道或队列可能会损坏,并可能无法被其他进程使用。类似地,如果进程已获得锁或信号量等,则终止它可能导致其他进程死锁。

  • kill()

    terminate() 相同,但在Unix上使用 SIGKILL 信号。

    3.7 新版功能.

  • close()

    关闭 Process 对象,释放与之关联的所有资源。如果底层进程仍在运行,则会引发 ValueError 。一旦 close() 成功返回, Process 对象的大多数其他方法和属性将引发 ValueError

    3.7 新版功能.

注意 start()join()is_alive()terminate()exitcode 方法只能由创建进程对象的进程调用。

Process 一些方法的示例用法:

 >>> import multiprocessing, time, signal
 >>> p = multiprocessing.Process(target=time.sleep, args=(1000,))
 >>> print(p, p.is_alive())
 <Process ... initial> False
 >>> p.start()
 >>> print(p, p.is_alive())
 <Process ... started> True
 >>> p.terminate()
 >>> time.sleep(0.1)
 >>> print(p, p.is_alive())
 <Process ... stopped exitcode=-SIGTERM> False
 >>> p.exitcode == -signal.SIGTERM
 True

exception multiprocessing.ProcessError

所有 multiprocessing 异常的基类。

exception multiprocessing.BufferTooShort

当提供的缓冲区对象太小而无法读取消息时, Connection.recv_bytes_into() 引发的异常。

如果 e 是一个 BufferTooShort 实例,那么 e.args[0] 将把消息作为字节字符串给出。

exception multiprocessing.AuthenticationError

出现身份验证错误时引发。

exception multiprocessing.TimeoutError

有超时的方法超时时引发。

管道和队列

使用多进程时,一般使用消息机制实现进程间通信,尽可能避免使用同步原语,例如锁。

消息机制包含: Pipe() (可以用于在两个进程间传递消息),以及队列(能够在多个生产者和消费者之间通信)。

Queue, SimpleQueue 以及 JoinableQueue 都是多生产者,多消费者,并且实现了 FIFO 的队列类型,其表现与标准库中的 queue.Queue 类相似。 不同之处在于 Queue 缺少标准库的 queue.Queue 从 Python 2.5 开始引入的 task_done()join() 方法。

如果你使用了 JoinableQueue ,那么你 必须 对每个已经移出队列的任务调用 JoinableQueue.task_done()。 不然的话用于统计未完成任务的信号量最终会溢出并抛出异常。

另外还可以通过使用一个管理器对象创建一个共享队列 。

注解

multiprocessing 使用了普通的 queue.Emptyqueue.Full 异常去表示超时。 你需要从 queue 中导入它们,因为它们并不在 multiprocessing 的命名空间中。

注解

当一个对象被放入一个队列中时,这个对象首先会被一个后台线程用 pickle 序列化,并将序列化后的数据通过一个底层管道的管道传递到队列中。 这种做法会有点让人惊讶,但一般不会出现什么问题。 如果它们确实妨碍了你,你可以使用一个由管理器 manager 创建的队列替换它。

  1. 将一个对象放入一个空队列后,可能需要极小的延迟,队列的方法 empty() 才会返回 False 。而 get_nowait() 可以不抛出 queue.Empty 直接返回。
  2. 如果有多个进程同时将对象放入队列,那么在队列的另一端接受到的对象可能是无序的。但是由同一个进程放入的多个对象的顺序在另一端输出时总是一样的。

警告

如果一个进程在尝试使用 Queue 期间被 Process.terminate()os.kill() 调用终止了,那么队列中的数据很可能被破坏。 这可能导致其他进程在尝试使用该队列时发生异常。

警告

正如刚才提到的,如果一个子进程将一些对象放进队列中 (并且它没有用 JoinableQueue.cancel_join_thread 方法),那么这个进程在所有缓冲区的对象被刷新进管道之前,是不会终止的。

这意味着,除非你确定所有放入队列中的对象都已经被消费了,否则如果你试图等待这个进程,你可能会陷入死锁中。相似地,如果该子进程不是后台进程,那么父进程可能在试图等待所有非后台进程退出时挂起。

注意用管理器创建的队列不存在这个问题。

multiprocessing.Pipe([duplex])

返回一对 Connection 对象 (conn1, conn2) , 分别表示管道的两端。

如果 duplex 被置为 True (默认值),那么该管道是双向的。如果 duplex 被置为 False ,那么该管道是单向的,即 conn1 只能用于接收消息,而 conn2 仅能用于发送消息。

class multiprocessing.Queue([maxsize])

返回一个使用一个管道和少量锁和信号量实现的共享队列实例。当一个进程将一个对象放进队列中时,一个写入线程会启动并将对象从缓冲区写入管道中。

一旦超时,将抛出标准库 queue 模块中常见的异常 queue.Emptyqueue.Full

除了 task_done()join() 之外,Queue 实现了标准库类 queue.Queue 中所有的方法。

  • qsize()

    返回队列的大致长度。由于多线程或者多进程的上下文,这个数字是不可靠的。

    Note that this may raise NotImplementedError on Unix platforms like macOS where sem_getvalue() is not implemented.

  • empty()

    如果队列是空的,返回 True ,反之返回 False 。 由于多线程或多进程的环境,该状态是不可靠的。

  • full()

    如果队列是满的,返回 True ,反之返回 False 。 由于多线程或多进程的环境,该状态是不可靠的。

  • put(obj[, block[, timeout]])

    将 obj 放入队列。如果可选参数 blockTrue (默认值) 而且 timeoutNone (默认值), 将会阻塞当前进程,直到有空的缓冲槽。如果 timeout 是正数,将会在阻塞了最多 timeout 秒之后还是没有可用的缓冲槽时抛出 queue.Full 异常。反之 (blockFalse 时),仅当有可用缓冲槽时才放入对象,否则抛出 queue.Full 异常 (在这种情形下 timeout 参数会被忽略)。

    在 3.8 版更改: 如果队列已经关闭,会抛出 ValueError 而不是 AssertionError

  • put_nowait(obj)

    相当于 put(obj, False)

  • get([block[, timeout]])

    从队列中取出并返回对象。如果可选参数 blockTrue (默认值) 而且 timeoutNone (默认值), 将会阻塞当前进程,直到队列中出现可用的对象。如果 timeout 是正数,将会在阻塞了最多 timeout 秒之后还是没有可用的对象时抛出 queue.Empty 异常。反之 (blockFalse 时),仅当有可用对象能够取出时返回,否则抛出 queue.Empty 异常 (在这种情形下 timeout 参数会被忽略)。

    在 3.8 版更改: 如果队列已经关闭,会抛出 ValueError 而不是 OSError

  • get_nowait()

    相当于 get(False)

multiprocessing.Queue 类有一些在 queue.Queue 类中没有出现的方法。这些方法在大多数情形下并不是必须的。

  • close()

    指示当前进程将不会再往队列中放入对象。一旦所有缓冲区中的数据被写入管道之后,后台的线程会退出。这个方法在队列被gc回收时会自动调用。

  • join_thread()

    等待后台线程。这个方法仅在调用了 close() 方法之后可用。这会阻塞当前进程,直到后台线程退出,确保所有缓冲区中的数据都被写入管道中。

    默认情况下,如果一个不是队列创建者的进程试图退出,它会尝试等待这个队列的后台线程。这个进程可以使用 cancel_join_thread()join_thread() 方法什么都不做直接跳过。

  • cancel_join_thread()

    防止 join_thread() 方法阻塞当前进程。具体而言,这防止进程退出时自动等待后台线程退出。

    这个方法更好的名字可能是 allow_exit_without_flush()。 这可能会导致已排入队列的数据丢失,几乎可以肯定你将不需要用到这个方法。 实际上它仅适用于当你需要当前进程立即退出而不必等待将已排入的队列更新到下层管道,并且你不担心丢失数据的时候。

注解

该类的功能依赖于宿主操作系统具有可用的共享信号量实现。否则该类将被禁用,任何试图实例化一个 Queue 对象的操作都会抛出 ImportError 异常 。后续说明的任何专用队列对象亦如此。

class multiprocessing.SimpleQueue

这是一个简化的 Queue 类的实现,很像带锁的 Pipe

  • close()

    关闭队列:释放内部资源。

    队列在被关闭后就不可再被使用。 例如不可再调用 get(), put()empty() 等方法。

    3.9 新版功能.

  • empty()

    如果队列为空返回 True ,否则返回 False

  • get()

    从队列中移出并返回一个对象。

  • put(item)

    item 放入队列。

class multiprocessing.JoinableQueue([maxsize])

JoinableQueue 类是 Queue 的子类,额外添加了 task_done()join() 方法。

  • task_done()

    指出之前进入队列的任务已经完成。由队列的消费者进程使用。对于每次调用 get() 获取的任务,执行完成后调用 task_done() 告诉队列该任务已经处理完成。

    如果 join() 方法正在阻塞之中,该方法会在所有对象都被处理完的时候返回 (即对之前使用 put() 放进队列中的所有对象都已经返回了对应的 task_done() ) 。

    如果被调用的次数多于放入队列中的项目数量,将引发 ValueError 异常 。

  • join()

    阻塞至队列中所有的元素都被接收和处理完毕。

    当条目添加到队列的时候,未完成任务的计数就会增加。每当消费者进程调用 task_done() 表示这个条目已经被回收,该条目所有工作已经完成,未完成计数就会减少。当未完成计数降到零的时候, join() 阻塞被解除。

杂项

multiprocessing.active_children()

返回当前进程存活的子进程的列表。

调用该方法有“等待”已经结束的进程的副作用。

multiprocessing.cpu_count()

返回系统的CPU数量。

该数量不同于当前进程可以使用的CPU数量。可用的CPU数量可以由 len(os.sched_getaffinity(0)) 方法获得。

When the number of CPUs cannot be determined a NotImplementedError is raised.

参见

os.cpu_count()

multiprocessing.current_process()

返回与当前进程相对应的 Process 对象。

threading.current_thread() 相同。

multiprocessing.parent_process()

返回父进程 Process 对象,和父进程调用 current_process() 返回的对象一样。如果一个进程已经是主进程, parent_process 会返回 None.

3.8 新版功能.

multiprocessing.freeze_support()

为使用了 multiprocessing 的程序,提供冻结以产生 Windows 可执行文件的支持。(在 py2exe, PyInstallercx_Freeze 上测试通过)

需要在 main 模块的 if __name__ == '__main__' 该行之后马上调用该函数。例如:

from multiprocessing import Process, freeze_support
def f():
    print('hello world!')
if __name__ == '__main__':
    freeze_support()
    Process(target=f).start()

如果没有调用 freeze_support() 在尝试运行被冻结的可执行文件时会抛出 RuntimeError 异常。

freeze_support() 的调用在非 Windows 平台上是无效的。如果该模块在 Windows 平台的 Python 解释器中正常运行 (该程序没有被冻结), 调用freeze_support() 也是无效的。

multiprocessing.get_all_start_methods()

返回支持的启动方法的列表,该列表的首项即为默认选项。可能的启动方法有 'fork''spawn'‘forkserver’ 。在 Windows 中,只有 'spawn' 是可用的。 Unix 平台总是支持 'fork''spawn' ,且 'fork' 是默认值。

3.4 新版功能.

multiprocessing.get_context(method=None)

返回一个 Context 对象。该对象具有和 multiprocessing 模块相同的API。

如果 method 设置成 None 那么将返回默认上下文对象。否则 method 应该是 'fork', 'spawn', 'forkserver' 。 如果指定的启动方法不存在,将抛出 ValueError 异常。

3.4 新版功能.

multiprocessing.get_start_method(allow_none=False)

返回启动进程时使用的启动方法名。

如果启动方法已经固定,并且 allow_none 被设置成 False ,那么启动方法将被固定为默认的启动方法,并且返回其方法名。如果启动方法没有设定,并且 allow_none 被设置成 True ,那么将返回 None

The return value can be 'fork', 'spawn', 'forkserver' or None. 'fork' is the default on Unix, while 'spawn' is the default on Windows and macOS.

在 3.8 版更改: 对于 macOS,spawn 启动方式是默认方式。 因为 fork 可能导致subprocess崩溃,被认为是不安全的,查看 bpo-33725

3.4 新版功能.

multiprocessing.set_executable()

设置在启动子进程时使用的 Python 解释器路径。 ( 默认使用 sys.executable ) 嵌入式编程人员可能需要这样做:

set_executable(os.path.join(sys.exec_prefix, 'pythonw.exe'))

以使他们可以创建子进程。

在 3.4 版更改: 现在在 Unix 平台上使用 'spawn' 启动方法时支持调用该方法。

multiprocessing.set_start_method(method)

设置启动子进程的方法。 method 可以是 'fork' , 'spawn' 或者 'forkserver'

注意这最多只能调用一次,并且需要藏在 main 模块中,由 if __name__ == '__main__' 保护着。

3.4 新版功能.

注解

multiprocessing 并没有包含类似 threading.active_count() , threading.enumerate() , threading.settrace() , threading.setprofile(), threading.Timer , 或者 threading.local 的方法和类。

连接对象(Connection)

Connection 对象允许收发可以序列化的对象或字符串。它们可以看作面向消息的连接套接字。

通常使用 Pipe 创建 Connection 对象。

class multiprocessing.connection.Connection

  • send(obj)

    将一个对象发送到连接的另一端,可以用 recv() 读取。

    发送的对象必须是可以序列化的,过大的对象 ( 接近 32MiB+ ,这个值取决于操作系统 ) 有可能引发 ValueError 异常。

  • recv()

    返回一个由另一端使用 send() 发送的对象。该方法会一直阻塞直到接收到对象。 如果对端关闭了连接或者没有东西可接收,将抛出 EOFError 异常。

  • fileno()

    返回由连接对象使用的描述符或者句柄。

  • close()

    关闭连接对象。

    当连接对象被垃圾回收时会自动调用。

  • poll([timeout])

    返回连接对象中是否有可以读取的数据。

    如果未指定 timeout ,此方法会马上返回。如果 timeout 是一个数字,则指定了最大阻塞的秒数。如果 timeoutNone,那么将一直等待,不会超时。

    注意通过使用 multiprocessing.connection.wait() 可以一次轮询多个连接对象。

  • send_bytes(buffer[, offset[, size]])

    从一个 bytes-like object 对象中取出字节数组并作为一条完整消息发送。

    如果由 offset 给定了在 buffer 中读取数据的位置。 如果给定了 size ,那么将会从缓冲区中读取多个字节。 过大的缓冲区 ( 接近 32MiB+ ,此值依赖于操作系统 ) 有可能引发 ValueError 异常。

  • recv_bytes([maxlength])

    以字符串形式返回一条从连接对象另一端发送过来的字节数据。此方法在接收到数据前将一直阻塞。 如果连接对象被对端关闭或者没有数据可读取,将抛出 EOFError 异常。

    如果给定了 maxlength 并且消息长于 maxlength 那么将抛出 OSError 并且该连接对象将不再可读。

    在 3.3 版更改: 曾经该函数抛出 IOError ,现在这是 OSError 的别名。

  • recv_bytes_into(buffer[, offset])

    将一条完整的字节数据消息读入 buffer 中并返回消息的字节数。 此方法在接收到数据前将一直阻塞。 如果连接对象被对端关闭或者没有数据可读取,将抛出 EOFError 异常。

    buffer must be a writable bytes-like object. If offset is given then the message will be written into the buffer from that position. Offset must be a non-negative integer less than the length of buffer (in bytes).

    如果缓冲区太小,则将引发 BufferTooShort 异常,并且完整的消息将会存放在异常实例 ee.args[0] 中。

在 3.3 版更改: 现在连接对象自身可以通过 Connection.send()Connection.recv() 在进程之间传递。

3.3 新版功能: 连接对象现已支持上下文管理协议 。 __enter__() 返回连接对象, __exit__() 会调用 close()

例如:

>>> from multiprocessing import Pipe
>>> a, b = Pipe()
>>> a.send([1, 'hello', None])
>>> b.recv()
[1, 'hello', None]
>>> b.send_bytes(b'thank you')
>>> a.recv_bytes()
b'thank you'
>>> import array
>>> arr1 = array.array('i', range(5))
>>> arr2 = array.array('i', [0] * 10)
>>> a.send_bytes(arr1)
>>> count = b.recv_bytes_into(arr2)
>>> assert count == len(arr1) * arr1.itemsize
>>> arr2
array('i', [0, 1, 2, 3, 4, 0, 0, 0, 0, 0])

警告

Connection.recv() 方法会自动解封它收到的数据,除非你能够信任发送消息的进程,否则此处可能有安全风险。

因此, 除非连接对象是由 Pipe() 产生的,否则你应该仅在使用了某种认证手段之后才使用 recv()send() 方法。

警告

如果一个进程在试图读写管道时被终止了,那么管道中的数据很可能是不完整的,因为此时可能无法确定消息的边界。

同步原语

通常来说同步原语在多进程环境中并不像它们在多线程环境中那么必要。

注意可以使用管理器对象创建同步原语。

class multiprocessing.Barrier(parties[, action[, timeout]])

类似 threading.Barrier 的栅栏对象。

3.3 新版功能.

class multiprocessing.BoundedSemaphore([value])

非常类似 threading.BoundedSemaphore 的有界信号量对象。

一个小小的不同在于,它的 acquire 方法的第一个参数名是和 Lock.acquire() 一样的 block

注解

On macOS, this is indistinguishable from Semaphore because sem_getvalue() is not implemented on that platform.

class multiprocessing.Condition([lock])

条件变量: threading.Condition 的别名。

指定的 lock 参数应该是 multiprocessing 模块中的 Lock 或者 RLock 对象。

在 3.3 版更改: 新增了 wait_for() 方法。

class multiprocessing.Event

A clone of threading.Event.

class multiprocessing.Lock

原始锁(非递归锁)对象,类似于 threading.Lock 。一旦一个进程或者线程拿到了锁,后续的任何其他进程或线程的其他请求都会被阻塞直到锁被释放。任何进程或线程都可以释放锁。除非另有说明,否则 multiprocessing.Lock 用于进程或者线程的概念和行为都和 threading.Lock 一致。

注意 Lock 实际上是一个工厂函数。它返回由默认上下文初始化的 multiprocessing.synchronize.Lock 对象。

Lock supports the context manager protocol and thus may be used in with statements.

  • acquire(block=True, timeout=None)

    可以阻塞或非阻塞地获得锁。

    如果 block 参数被设为 True ( 默认值 ) , 对该方法的调用在锁处于释放状态之前都会阻塞,然后将锁设置为锁住状态并返回 True 。需要注意的是第一个参数名与 threading.Lock.acquire() 的不同。

    如果 block 参数被设置成 False ,方法的调用将不会阻塞。 如果锁当前处于锁住状态,将返回 False ; 否则将锁设置成锁住状态,并返回 True

    timeout 是一个正浮点数时,会在等待锁的过程中最多阻塞等待 timeout 秒,当 timeout 是负数时,效果和 timeout 为0时一样,当 timeoutNone (默认值)时,等待时间是无限长。需要注意的是,对于 timeout 参数是负数和 None 的情况, 其行为与 threading.Lock.acquire() 是不一样的。当 block 参数 为 False 时, timeout 并没有实际用处,会直接忽略。否则,函数会在拿到锁后返回 True 或者 超时没拿到锁后返回 False

  • release()

    释放锁,可以在任何进程、线程使用,并不限于锁的拥有者。

    当尝试释放一个没有被持有的锁时,会抛出 ValueError 异常,除此之外其行为与 threading.Lock.release() 一样。

class multiprocessing.RLock

递归锁对象: 类似于 threading.RLock 。递归锁必须由持有线程、进程亲自释放。如果某个进程或者线程拿到了递归锁,这个进程或者线程可以再次拿到这个锁而不需要等待。但是这个进程或者线程的拿锁操作和释放锁操作的次数必须相同。

注意 RLock 是一个工厂函数,调用后返回一个使用默认 context 初始化的 multiprocessing.synchronize.RLock 实例。

RLock 支持 context manager 协议,因此可在 with 语句内使用。

  • acquire(block=True, timeout=None)

    可以阻塞或非阻塞地获得锁。

    block 参数设置为 True 时,会一直阻塞直到锁处于空闲状态(没有被任何进程、线程拥有),除非当前进程或线程已经拥有了这把锁。然后当前进程/线程会持有这把锁(在锁没有其他持有者的情况下),锁内的递归等级加一,并返回 True . 注意, 这个函数第一个参数的行为和 threading.RLock.acquire() 的实现有几个不同点,包括参数名本身。

    block 参数是 False , 将不会阻塞,如果此时锁被其他进程或者线程持有,当前进程、线程获取锁操作失败,锁的递归等级也不会改变,函数返回 False , 如果当前锁已经处于释放状态,则当前进程、线程则会拿到锁,并且锁内的递归等级加一,函数返回 True

    timeout 参数的使用方法及行为与 Lock.acquire() 一样。但是要注意 timeout 的其中一些行为和 threading.RLock.acquire() 中实现的行为是不同的。

  • release()

    释放锁,使锁内的递归等级减一。如果释放后锁内的递归等级降低为0,则会重置锁的状态为释放状态(即没有被任何进程、线程持有),重置后如果有有其他进程和线程在等待这把锁,他们中的一个会获得这个锁而继续运行。如果释放后锁内的递归等级还没到达0,则这个锁仍将保持未释放状态且当前进程和线程仍然是持有者。

    只有当前进程或线程是锁的持有者时,才允许调用这个方法。如果当前进程或线程不是这个锁的拥有者,或者这个锁处于已释放的状态(即没有任何拥有者),调用这个方法会抛出 AssertionError 异常。注意这里抛出的异常类型和 threading.RLock.release() 中实现的行为不一样。

class multiprocessing.Semaphore([value])

一种信号量对象: 类似于 threading.Semaphore.

一个小小的不同在于,它的 acquire 方法的第一个参数名是和 Lock.acquire() 一样的 block

注解

On macOS, sem_timedwait is unsupported, so calling acquire() with a timeout will emulate that function’s behavior using a sleeping loop.

注解

假如信号 SIGINT 是来自于 Ctrl-C ,并且主线程被 BoundedSemaphore.acquire(), Lock.acquire(), RLock.acquire(), Semaphore.acquire(), Condition.acquire()Condition.wait() 阻塞,则调用会立即中断同时抛出 KeyboardInterrupt 异常。

这和 threading 的行为不同,此模块中当执行对应的阻塞式调用时,SIGINT 会被忽略。

注解

这个包的某些功能依赖于宿主机系统的共享信号量的实现,如果系统没有这个特性, multiprocessing.synchronize 会被禁用,尝试导入这个模块会引发 ImportError 异常,详细信息请查看 bpo-3770

共享 ctypes 对象

在共享内存上创建可被子进程继承的共享对象时是可行的。

multiprocessing.Value(typecode_or_type, \args, lock=True*)

返回一个从共享内存上创建的 ctypes 对象。默认情况下返回的对象实际上是经过了同步器包装过的。可以通过 Valuevalue 属性访问这个对象本身。

typecode_or_type 指明了返回的对象类型: 它可能是一个 ctypes 类型或者 array 模块中每个类型对应的单字符长度的字符串。 \args* 会透传给这个类的构造函数。

如果 lock 参数是 True (默认值), 将会新建一个递归锁用于同步对于此值的访问操作。 如果 lockLock 或者 RLock 对象,那么这个传入的锁将会用于同步对这个值的访问操作,如果 lockFalse , 那么对这个对象的访问将没有锁保护,也就是说这个变量不是进程安全的。

诸如 += 这类的操作会引发独立的读操作和写操作,也就是说这类操作符并不具有原子性。所以,如果你想让递增共享变量的操作具有原子性,仅仅以这样的方式并不能达到要求:

counter.value += 1

共享对象内部关联的锁是递归锁(默认情况下就是)的情况下, 你可以采用这种方式

with counter.get_lock():    
    counter.value += 1

注意 lock 只能是命名参数。

multiprocessing.Array(typecode_or_type, size_or_initializer, **, lock=True*)

从共享内存中申请并返回一个具有ctypes类型的数组对象。默认情况下返回值实际上是被同步器包装过的数组对象。

typecode_or_type 指明了返回的数组中的元素类型: 它可能是一个 ctypes 类型或者 array 模块中每个类型对应的单字符长度的字符串。 如果 size_or_initializer 是一个整数,那就会当做数组的长度,并且整个数组的内存会初始化为0。否则,如果 size_or_initializer 会被当成一个序列用于初始化数组中的每一个元素,并且会根据元素个数自动判断数组的长度。

如果 lockTrue (默认值) 则将创建一个新的锁对象用于同步对值的访问。 如果 lock 为一个 LockRLock 对象则该对象将被用于同步对值的访问。 如果 lockFalse 则对返回对象的访问将不会自动得到锁的保护,也就是说它不是“进程安全的”。

请注意 lock 是一个仅限关键字参数。

请注意 ctypes.c_char 的数组具有 valueraw 属性,允许被用来保存和提取字符串。

multiprocessing.sharedctypes 模块

multiprocessing.sharedctypes 模块提供了一些函数,用于分配来自共享内存的、可被子进程继承的 ctypes 对象。

注解

虽然可以将指针存储在共享内存中,但请记住它所引用的是特定进程地址空间中的位置。 而且,指针很可能在第二个进程的上下文中无效,尝试从第二个进程对指针进行解引用可能会导致崩溃。

multiprocessing.sharedctypes.RawArray(typecode_or_type, size_or_initializer)

从共享内存中申请并返回一个 ctypes 数组。

typecode_or_type 指明了返回的数组中的元素类型: 它可能是一个 ctypes 类型或者 array 模块中使用的类型字符。 如果 size_or_initializer 是一个整数,那就会当做数组的长度,并且整个数组的内存会初始化为0。否则,如果 size_or_initializer 会被当成一个序列用于初始化数组中的每一个元素,并且会根据元素个数自动判断数组的长度。

注意对元素的访问、赋值操作可能是非原子操作 - 使用 Array() , 从而借助其中的锁保证操作的原子性。

multiprocessing.sharedctypes.RawValue(typecode_or_type, \args*)

从共享内存中申请并返回一个 ctypes 对象。

typecode_or_type 指明了返回的对象类型: 它可能是一个 ctypes 类型或者 array 模块中每个类型对应的单字符长度的字符串。 \args* 会透传给这个类的构造函数。

注意对 value 的访问、赋值操作可能是非原子操作 - 使用 Value() ,从而借助其中的锁保证操作的原子性。

请注意 ctypes.c_char 的数组具有 valueraw 属性,允许被用来保存和提取字符串 。

multiprocessing.sharedctypes.Array(typecode_or_type, size_or_initializer, **, lock=True*)

返回一个纯 ctypes 数组, 或者在此之上经过同步器包装过的进程安全的对象,这取决于 lock 参数的值,除此之外,和 RawArray() 一样。

如果 lockTrue (默认值) 则将创建一个新的锁对象用于同步对值的访问。 如果 lock 为一个 LockRLock 对象则该对象将被用于同步对值的访问。 如果 lockFalse 则对所返回对象的访问将不会自动得到锁的保护,也就是说它将不是“进程安全的”。

注意 lock 只能是命名参数。

multiprocessing.sharedctypes.Value(typecode_or_type, \args, lock=True*)

返回一个纯 ctypes 数组, 或者在此之上经过同步器包装过的进程安全的对象,这取决于 lock 参数的值,除此之外,和 RawArray() 一样。

如果 lockTrue (默认值) 则将创建一个新的锁对象用于同步对值的访问。 如果 lock 为一个 LockRLock 对象则该对象将被用于同步对值的访问。 如果 lockFalse 则对所返回对象的访问将不会自动得到锁的保护,也就是说它将不是“进程安全的”。

注意 lock 只能是命名参数。

multiprocessing.sharedctypes.copy(obj)

从共享内存中申请一片空间将 ctypes 对象 obj 过来,然后返回一个新的 ctypes 对象。

multiprocessing.sharedctypes.synchronized(obj[, lock])

将一个 ctypes 对象包装为进程安全的对象并返回,使用 lock 同步对于它的操作。如果 lockNone (默认值) ,则会自动创建一个 multiprocessing.RLock 对象。

同步器包装后的对象会在原有对象基础上额外增加两个方法: get_obj() 返回被包装的对象, get_lock() 返回内部用于同步的锁。

需要注意的是,访问包装后的ctypes对象会比直接访问原来的纯 ctypes 对象慢得多。

在 3.5 版更改: 同步器包装后的对象支持 context manager 协议。

下面的表格对比了创建普通ctypes对象和基于共享内存上创建共享ctypes对象的语法。(表格中的 MyStructctypes.Structure 的子类)

ctypes 使用类型的共享ctypes 使用 typecode 的共享 ctypes
c_double(2.4) RawValue(c_double, 2.4) RawValue(‘d’, 2.4)
MyStruct(4, 6) RawValue(MyStruct, 4, 6)
(c_short 7)() RawArray(c_short, 7) RawArray(‘h’, 7)
(c_int 3)(9, 2, 8) RawArray(c_int, (9, 2, 8)) RawArray(‘i’, (9, 2, 8))

下面是一个在子进程中修改多个ctypes对象的例子。

from multiprocessing import Process, Lock
from multiprocessing.sharedctypes import Value, Array
from ctypes import Structure, c_double
class Point(Structure):
    _fields_ = [('x', c_double), ('y', c_double)]
def modify(n, x, s, A):
    n.value **= 2
    x.value **= 2
    s.value = s.value.upper()
    for a in A:
        a.x **= 2
        a.y **= 2
if __name__ == '__main__':
    lock = Lock()
    n = Value('i', 7)
    x = Value(c_double, 1.0/3.0, lock=False)
    s = Array('c', b'hello world', lock=lock)
    A = Array(Point, [(1.875,-6.25), (-5.75,2.0), (2.375,9.5)], lock=lock)
    p = Process(target=modify, args=(n, x, s, A))
    p.start()
    p.join()
    print(n.value)
    print(x.value)
    print(s.value)
    print([(a.x, a.y) for a in A])

输出如下

49
0.1111111111111111
HELLO WORLD
[(3.515625, 39.0625), (33.0625, 4.0), (5.640625, 90.25)]

管理器

管理器提供了一种创建共享数据的方法,从而可以在不同进程中共享,甚至可以通过网络跨机器共享数据。管理器维护一个用于管理 共享对象 的服务。其他进程可以通过代理访问这些共享对象。

multiprocessing.Manager()

返回一个已启动的 SyncManager 管理器对象,这个对象可以用于在不同进程中共享数据。返回的管理器对象对应了一个已经启动的子进程,并且拥有一系列方法可以用于创建共享对象、返回对应的代理。

当管理器被垃圾回收或者父进程退出时,管理器进程会立即退出。管理器类定义在 multiprocessing.managers 模块:

class multiprocessing.managers.BaseManager([address[, authkey]])

创建一个 BaseManager 对象。

一旦创建,应该及时调用 start() 或者 get_server().serve_forever() 以确保管理器对象对应的管理进程已经启动。

address 是管理器服务进程监听的地址。如果 addressNone ,则允许和任意主机的请求建立连接。

authkey 是认证标识,用于检查连接服务进程的请求合法性。如果 authkeyNone, 则会使用 current_process().authkey , 否则,就使用 authkey , 需要保证它必须是 byte 类型的字符串。

  • start([initializer[, initargs]])

    为管理器开启一个子进程,如果 initializer 不是 None , 子进程在启动时将会调用 initializer(*initargs)

  • get_server()

    返回一个 Server 对象,它是管理器在后台控制的真实的服务。 Server 对象拥有 serve_forever() 方法。

    >>> from multiprocessing.managers import BaseManager
    >>> manager = BaseManager(address=('', 50000), authkey=b'abc')
    >>> server = manager.get_server()
    >>> server.serve_forever()

    Server 额外拥有一个 address 属性。

  • connect()

    将本地管理器对象连接到一个远程管理器进程:

    >>> from multiprocessing.managers import BaseManager
    >>> m = BaseManager(address=('127.0.0.1', 50000), authkey=b'abc')
    >>> m.connect()
  • shutdown()

    停止管理器的进程。这个方法只能用于已经使用 start() 启动的服务进程。

    它可以被多次调用。

  • register(typeid[, callable[, proxytype[, exposed[, method_to_typeid[, create_method]]]]])

    一个 classmethod,可以将一个类型或者可调用对象注册到管理器类。

    typeid 是一种 “类型标识符”,用于唯一表示某种共享对象类型,必须是一个字符串。

    callable 是一个用来为此类型标识符创建对象的可调用对象。如果一个管理器实例将使用 connect() 方法连接到服务器,或者 create_method 参数为 False,那么这里可留下 None

    proxytypeBaseProxy 的子类,可以根据 typeid 为共享对象创建一个代理,如果是 None , 则会自动创建一个代理类。

    exposed 是一个函数名组成的序列,用来指明只有这些方法可以使用 BaseProxy._callmethod() 代理。(如果 exposedNone, 则会在 proxytype._exposed_ 存在的情况下转而使用它) 当暴露的方法列表没有指定的时候,共享对象的所有 “公共方法” 都会被代理。(这里的“公共方法”是指所有拥有 __call__() 方法并且不是以 '_' 开头的属性)

    method_to_typeid 是一个映射,用来指定那些应该返回代理对象的暴露方法所返回的类型。(如果 method_to_typeidNone, 则 proxytype._method_to_typeid_ 会在存在的情况下被使用)如果方法名称不在这个映射中或者映射是 None ,则方法返回的对象会是一个值拷贝。

    create_method 指明,是否要创建一个以 typeid 命名并返回一个代理对象的方法,这个函数会被服务进程用于创建共享对象,默认为 True

BaseManager 实例也有一个只读属性。

  • address

    管理器所用的地址。

在 3.3 版更改: 管理器对象支持上下文管理协议 。__enter__() 启动服务进程(如果它还没有启动)并且返回管理器对象, __exit__() 会调用 shutdown()

在之前的版本中,如果管理器服务进程没有启动, __enter__() 不会负责启动它。

class multiprocessing.managers.SyncManager

BaseManager 的子类,可用于进程的同步。这个类型的对象使用 multiprocessing.Manager() 创建。

它拥有一系列方法,可以为大部分常用数据类型创建并返回 代理对象 代理,用于进程间同步。甚至包括共享列表和字典。

  • Barrier(parties[, action[, timeout]])

    创建一个共享的 threading.Barrier 对象并返回它的代理。

    3.3 新版功能.

  • BoundedSemaphore([value])

    创建一个共享的 threading.BoundedSemaphore 对象并返回它的代理。

  • Condition([lock])

    创建一个共享的 threading.Condition 对象并返回它的代理。

    如果提供了 lock 参数,那它必须是 threading.Lockthreading.RLock 的代理对象。

    在 3.3 版更改: 新增了 wait_for() 方法。

  • Event()

    创建一个共享的 threading.Event 对象并返回它的代理。

  • Lock()

    创建一个共享的 threading.Lock 对象并返回它的代理。

  • Namespace()

    创建一个共享的 Namespace 对象并返回它的代理。

  • Queue([maxsize])

    创建一个共享的 queue.Queue 对象并返回它的代理。

  • RLock()

    创建一个共享的 threading.RLock 对象并返回它的代理。

  • Semaphore([value])

    创建一个共享的 threading.Semaphore 对象并返回它的代理。

  • Array(typecode, sequence)

    创建一个数组并返回它的代理。

  • Value(typecode, value)

    创建一个具有可写 value 属性的对象并返回它的代理。

  • dict()

    dict(mapping)

    dict(sequence)

    创建一个共享的 dict 对象并返回它的代理。

  • list()

    list(sequence)

    创建一个共享的 list 对象并返回它的代理。

在 3.6 版更改: 共享对象能够嵌套。例如, 共享的容器对象如共享列表,可以包含另一个共享对象,他们全都会在 SyncManager 中进行管理和同步。

class multiprocessing.managers.Namespace

一个可以注册到 SyncManager 的类型。

命名空间对象没有公共方法,但是拥有可写的属性。直接print会显示所有属性的值。

值得一提的是,当对命名空间对象使用代理的时候,访问所有名称以 '_' 开头的属性都只是代理器上的属性,而不是命名空间对象的属性。

>>> manager = multiprocessing.Manager()
>>> Global = manager.Namespace()
>>> Global.x = 10
>>> Global.y = 'hello'
>>> Global._z = 12.3    # this is an attribute of the proxy
>>> print(Global)
Namespace(x=10, y='hello')
自定义管理器

要创建一个自定义的管理器,需要新建一个 BaseManager 的子类,然后使用这个管理器类上的 register() 类方法将新类型或者可调用方法注册上去。例如:

from multiprocessing.managers import BaseManager
class MathsClass:
    def add(self, x, y):
        return x + y
    def mul(self, x, y):
        return x * y
class MyManager(BaseManager):
    pass
MyManager.register('Maths', MathsClass)
if __name__ == '__main__':
    with MyManager() as manager:
        maths = manager.Maths()
        print(maths.add(4, 3))         # prints 7
        print(maths.mul(7, 8))         # prints 56
使用远程管理器

可以将管理器服务运行在一台机器上,然后使用客户端从其他机器上访问。(假设它们的防火墙允许)

运行下面的代码可以启动一个服务,此付包含了一个共享队列,允许远程客户端访问:

>>> from multiprocessing.managers import BaseManager
>>> from queue import Queue
>>> queue = Queue()
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue', callable=lambda:queue)
>>> m = QueueManager(address=('', 50000), authkey=b'abracadabra')
>>> s = m.get_server()
>>> s.serve_forever()

远程客户端可以通过下面的方式访问服务:

>>> from multiprocessing.managers import BaseManager
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue')
>>> m = QueueManager(address=('foo.bar.org', 50000), authkey=b'abracadabra')
>>> m.connect()
>>> queue = m.get_queue()
>>> queue.put('hello')

也可以通过下面的方式:

>>> from multiprocessing.managers import BaseManager
>>> class QueueManager(BaseManager): pass
>>> QueueManager.register('get_queue')
>>> m = QueueManager(address=('foo.bar.org', 50000), authkey=b'abracadabra')
>>> m.connect()
>>> queue = m.get_queue()
>>> queue.get()
'hello'

本地进程也可以访问这个队列,利用上面的客户端代码通过远程方式访问:

>>> from multiprocessing import Process, Queue
>>> from multiprocessing.managers import BaseManager
>>> class Worker(Process):
...     def __init__(self, q):
...         self.q = q
...         super().__init__()
...     def run(self):
...         self.q.put('local hello')
...
>>> queue = Queue()
>>> w = Worker(queue)
>>> w.start()
>>> class QueueManager(BaseManager): pass
...
>>> QueueManager.register('get_queue', callable=lambda: queue)
>>> m = QueueManager(address=('', 50000), authkey=b'abracadabra')
>>> s = m.get_server()
>>> s.serve_forever()

代理对象

代理是一个 指向 其他共享对象的对象,这个对象(很可能)在另外一个进程中。共享对象也可以说是代理 指涉 的对象。多个代理对象可能指向同一个指涉对象。

代理对象代理了指涉对象的一系列方法调用(虽然并不是指涉对象的每个方法都有必要被代理)。通过这种方式,代理的使用方法可以和它的指涉对象一样:

>>> from multiprocessing import Manager
>>> manager = Manager()
>>> l = manager.list([i*i for i in range(10)])
>>> print(l)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> print(repr(l))
<ListProxy object, typeid 'list' at 0x...>
>>> l[4]
16
>>> l[2:5]
[4, 9, 16]

注意,对代理使用 str() 函数会返回指涉对象的字符串表示,但是 repr() 却会返回代理本身的内部字符串表示。

被代理的对象很重要的一点是必须可以被序列化,这样才能允许他们在进程间传递。因此,指涉对象可以包含 代理对象 。这允许管理器中列表、字典或者其他 代理对象 对象之间的嵌套。

>>> a = manager.list()
>>> b = manager.list()
>>> a.append(b)         # referent of a now contains referent of b
>>> print(a, b)
[<ListProxy object, typeid 'list' at ...>] []
>>> b.append('hello')
>>> print(a[0], b)
['hello'] ['hello']

类似地,字典和列表代理也可以相互嵌套:

>>> l_outer = manager.list([ manager.dict() for i in range(2) ])
>>> d_first_inner = l_outer[0]
>>> d_first_inner['a'] = 1
>>> d_first_inner['b'] = 2
>>> l_outer[1]['c'] = 3
>>> l_outer[1]['z'] = 26
>>> print(l_outer[0])
{'a': 1, 'b': 2}
>>> print(l_outer[1])
{'c': 3, 'z': 26}

如果指涉对象包含了普通 listdict 对象,对这些内部可变对象的修改不会通过管理器传播,因为代理无法得知被包含的值什么时候被修改了。但是把存放在容器代理中的值本身是会通过管理器传播的(会触发代理对象中的 __setitem__ )从而有效修改这些对象,所以可以把修改过的值重新赋值给容器代理:

# create a list proxy and append a mutable object (a dictionary)
lproxy = manager.list()
lproxy.append({})
# now mutate the dictionary
d = lproxy[0]
d['a'] = 1
d['b'] = 2
# at this point, the changes to d are not yet synced, but by
# updating the dictionary, the proxy is notified of the change
lproxy[0] = d

在大多是使用情形下,这种实现方式并不比嵌套 代理对象 方便,但是依然演示了对于同步的一种控制级别。

注解

multiprocessing 中的代理类并没有提供任何对于代理值比较的支持。所以,我们会得到如下结果:

>>> manager.list([1,2,3]) == [1,2,3]
False

当需要比较值的时候,应该替换为使用指涉对象的拷贝。

class multiprocessing.managers.BaseProxy

代理对象是 BaseProxy 派生类的实例。

  • _callmethod(methodname[, args[, kwds]])

    调用指涉对象的方法并返回结果。

    如果 proxy 是一个代理且其指涉的是 obj , 那么下面的表达式:

    proxy._callmethod(methodname, args, kwds)

    相当于求取以下表达式的值:

    getattr(obj, methodname)(*args, **kwds)

    于管理器进程。

    返回结果会是一个值拷贝或者一个新的共享对象的代理 - 见函数 BaseManager.register() 中关于参数 method_to_typeid 的文档。

    如果这个调用熬出了异常,则这个异常会被 _callmethod() 透传出来。如果是管理器进程本身抛出的一些其他异常,则会被 _callmethod() 转换为 RemoteError 异常重新抛出。

    特别注意,如果 methodname 没有 暴露 出来,将会引发一个异常。

    _callmethod() 的一个使用示例:

    >>> l = manager.list(range(10))
    >>> l._callmethod('__len__')
    10
    >>> l._callmethod('__getitem__', (slice(2, 7),)) # equivalent to l[2:7]
    [2, 3, 4, 5, 6]
    >>> l._callmethod('__getitem__', (20,))          # equivalent to l[20]
    Traceback (most recent call last):
    ...
    IndexError: list index out of range
  • _getvalue()

    返回指涉对象的一份拷贝。

    如果指涉对象无法序列化,则会抛出一个异常。

  • __repr__()

    返回代理对象的内部字符串表示。

  • __str__()

    返回指涉对象的内部字符串表示。

清理

代理对象使用了一个弱引用回调函数,当它被垃圾回收时,会将自己从拥有此指涉对象的管理器上反注册,

当共享对象没有被任何代理器引用时,会被管理器进程删除。

进程池

可以创建一个进程池,它将使用 Pool 类执行提交给它的任务。

class multiprocessing.pool.Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])

一个进程池对象,它控制可以提交作业的工作进程池。它支持带有超时和回调的异步结果,以及一个并行的 map 实现。

processes 是要使用的工作进程数目。如果 processesNone,则使用 os.cpu_count() 返回的值。

如果 initializer 不为 None,则每个工作进程将会在启动时调用 initializer(*initargs)

maxtasksperchild 是一个工作进程在它退出或被一个新的工作进程代替之前能完成的任务数量,为了释放未使用的资源。默认的 maxtasksperchildNone,意味着工作进程寿与池齐。

context 可被用于指定启动的工作进程的上下文。通常一个进程池是使用函数 multiprocessing.Pool() 或者一个上下文对象的 Pool() 方法创建的。在这两种情况下, context 都是适当设置的。

注意,进程池对象的方法只有创建它的进程能够调用。

警告

multiprocessing.pool 对象具有需要正确管理的内部资源 (像任何其他资源一样),具体方式是将进程池用作上下文管理器,或者手动调用 close()terminate()。 未做此类操作将导致进程在终结阶段挂起。

请注意依赖垃圾回收器来销毁进程池是 不正确的 做法,因为 CPython 并不保证进程池终结器会被调用

3.2 新版功能: maxtasksperchild

3.4 新版功能: context

注解

通常来说,Pool 中的 Worker 进程的生命周期和进程池的工作队列一样长。一些其他系统中(如 Apache, mod_wsgi 等)也可以发现另一种模式,他们会让工作进程在完成一些任务后退出,清理、释放资源,然后启动一个新的进程代替旧的工作进程。 Poolmaxtasksperchild 参数给用户提供了这种能力。

  • apply(func[, args[, kwds]])

    使用 args 参数以及 kwds 命名参数调用 func , 它会返回结果前阻塞。这种情况下,apply_async() 更适合并行化工作。另外 func 只会在一个进程池中的一个工作进程中执行。

  • apply_async(func[, args[, kwds[, callback[, error_callback]]]])

    apply() 方法的一个变种,返回一个 AsyncResult 对象。

    如果指定了 callback , 它必须是一个接受单个参数的可调用对象。当执行成功时, callback 会被用于处理执行后的返回结果,否则,调用 error_callback

    如果指定了 error_callback , 它必须是一个接受单个参数的可调用对象。当目标函数执行失败时, 会将抛出的异常对象作为参数传递给 error_callback 执行。

    回调函数应该立即执行完成,否则会阻塞负责处理结果的线程。

  • map(func, iterable[, chunksize])

    内置 map() 函数的并行版本 (但它只支持一个 iterable 参数,对于多个可迭代对象请参阅 starmap())。 它会保持阻塞直到获得结果。

    这个方法会将可迭代对象分割为许多块,然后提交给进程池。可以将 chunksize 设置为一个正整数从而(近似)指定每个块的大小可以。

    注意对于很长的迭代对象,可能消耗很多内存。可以考虑使用 imap()imap_unordered() 并且显示指定 chunksize 以提升效率。

  • map_async(func, iterable[, chunksize[, callback[, error_callback]]])

    map() 方法的一个变种,返回一个 AsyncResult 对象。

    如果指定了 callback , 它必须是一个接受单个参数的可调用对象。当执行成功时, callback 会被用于处理执行后的返回结果,否则,调用 error_callback

    如果指定了 error_callback , 它必须是一个接受单个参数的可调用对象。当目标函数执行失败时, 会将抛出的异常对象作为参数传递给 error_callback 执行。

    回调函数应该立即执行完成,否则会阻塞负责处理结果的线程。

  • imap(func, iterable[, chunksize])

    map() 的延迟执行版本。

    chunksize 参数的作用和 map() 方法的一样。对于很长的迭代器,给 chunksize 设置一个很大的值会比默认值 1 极大 地加快执行速度。

    同样,如果 chunksize1 , 那么 imap() 方法所返回的迭代器的 next() 方法拥有一个可选的 timeout 参数: 如果无法在 timeout 秒内执行得到结果,则next(timeout) 会抛出 multiprocessing.TimeoutError 异常。

  • imap_unordered(func, iterable[, chunksize])

    imap() 相同,只不过通过迭代器返回的结果是任意的。(当进程池中只有一个工作进程的时候,返回结果的顺序才能认为是”有序”的)

  • starmap(func, iterable[, chunksize])

    map() 类似,不过 iterable 中的每一项会被解包再作为函数参数。

    比如可迭代对象 [(1,2), (3, 4)] 会转化为等价于 [func(1,2), func(3,4)] 的调用。

    3.3 新版功能.

  • starmap_async(func, iterable[, chunksize[, callback[, error_callback]]])

    相当于 starmap()map_async() 的结合,迭代 iterable 的每一项,解包作为 func 的参数并执行,返回用于获取结果的对象。

    3.3 新版功能.

  • close()

    阻止后续任务提交到进程池,当所有任务执行完成后,工作进程会退出。

  • terminate()

    不必等待未完成的任务,立即停止工作进程。当进程池对象被垃圾回收时,会立即调用 terminate()

  • join()

    等待工作进程结束。调用 join() 前必须先调用 close() 或者 terminate()

3.3 新版功能: 进程池对象现在支持上下文管理器协议。__enter__() 返回进程池对象, __exit__() 会调用 terminate()

class multiprocessing.pool.AsyncResult

Pool.apply_async()Pool.map_async() 返回对象所属的类。

  • get([timeout])

    用于获取执行结果。如果 timeout 不是 None 并且在 timeout 秒内仍然没有执行完得到结果,则抛出 multiprocessing.TimeoutError 异常。如果远程调用发生异常,这个异常会通过 get() 重新抛出。

  • wait([timeout])

    阻塞,直到返回结果,或者 timeout 秒后超时。

  • ready()

    返回执行状态,是否已经完成。

  • successful()

    判断调用是否已经完成并且未引发异常。 如果还未获得结果则将引发 ValueError

    在 3.7 版更改: 如果没有执行完,会抛出 ValueError 异常而不是 AssertionError

下面的例子演示了进程池的用法:

from multiprocessing import Pool
import time
def f(x):
    return x*x
if __name__ == '__main__':
    with Pool(processes=4) as pool:         # start 4 worker processes
        result = pool.apply_async(f, (10,)) # evaluate "f(10)" asynchronously in a single process
        print(result.get(timeout=1))        # prints "100" unless your computer is *very* slow
        print(pool.map(f, range(10)))       # prints "[0, 1, 4,..., 81]"
        it = pool.imap(f, range(10))
        print(next(it))                     # prints "0"
        print(next(it))                     # prints "1"
        print(it.next(timeout=1))           # prints "4" unless your computer is *very* slow
        result = pool.apply_async(time.sleep, (10,))
        print(result.get(timeout=1))        # raises multiprocessing.TimeoutError

监听器及客户端

通常情况下,进程间通过队列或者 Pipe() 返回的 Connection 传递消息。

不过,multiprocessing.connection 模块其实提供了一些更灵活的特性。最基础的用法是通过它抽象出来的高级API来操作socket或者Windows命名管道。也提供一些高级用法,如通过 hmac 模块来支持 摘要认证,以及同时监听多个管道连接。

multiprocessing.connection.deliver_challenge(connection, authkey)

发送一个随机生成的消息到另一端,并等待回复。

如果收到的回复与使用 authkey 作为键生成的信息摘要匹配成功,就会发送一个欢迎信息给管道另一端。否则抛出 AuthenticationError 异常。

multiprocessing.connection.answer_challenge(connection, authkey)

接收一条信息,使用 authkey 作为键计算信息摘要,然后将摘要发送回去。

如果没有收到欢迎消息,就抛出 AuthenticationError 异常。

multiprocessing.connection.Client(address[, family[, authkey]])

尝试使用 address 地址上的监听器建立一个连接,返回 Connection

连接的类型取决于 family 参数,但是通常可以省略,因为可以通过 address 的格式推导出来。

如果提供了 authkey 参数并且不是 None,那它必须是一个字符串并且会被当做基于 HMAC 认证的密钥。如果 authkey 是None 则不会有认证行为。认证失败抛出 AuthenticationError 异常,请查看 See 认证密码 。

class multiprocessing.connection.Listener([address[, family[, backlog[, authkey]]]])

可以监听连接请求,是对于绑定套接字或者 Windows 命名管道的封装。

address 是监听器对象中的绑定套接字或命名管道使用的地址。

注解

如果使用 ‘0.0.0.0’ 作为监听地址,那么在Windows上这个地址无法建立连接。想要建立一个可连接的端点,应该使用 ‘127.0.0.1’ 。

family 是套接字(或者命名管道)使用的类型。它可以是以下一种: 'AF_INET' ( TCP 套接字类型), 'AF_UNIX' ( Unix 域套接字) 或者 'AF_PIPE' ( Windows 命名管道)。其中只有第一个保证各平台可用。如果 familyNone ,那么 family 会根据 address 的格式自动推导出来。如果 address 也是 None , 则取默认值。默认值为可用类型中速度最快的。注意,如果 family'AF_UNIX' 而address是None ,套接字会在一个 tempfile.mkstemp() 创建的私有临时目录中创建。

如果监听器对象使用了套接字,backlog (默认值为1) 会在套接字绑定后传递给它的 listen() 方法。

如果提供了 authkey 参数并且不是 None,那它必须是一个字符串并且会被当做基于 HMAC 认证的密钥。如果 authkey 是None 则不会有认证行为。认证失败抛出 AuthenticationError 异常。

  • accept()

    接受一个连接并返回一个 Connection 对象,其连接到的监听器对象已绑定套接字或者命名管道。如果已经尝试过认证并且失败了,则会抛出 AuthenticationError 异常。

  • close()

    关闭监听器对象上的绑定套接字或者命名管道。此函数会在监听器被垃圾回收后自动调用。不过仍然建议显式调用函数关闭。

监听器对象拥有下列只读属性:

  • address

    监听器对象使用的地址。

  • last_accepted

    最后一个连接所使用的地址。如果没有的话就是 None

3.3 新版功能: 监听器对象现在支持了上下文管理协议 。 __enter__() 返回一个监听器对象, __exit__() 会调用 close()

multiprocessing.connection.wait(object_list, timeout=None)

一直等待直到 object_list 中某个对象处于就绪状态。返回 object_list 中处于就绪状态的对象。如果 timeout 是一个浮点型,该方法会最多阻塞这么多秒。如果 timeoutNone ,则会允许阻塞的事件没有限制。timeout为负数的情况下和为0的情况相同。

对于 Unix 和 Windows ,满足下列条件的对象可以出现在 object_list

  • 可读的 Connection 对象;
  • 一个已连接并且可读的 socket.socket 对象;或者
  • Process 对象中的 sentinel 属性。

当一个连接或者套接字对象拥有有效的数据可被读取的时候,或者另一端关闭后,这个对象就处于就绪状态。

Unix: wait(object_list, timeout)select.select(object_list, [], [], timeout) 几乎相同。差别在于,如果 select.select() 被信号中断,它会抛出一个附带错误号为 EINTROSError 异常,而 wait() 不会。

Windows: object_list 中的元素必须是一个表示为整数的可等待的句柄(按照 Win32 函数 WaitForMultipleObjects() 的文档中所定义) 或者一个拥有 fileno() 方法的对象,这个对象返回一个套接字句柄或者管道句柄。(注意管道和套接字两种句柄 不是 可等待的句柄)

3.3 新版功能.

示例

下面的服务代码创建了一个使用 'secret password' 作为认证密码的监听器。它会等待连接然后发送一些数据给客户端:

from multiprocessing.connection import Listener
from array import array
address = ('localhost', 6000)     # family is deduced to be 'AF_INET'
with Listener(address, authkey=b'secret password') as listener:
    with listener.accept() as conn:
        print('connection accepted from', listener.last_accepted)
        conn.send([2.25, None, 'junk', float])
        conn.send_bytes(b'hello')
        conn.send_bytes(array('i', [42, 1729]))

下面的代码连接到服务然后从服务器上j接收一些数据:

from multiprocessing.connection import Client
from array import array
address = ('localhost', 6000)
with Client(address, authkey=b'secret password') as conn:
    print(conn.recv())                  # => [2.25, None, 'junk', float]
    print(conn.recv_bytes())            # => 'hello'
    arr = array('i', [0, 0, 0, 0, 0])
    print(conn.recv_bytes_into(arr))    # => 8
    print(arr)                          # => array('i', [42, 1729, 0, 0, 0])

下面的代码使用了 wait() ,以便在同时等待多个进程发来消息。

import time, random
from multiprocessing import Process, Pipe, current_process
from multiprocessing.connection import wait
def foo(w):
    for i in range(10):
        w.send((i, current_process().name))
    w.close()
if __name__ == '__main__':
    readers = []
    for i in range(4):
        r, w = Pipe(duplex=False)
        readers.append(r)
        p = Process(target=foo, args=(w,))
        p.start()
        # We close the writable end of the pipe now to be sure that
        # p is the only process which owns a handle for it.  This
        # ensures that when p closes its handle for the writable end,
        # wait() will promptly report the readable end as being ready.
        w.close()
    while readers:
        for r in wait(readers):
            try:
                msg = r.recv()
            except EOFError:
                readers.remove(r)
            else:
                print(msg)
地址格式
  • 'AF_INET' 地址是 (hostname, port) 形式的元组类型,其中 hostname 是一个字符串,port 是整数。
  • 'AF_UNIX' 地址是文件系统上文件名的字符串。
  • 'AF_PIPE' 地址是一个 r'\.\pipe{PipeName}' 形式的字符串。 要使用 Client() 来连接到远程计算机上一个名为 ServerName 的命名管道,则应当改用 r'\*ServerName*\pipe{PipeName}' 形式的地址。

注意,使用两个反斜线开头的字符串默认被当做 'AF_PIPE' 地址而不是 'AF_UNIX'

认证密码

当使用 Connection.recv 接收数据时,数据会自动被反序列化。不幸的是,对于一个不可信的数据源发来的数据,反序列化是存在安全风险的。所以 ListenerClient() 之间使用 hmac 模块进行摘要认证。

认证密钥是一个 byte 类型的字符串,可以认为是和密码一样的东西,连接建立好后,双方都会要求另一方证明知道认证密钥。(这个证明过程不会通过连接发送密钥)

如果要求认证但是没有指定认证密钥,则会使用 current_process().authkey 的返回值。 这个值将被当前进程所创建的任何 Process 对象自动继承。 这意味着 (在默认情况下) 一个包含多进程的程序中的所有进程会在相互间建立连接的时候共享单个认证密钥。

os.urandom() 也可以用来生成合适的认证密钥。

日志记录

当前模块也提供了一些对 logging 的支持。注意, logging 模块本身并没有使用进程间共享的锁,所以来自于多个进程的日志可能(具体取决于使用的日志 handler 类型)相互覆盖或者混杂。

multiprocessing.get_logger()

返回 multiprocessing 使用的 logger,必要的话会创建一个新的。

如果创建的首个 logger 日志级别为 logging.NOTSET 并且没有默认 handler。通过这个 logger 打印的消息不会传递到根 logger。

注意在 Windows 上,子进程只会继承父进程 logger 的日志级别 - 对于logger的其他自定义项不会继承。

multiprocessing.log_to_stderr()

此函数会调用 get_logger() 但是会在返回的 logger 上增加一个 handler,将所有输出都使用 '[%(levelname)s/%(processName)s] %(message)s' 的格式发送到 sys.stderr

下面是一个在交互式解释器中打开日志功能的例子:

>>> import multiprocessing, logging
>>> logger = multiprocessing.log_to_stderr()
>>> logger.setLevel(logging.INFO)
>>> logger.warning('doomed')
[WARNING/MainProcess] doomed
>>> m = multiprocessing.Manager()
[INFO/SyncManager-...] child process calling self.run()
[INFO/SyncManager-...] created temp directory /.../pymp-...
[INFO/SyncManager-...] manager serving at '/.../listener-...'
>>> del m
[INFO/MainProcess] sending shutdown message to manager
[INFO/SyncManager-...] manager exiting with exitcode 0

multiprocessing.dummy 模块

multiprocessing.dummy 复制了 multiprocessing 的 API,不过是在 threading 模块之上包装了一层。

特别地,multiprocessing.dummy 所提供的 Pool 函数会返回一个 ThreadPool 的实例,该类是 Pool 的子类,它支持所有相同的方法调用但会使用一个工作线程池而非工作进程池。

class multiprocessing.pool.ThreadPool([processes[, initializer[, initargs]]])

一个线程池对象,用来控制可向其提交任务的工作线程池。 ThreadPool 实例与 Pool 实例是完全接口兼容的,并且它们的资源也必须被正确地管理,或者是将线程池作为上下文管理器来使用,或者是通过手动调用 close()terminate()

processes 是要使用的工作线程数目。 如果 processesNone,则使用 os.cpu_count() 返回的值。

如果 initializer 不为 None,则每个工作进程将会在启动时调用 initializer(*initargs)

不同于 Poolmaxtasksperchildcontext 不可被提供。

注解

ThreadPool 具有与 Pool 相同的接口,它围绕一个进程池进行设计并且先于 concurrent.futures 模块的引入。 因此,它继承了一些对于基于线程的池来说没有意义的操作,并且它具有自己的用于表示异步任务状态的类型 AsyncResult,该类型不为任何其他库所知。

用户通常应该倾向于使用 concurrent.futures.ThreadPoolExecutor,它拥有从一开始就围绕线程进行设计的更简单接口,并且返回与许多其他库相兼容的 concurrent.futures.Future 实例,包括 asyncio 库。

编程指导

使用 multiprocessing 时,应遵循一些指导原则和习惯用法。

所有start方法

下面这些适用于所有start方法。

避免共享状态

应该尽可能避免在进程间传递大量数据,越少越好。

最好坚持使用队列或者管道进行进程间通信,而不是底层的同步原语。

可序列化

保证所代理的方法的参数是可以序列化的。

代理的线程安全性

不要在多线程中同时使用一个代理对象,除非你用锁保护它。

(而在不同进程中使用 相同 的代理对象却没有问题。)

使用 Join 避免僵尸进程

在 Unix 上,如果一个进程执行完成但是没有被 join,就会变成僵尸进程。一般来说,僵尸进程不会很多,因为每次新启动进程(或者 active_children() 被调用)时,所有已执行完成且没有被 join 的进程都会自动被 join,而且对一个执行完的进程调用 Process.is_alive 也会 join 这个进程。尽管如此,对自己启动的进程显式调用 join 依然是最佳实践。

继承优于序列化、反序列化

当使用 spawn 或者 forkserver 的启动方式时,multiprocessing 中的许多类型都必须是可序列化的,这样子进程才能使用它们。但是通常我们都应该避免使用管道和队列发送共享对象到另外一个进程,而是重新组织代码,对于其他进程创建出来的共享对象,让那些需要访问这些对象的子进程可以直接将这些对象从父进程继承过来。

避免杀死进程

听过 Process.terminate 停止一个进程很容易导致这个进程正在使用的共享资源(如锁、信号量、管道和队列)损坏或者变得不可用,无法在其他进程中继续使用。

所以,最好只对那些从来不使用共享资源的进程调用 Process.terminate

Join 使用队列的进程

记住,往队列放入数据的进程会一直等待直到队列中所有项被”feeder” 线程传给底层管道。(子进程可以调用队列的 Queue.cancel_join_thread 方法禁止这种行为)

这意味着,任何使用队列的时候,你都要确保在进程join之前,所有存放到队列中的项将会被其他进程、线程完全消费。否则不能保证这个写过队列的进程可以正常终止。记住非精灵进程会自动 join 。

下面是一个会导致死锁的例子:

from multiprocessing import Process, Queue
def f(q):
    q.put('X' * 1000000)
if __name__ == '__main__':
    queue = Queue()
    p = Process(target=f, args=(queue,))
    p.start()
    p.join()                    # this deadlocks
    obj = queue.get()

交换最后两行可以修复这个问题(或者直接删掉 p.join())。

显式传递资源给子进程

在Unix上,使用 fork 方式启动的子进程可以使用父进程中全局创建的共享资源。不过,最好是显式将资源对象通过参数的形式传递给子进程。

除了(部分原因)让代码兼容 Windows 以及其他的进程启动方式外,这种形式还保证了在子进程生命期这个对象是不会被父进程垃圾回收的。如果父进程中的某些对象被垃圾回收会导致资源释放,这就变得很重要。

所以对于实例:

from multiprocessing import Process, Lock
def f():
    ... do something using "lock" ...
if __name__ == '__main__':
    lock = Lock()
    for i in range(10):
        Process(target=f).start()

应当重写成这样:

from multiprocessing import Process, Lock
def f(l):
    ... do something using "l" ...
if __name__ == '__main__':
    lock = Lock()
    for i in range(10):
        Process(target=f, args=(lock,)).start()

谨防将 sys.stdin 数据替换为 “类似文件的对象”

multiprocessing 原本会无条件地这样调用:

os.close(sys.stdin.fileno())

multiprocessing.Process._bootstrap() 方法中 —— 这会导致与”进程中的进程”相关的一些问题。这已经被修改成了:

sys.stdin.close()
sys.stdin = open(os.open(os.devnull, os.O_RDONLY), closefd=False)

它解决了进程相互冲突导致文件描述符错误的根本问题,但是对使用带缓冲的“文件类对象”替换 sys.stdin() 作为输出的应用程序造成了潜在的危险。如果多个进程调用了此文件类对象的 close() 方法,会导致相同的数据多次刷写到此对象,损坏数据。

如果你写入文件类对象并实现了自己的缓存,可以在每次追加缓存数据时记录当前进程id,从而将其变成 fork 安全的,当发现进程id变化后舍弃之前的缓存,例如:

@property
def cache(self):
    pid = os.getpid()
    if pid != self._pid:
        self._pid = pid
        self._cache = []
    return self._cache

需要更多信息,请查看 bpo-5155, bpo-5313 以及 bpo-5331

spawnforkserver 启动方式

相对于 fork 启动方式,有一些额外的限制。

更依赖序列化

Process.__init__() 的所有参数都必须可序列化。同样的,当你继承 Process 时,需要保证当调用 Process.start 方法时,实例可以被序列化。

全局变量

记住,如果子进程中的代码尝试访问一个全局变量,它所看到的值(如果有)可能和父进程中执行 Process.start 那一刻的值不一样。

当全局变量知识模块级别的常量时,是不会有问题的。

安全导入主模块

确保主模块可以被新启动的Python解释器安全导入而不会引发什么副作用(比如又启动了一个子进程)

例如,使用 spawnforkserver 启动方式执行下面的模块,会引发 RuntimeError 异常而失败。

from multiprocessing import Process
def foo():
    print('hello')
p = Process(target=foo)
p.start()

应该通过下面的方法使用 if __name__ == '__main__': ,从而保护程序”入口点”:

from multiprocessing import Process, freeze_support, set_start_method
def foo():
    print('hello')
if __name__ == '__main__':
    freeze_support()
    set_start_method('spawn')
    p = Process(target=foo)
    p.start()

(如果程序将正常运行而不是冻结,则可以省略 freeze_support() 行)

这允许新启动的 Python 解释器安全导入模块然后运行模块中的 foo() 函数。

如果主模块中创建了进程池或者管理器,这个规则也适用。

例子

创建和使用自定义管理器、代理的示例:

from multiprocessing import freeze_support
from multiprocessing.managers import BaseManager, BaseProxy
import operator
##
class Foo:
    def f(self):
        print('you called Foo.f()')
    def g(self):
        print('you called Foo.g()')
    def _h(self):
        print('you called Foo._h()')
# A simple generator function
def baz():
    for i in range(10):
        yield i*i
# Proxy type for generator objects
class GeneratorProxy(BaseProxy):
    _exposed_ = ['__next__']
    def __iter__(self):
        return self
    def __next__(self):
        return self._callmethod('__next__')
# Function to return the operator module
def get_operator_module():
    return operator
##
class MyManager(BaseManager):
    pass
# register the Foo class; make `f()` and `g()` accessible via proxy
MyManager.register('Foo1', Foo)
# register the Foo class; make `g()` and `_h()` accessible via proxy
MyManager.register('Foo2', Foo, exposed=('g', '_h'))
# register the generator function baz; use `GeneratorProxy` to make proxies
MyManager.register('baz', baz, proxytype=GeneratorProxy)
# register get_operator_module(); make public functions accessible via proxy
MyManager.register('operator', get_operator_module)
##
def test():
    manager = MyManager()
    manager.start()
    print('-' * 20)
    f1 = manager.Foo1()
    f1.f()
    f1.g()
    assert not hasattr(f1, '_h')
    assert sorted(f1._exposed_) == sorted(['f', 'g'])
    print('-' * 20)
    f2 = manager.Foo2()
    f2.g()
    f2._h()
    assert not hasattr(f2, 'f')
    assert sorted(f2._exposed_) == sorted(['g', '_h'])
    print('-' * 20)
    it = manager.baz()
    for i in it:
        print('<%d>' % i, end=' ')
    print()
    print('-' * 20)
    op = manager.operator()
    print('op.add(23, 45) =', op.add(23, 45))
    print('op.pow(2, 94) =', op.pow(2, 94))
    print('op._exposed_ =', op._exposed_)
##
if __name__ == '__main__':
    freeze_support()
    test()

使用 Pool:

import multiprocessing
import time
import random
import sys
#
# Functions used by test code
#
def calculate(func, args):
    result = func(*args)
    return '%s says that %s%s = %s' % (
        multiprocessing.current_process().name,
        func.__name__, args, result
        )
def calculatestar(args):
    return calculate(*args)
def mul(a, b):
    time.sleep(0.5 * random.random())
    return a * b
def plus(a, b):
    time.sleep(0.5 * random.random())
    return a + b
def f(x):
    return 1.0 / (x - 5.0)
def pow3(x):
    return x ** 3
def noop(x):
    pass
#
# Test code
#
def test():
    PROCESSES = 4
    print('Creating pool with %d processes\n' % PROCESSES)
    with multiprocessing.Pool(PROCESSES) as pool:
        #
        # Tests
        #
        TASKS = [(mul, (i, 7)) for i in range(10)] + \
                [(plus, (i, 8)) for i in range(10)]
        results = [pool.apply_async(calculate, t) for t in TASKS]
        imap_it = pool.imap(calculatestar, TASKS)
        imap_unordered_it = pool.imap_unordered(calculatestar, TASKS)
        print('Ordered results using pool.apply_async():')
        for r in results:
            print('\t', r.get())
        print()
        print('Ordered results using pool.imap():')
        for x in imap_it:
            print('\t', x)
        print()
        print('Unordered results using pool.imap_unordered():')
        for x in imap_unordered_it:
            print('\t', x)
        print()
        print('Ordered results using pool.map() --- will block till complete:')
        for x in pool.map(calculatestar, TASKS):
            print('\t', x)
        print()
        #
        # Test error handling
        #
        print('Testing error handling:')
        try:
            print(pool.apply(f, (5,)))
        except ZeroDivisionError:
            print('\tGot ZeroDivisionError as expected from pool.apply()')
        else:
            raise AssertionError('expected ZeroDivisionError')
        try:
            print(pool.map(f, list(range(10))))
        except ZeroDivisionError:
            print('\tGot ZeroDivisionError as expected from pool.map()')
        else:
            raise AssertionError('expected ZeroDivisionError')
        try:
            print(list(pool.imap(f, list(range(10)))))
        except ZeroDivisionError:
            print('\tGot ZeroDivisionError as expected from list(pool.imap())')
        else:
            raise AssertionError('expected ZeroDivisionError')
        it = pool.imap(f, list(range(10)))
        for i in range(10):
            try:
                x = next(it)
            except ZeroDivisionError:
                if i == 5:
                    pass
            except StopIteration:
                break
            else:
                if i == 5:
                    raise AssertionError('expected ZeroDivisionError')
        assert i == 9
        print('\tGot ZeroDivisionError as expected from IMapIterator.next()')
        print()
        #
        # Testing timeouts
        #
        print('Testing ApplyResult.get() with timeout:', end=' ')
        res = pool.apply_async(calculate, TASKS[0])
        while 1:
            sys.stdout.flush()
            try:
                sys.stdout.write('\n\t%s' % res.get(0.02))
                break
            except multiprocessing.TimeoutError:
                sys.stdout.write('.')
        print()
        print()
        print('Testing IMapIterator.next() with timeout:', end=' ')
        it = pool.imap(calculatestar, TASKS)
        while 1:
            sys.stdout.flush()
            try:
                sys.stdout.write('\n\t%s' % it.next(0.02))
            except StopIteration:
                break
            except multiprocessing.TimeoutError:
                sys.stdout.write('.')
        print()
        print()
if __name__ == '__main__':
    multiprocessing.freeze_support()
    test()

一个演示如何使用队列来向一组工作进程提供任务并收集结果的例子:

import time
import random
from multiprocessing import Process, Queue, current_process, freeze_support
#
# Function run by worker processes
#
def worker(input, output):
    for func, args in iter(input.get, 'STOP'):
        result = calculate(func, args)
        output.put(result)
#
# Function used to calculate result
#
def calculate(func, args):
    result = func(*args)
    return '%s says that %s%s = %s' % \
        (current_process().name, func.__name__, args, result)
#
# Functions referenced by tasks
#
def mul(a, b):
    time.sleep(0.5*random.random())
    return a * b
def plus(a, b):
    time.sleep(0.5*random.random())
    return a + b
#
#
#
def test():
    NUMBER_OF_PROCESSES = 4
    TASKS1 = [(mul, (i, 7)) for i in range(20)]
    TASKS2 = [(plus, (i, 8)) for i in range(10)]
    # Create queues
    task_queue = Queue()
    done_queue = Queue()
    # Submit tasks
    for task in TASKS1:
        task_queue.put(task)
    # Start worker processes
    for i in range(NUMBER_OF_PROCESSES):
        Process(target=worker, args=(task_queue, done_queue)).start()
    # Get and print results
    print('Unordered results:')
    for i in range(len(TASKS1)):
        print('\t', done_queue.get())
    # Add more tasks using `put()`
    for task in TASKS2:
        task_queue.put(task)
    # Get and print some more results
    for i in range(len(TASKS2)):
        print('\t', done_queue.get())
    # Tell child processes to stop
    for i in range(NUMBER_OF_PROCESSES):
        task_queue.put('STOP')
if __name__ == '__main__':
    freeze_support()
    test()

multiprocessing.shared_memory —- 可从进程直接访问的共享内存

源代码: Lib/multiprocessing/shared_memory.py

3.8 新版功能.


该模块提供了一个 SharedMemory 类,用于分配和管理多核或对称多处理器(SMP)机器上进程间的共享内存。为了协助管理不同进程间的共享内存生命周期,multiprocessing.managers 模块也提供了一个 BaseManager 的子类: SharedMemoryManager

本模块中,共享内存是指 “System V 类型” 的共享内存块(虽然可能和它实现方式不完全一致)而不是 “分布式共享内存”。这种类型的的共享内存允许不同进程读写一片公共(或者共享)的易失性存储区域。一般来说,进程被限制只能访问属于自己进程空间的内存,但是共享内存允许跨进程共享数据,从而避免通过进程间发送消息的形式传递数据。相比通过磁盘、套接字或者其他要求序列化、反序列化和复制数据的共享形式,直接通过内存共享数据拥有更出色性能。

class multiprocessing.shared_memory.SharedMemory(name=None, create=False, size=0)

创建一个新的共享内存块或者连接到一片已经存在的共享内存块。每个共享内存块都被指定了一个全局唯一的名称。通过这种方式,进程可以使用一个特定的名字创建共享内存区块,然后其他进程使用同样的名字连接到这个共享内存块。

作为一种跨进程共享数据的方式,共享内存块的寿命可能超过创建它的原始进程。一个共享内存块可能同时被多个进程使用,当一个进程不再需要访问这个共享内存块的时候,应该调用 close() 方法。当一个共享内存块不被任何进程使用的时候,应该调用 unlink() 方法以执行必要的清理。

name 是共享内存的唯一名称,字符串类型。如果创建一个新共享内存块的时候,名称指定为 None (默认值),将会随机产生一个新名称。

create 指定创建一个新的共享内存块 (True) 还是连接到已存在的共享内存块 (False) 。

如果是新创建共享内存块则 size 用于指定块的大小为多少字节。由于某些平台是以内存页大小为最小单位来分配内存的,最终得到的内存块大小可能大于或等于要求的大小。如果是连接到已经存在的共享内存块, size 参数会被忽略。

  • close()

    关闭实例对于共享内存的访问连接。所有实例确认自己不再需要使用共享内存的时候都应该调用 close() ,以保证必要的资源清理。调用 close() 并不会销毁共享内存区域。

  • unlink()

    请求销毁底层的共享内存块。为了执行必要的资源清理, 在所有使用这个共享内存块的进程中, unlink() 应该调用一次(且只能调用一次) 。发出此销毁请求后,共享内存块可能会、也可能不会立即销毁,且此行为在不同操作系统之间可能不同。调用 unlink() 后再尝试方位其中的数据可能导致内存错误。注意: 最后一个关闭共享内存访问权限的进程可以以任意顺序调用 unlink()close()

  • buf

    共享内存块内容的 memoryview 。

  • name

    共享内存块的唯一标识,只读属性。

  • size

    共享内存块的字节大小,只读属性。

以下示例展示了 SharedMemory 底层的用法:

>>> from multiprocessing import shared_memory
>>> shm_a = shared_memory.SharedMemory(create=True, size=10)
>>> type(shm_a.buf)
<class 'memoryview'>
>>> buffer = shm_a.buf
>>> len(buffer)
10
>>> buffer[:4] = bytearray([22, 33, 44, 55])  # Modify multiple at once
>>> buffer[4] = 100                           # Modify single byte at a time
>>> # Attach to an existing shared memory block
>>> shm_b = shared_memory.SharedMemory(shm_a.name)
>>> import array
>>> array.array('b', shm_b.buf[:5])  # Copy the data into a new array.array
array('b', [22, 33, 44, 55, 100])
>>> shm_b.buf[:5] = b'howdy'  # Modify via shm_b using bytes
>>> bytes(shm_a.buf[:5])      # Access via shm_a
b'howdy'
>>> shm_b.close()   # Close each SharedMemory instance
>>> shm_a.close()
>>> shm_a.unlink()  # Call unlink only once to release the shared memory

以下示例展示了一个现实中的例子,使用 SharedMemory 类和 NumPy arrays 结合, 从两个 Python shell 中访问同一个 numpy.ndarray :

>>> # In the first Python interactive shell
>>> import numpy as np
>>> a = np.array([1, 1, 2, 3, 5, 8])  # Start with an existing NumPy array
>>> from multiprocessing import shared_memory
>>> shm = shared_memory.SharedMemory(create=True, size=a.nbytes)
>>> # Now create a NumPy array backed by shared memory
>>> b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf)
>>> b[:] = a[:]  # Copy the original data into shared memory
>>> b
array([1, 1, 2, 3, 5, 8])
>>> type(b)
<class 'numpy.ndarray'>
>>> type(a)
<class 'numpy.ndarray'>
>>> shm.name  # We did not specify a name so one was chosen for us
'psm_21467_46075'
>>> # In either the same shell or a new Python shell on the same machine
>>> import numpy as np
>>> from multiprocessing import shared_memory
>>> # Attach to the existing shared memory block
>>> existing_shm = shared_memory.SharedMemory(name='psm_21467_46075')
>>> # Note that a.shape is (6,) and a.dtype is np.int64 in this example
>>> c = np.ndarray((6,), dtype=np.int64, buffer=existing_shm.buf)
>>> c
array([1, 1, 2, 3, 5, 8])
>>> c[-1] = 888
>>> c
array([  1,   1,   2,   3,   5, 888])
>>> # Back in the first Python interactive shell, b reflects this change
>>> b
array([  1,   1,   2,   3,   5, 888])
>>> # Clean up from within the second Python shell
>>> del c  # Unnecessary; merely emphasizing the array is no longer used
>>> existing_shm.close()
>>> # Clean up from within the first Python shell
>>> del b  # Unnecessary; merely emphasizing the array is no longer used
>>> shm.close()
>>> shm.unlink()  # Free and release the shared memory block at the very end

class multiprocessing.managers.SharedMemoryManager([address[, authkey]])

BaseManager 的子类,可用于管理跨进程的共享内存块。

调用 SharedMemoryManager 实例上的 start() 方法会启动一个新进程。这个新进程的唯一目的就是管理所有由它创建的共享内存块的生命周期。想要释放此进程管理的所有共享内存块,可以调用实例的 shutdown() 方法。这会触发执行它管理的所有 SharedMemory 对象的 SharedMemory.unlink() 方法,然后停止这个进程。通过 SharedMemoryManager 创建 SharedMemory 实例,我们可以避免手动跟踪和释放共享内存资源。

这个类提供了创建和返回 SharedMemory 实例的方法,以及以共享内存为基础创建一个列表类对象 (ShareableList) 的方法。

有关继承的可选输入参数 addressauthkey 以及他们如何用于从进程连接已经存在的 SharedMemoryManager 服务,参见 multiprocessing.managers.BaseManager

  • SharedMemory(size)

    使用 size 参数,创建一个新的指定字节大小的 SharedMemory 对象并返回。

  • ShareableList(sequence)

    创建并返回一个新的 ShareableList 对象,通过输入参数 sequence 初始化。

下面的案例展示了 SharedMemoryManager 的基本机制:

>>> from multiprocessing.managers import SharedMemoryManager
>>> smm = SharedMemoryManager()
>>> smm.start()  # Start the process that manages the shared memory blocks
>>> sl = smm.ShareableList(range(4))
>>> sl
ShareableList([0, 1, 2, 3], name='psm_6572_7512')
>>> raw_shm = smm.SharedMemory(size=128)
>>> another_sl = smm.ShareableList('alpha')
>>> another_sl
ShareableList(['a', 'l', 'p', 'h', 'a'], name='psm_6572_12221')
>>> smm.shutdown()  # Calls unlink() on sl, raw_shm, and another_sl

以下案例展示了 SharedMemoryManager 对象的一种可能更方便的使用方式,通过 with 语句来保证所有共享内存块在使用完后被释放。

>>> with SharedMemoryManager() as smm:
...     sl = smm.ShareableList(range(2000))
...     # Divide the work among two processes, storing partial results in sl
...     p1 = Process(target=do_work, args=(sl, 0, 1000))
...     p2 = Process(target=do_work, args=(sl, 1000, 2000))
...     p1.start()
...     p2.start()  # A multiprocessing.Pool might be more efficient
...     p1.join()
...     p2.join()   # Wait for all work to complete in both processes
...     total_result = sum(sl)  # Consolidate the partial results now in sl

with 语句中使用 SharedMemoryManager 对象的时候,使用这个管理器创建的共享内存块会在 with 语句代码块结束后被释放。

class multiprocessing.shared_memory.ShareableList(sequence=None, **, name=None*)

提供一个可修改的类 list 对象,其中所有值都存放在共享内存块中。这限制了可被存储在其中的值只能是 int, float, bool, str (每条数据小于10M), bytes (每条数据小于10M)以及 None 这些内置类型。它另一个显著区别于内置 list 类型的地方在于它的长度无法修改(比如,没有 append, insert 等操作)且不支持通过切片操作动态创建新的 ShareableList 实例。

sequence 会被用来为一个新的 ShareableList 填充值。 设为 None 则会基于唯一的共享内存名称关联到已经存在的 ShareableList

name 是所请求的共享内存的唯一名称,与 SharedMemory 的定义中所描述的一致。 当关联到现有的 ShareableList 时,则指明其共享内存块的唯一名称并将 sequence 设为 None

  • count(value)

    返回 value 出现的次数。

  • index(value)

    返回 value 首次出现的位置,如果 value 不存在, 则抛出 ValueError 异常。

  • format

    包含由所有当前存储值所使用的 struct 打包格式的只读属性。

  • shm

    存储了值的 SharedMemory 实例。

下面的例子演示了 ShareableList 实例的基本用法:

>>> from multiprocessing import shared_memory
>>> a = shared_memory.ShareableList(['howdy', b'HoWdY', -273.154, 100, None, True, 42])
>>> [ type(entry) for entry in a ]
[<class 'str'>, <class 'bytes'>, <class 'float'>, <class 'int'>, <class 'NoneType'>, <class 'bool'>, <class 'int'>]
>>> a[2]
-273.154
>>> a[2] = -78.5
>>> a[2]
-78.5
>>> a[2] = 'dry ice'  # Changing data types is supported as well
>>> a[2]
'dry ice'
>>> a[2] = 'larger than previously allocated storage space'
Traceback (most recent call last):
  ...
ValueError: exceeds available storage for existing str
>>> a[2]
'dry ice'
>>> len(a)
7
>>> a.index(42)
6
>>> a.count(b'howdy')
0
>>> a.count(b'HoWdY')
1
>>> a.shm.close()
>>> a.shm.unlink()
>>> del a  # Use of a ShareableList after call to unlink() is unsupported

下面的例子演示了一个、两个或多个进程如何通过提供下层的共享内存块名称来访问同一个 ShareableList:

>>> b = shared_memory.ShareableList(range(5))         # In a first process
>>> c = shared_memory.ShareableList(name=b.shm.name)  # In a second process
>>> c
ShareableList([0, 1, 2, 3, 4], name='...')
>>> c[-1] = -999
>>> b[-1]
-999
>>> b.shm.close()
>>> c.shm.close()
>>> c.shm.unlink()

The following examples demonstrates that ShareableList (and underlying SharedMemory) objects can be pickled and unpickled if needed. Note, that it will still be the same shared object. This happens, because the deserialized object has the same unique name and is just attached to an existing object with the same name (if the object is still alive):

>>> import pickle
>>> from multiprocessing import shared_memory
>>> sl = shared_memory.ShareableList(range(10))
>>> list(sl)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> deserialized_sl = pickle.loads(pickle.dumps(sl))
>>> list(deserialized_sl)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> sl[0] = -1
>>> deserialized_sl[1] = -2
>>> list(sl)
[-1, -2, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(deserialized_sl)
[-1, -2, 2, 3, 4, 5, 6, 7, 8, 9]

>>> sl.shm.close()
>>> sl.shm.unlink()

concurrent

目前,此包中只有一个模块:

  • concurrent.futures —— 启动并行任务

concurrent.futures —- 启动并行任务

3.2 新版功能.

源码: Lib/concurrent/futures/thread.pyLib/concurrent/futures/process.py


concurrent.futures 模块提供异步执行可调用对象高层接口。

异步执行可以由 ThreadPoolExecutor 使用线程或由 ProcessPoolExecutor 使用单独的进程来实现。 两者都是实现抽像类 Executor 定义的接口。

Executor 对象

class concurrent.futures.Executor

抽象类提供异步执行调用方法。要通过它的子类调用,而不是直接调用。

  • submit(fn, /, \args, *kwargs)

    调度可调用对象 fn,以 fn(*args **kwargs) 方式执行并返回 Future 对象代表可调用对象的执行。:

    with ThreadPoolExecutor(max_workers=1) as executor:
        future = executor.submit(pow, 323, 1235)
        print(future.result())
  • map(func, \iterables, timeout=None, chunksize=1*)

    类似于 map(func, *iterables) 函数,除了以下两点:

    • iterables 是立即执行而不是延迟执行的;

    • func 是异步执行的,对 func 的多个调用可以并发执行。

      如果从原始调用到 Executor.map() 经过 timeout 秒后, __next__() 已被调用且返回的结果还不可用,那么已返回的迭代器将触发 concurrent.futures.TimeoutErrortimeout 可以是整数或浮点数。如果 timeout 没有指定或为 None ,则没有超时限制。
      如果 func 调用引发一个异常,当从迭代器中取回它的值时这个异常将被引发。
      使用 ProcessPoolExecutor 时,这个方法会将 iterables 分割任务块并作为独立的任务并提交到执行池中。这些块的大概数量可以由 chunksize 指定正整数设置。 对很长的迭代器来说,使用大的 chunksize 值比默认值 1 能显著地提高性能。 chunksizeThreadPoolExecutor 没有效果。
      在 3.5 版更改: 加入 chunksize 参数。

  • shutdown(wait=True, **, cancel_futures=False*)

    当待执行的 future 对象完成执行后向执行者发送信号,它就会释放正在使用的任何资源。 在关闭后调用 Executor.submit()Executor.map() 将会引发 RuntimeError

    如果 waitTrue 则此方法只有在所有待执行的 future 对象完成执行且释放已分配的资源后才会返回。 如果 waitFalse,方法立即返回,所有待执行的 future 对象完成执行后会释放已分配的资源。 不管 wait 的值是什么,整个 Python 程序将等到所有待执行的 future 对象完成执行后才退出。

    如果 cancel_futuresTrue,此方法将取消所有执行器还未开始运行的挂起的 Future。 任何已完成或正在运行的 Future 将不会被取消,无论 cancel_futures 的值是什么?

    如果 cancel_futureswait 均为 True,则执行器已开始运行的所有 Future 将在此方法返回之前完成。 其余的 Future 会被取消。

    如果使用 with 语句,你就可以避免显式调用这个方法,它将会停止 Executor (就好像 Executor.shutdown() 调用时 wait 设为 True 一样等待):

    import shutil
    with ThreadPoolExecutor(max_workers=4) as e:
        e.submit(shutil.copy, 'src1.txt', 'dest1.txt')
        e.submit(shutil.copy, 'src2.txt', 'dest2.txt')
        e.submit(shutil.copy, 'src3.txt', 'dest3.txt')
        e.submit(shutil.copy, 'src4.txt', 'dest4.txt')

    在 3.9 版更改: 增加了 cancel_futures

ThreadPoolExecutor

ThreadPoolExecutorExecutor 的子类,它使用线程池来异步执行调用。

当回调已关联了一个 Future 然后再等待另一个 Future 的结果时就会发产死锁情况。例如:

import time
def wait_on_b():
    time.sleep(5)
    print(b.result())  # b will never complete because it is waiting on a.
    return 5
def wait_on_a():
    time.sleep(5)
    print(a.result())  # a will never complete because it is waiting on b.
    return 6
executor = ThreadPoolExecutor(max_workers=2)
a = executor.submit(wait_on_b)
b = executor.submit(wait_on_a)

与:

def wait_on_future():
    f = executor.submit(pow, 5, 2)
    # This will never complete because there is only one worker thread and
    # it is executing this function.
    print(f.result())
executor = ThreadPoolExecutor(max_workers=1)
executor.submit(wait_on_future)

class concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix=’’, initializer=None, initargs=())

Executor 子类使用最多 max_workers 个线程的线程池来异步执行调用。

initializer 是在每个工作者线程开始处调用的一个可选可调用对象。 initargs 是传递给初始化器的元组参数。任何向池提交更多工作的尝试, initializer 都将引发一个异常,当前所有等待的工作都会引发一个 BrokenThreadPool

在 3.5 版更改: 如果 max_workersNone 或没有指定,将默认为机器处理器的个数,假如 ThreadPoolExecutor 则重于I/O操作而不是CPU运算,那么可以乘以 5 ,同时工作线程的数量可以比 ProcessPoolExecutor 的数量高。

3.6 新版功能: 添加 thread_name_prefix 参数允许用户控制由线程池创建的 threading.Thread 工作线程名称以方便调试。

在 3.7 版更改: 加入 initializerinitargs 参数。

在 3.8 版更改: max_workers 的默认值已改为 min(32, os.cpu_count() + 4)。 这个默认值会保留至少 5 个工作线程用于 I/O 密集型任务。 对于那些释放了 GIL 的 CPU 密集型任务,它最多会使用 32 个 CPU 核心。这样能够避免在多核机器上不知不觉地使用大量资源。

现在 ThreadPoolExecutor 在启动 max_workers 个工作线程之前也会重用空闲的工作线程。

ThreadPoolExecutor 例子

import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']
# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()
# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor

ProcessPoolExecutor 类是 Executor 的子类,它使用进程池来异步地执行调用。 ProcessPoolExecutor 会使用 multiprocessing 模块,这允许它绕过 全局解释器锁 但也意味着只可以处理和返回可封存的对象。

__main__ 模块必须可以被工作者子进程导入。这意味着 ProcessPoolExecutor 不可以工作在交互式解释器中。

从可调用对象中调用 ExecutorFuture 的方法提交给 ProcessPoolExecutor 会导致死锁。

class concurrent.futures.ProcessPoolExecutor(max_workers=None, mp_context=None, initializer=None, initargs=())

异步地执行调用的 Executor 子类使用最多具有 max_workers 个进程的进程池。 如果 max_workersNone 或未给出,它将默认为机器的处理器个数。 如果 max_workers 小于等于 0,则将引发 ValueError。 在 Windows 上,max_workers 必须小于等于 61,否则将引发 ValueError。 如果 max_workersNone,则所选择的默认值最多为 61,即使存在更多的处理器。 mp_context 可以是一个多进程上下文或是 None。 它将被用来启动工作进程。 如果 mp_contextNone 或未给出,则将使用默认的多进程上下文。

initializer 是一个可选的可调用对象,它会在每个工作进程启动时被调用;initargs 是传给 initializer 的参数元组。 如果 initializer 引发了异常,则所有当前在等待的任务以及任何向进程池提交更多任务的尝试都将引发 BrokenProcessPool

在 3.3 版更改: 如果其中一个工作进程被突然终止,BrokenProcessPool 就会马上触发。 可预计的行为没有定义,但执行器上的操作或它的 future 对象会被冻结或死锁。

在 3.7 版更改: 添加 mp_context 参数允许用户控制由进程池创建给工作者进程的开始方法 。

加入 initializerinitargs 参数。

ProcessPoolExecutor 例子

import concurrent.futures
import math
PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]
def is_prime(n):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True
def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
    main()

Future 对象

Future 类将可调用对象封装为异步执行。Future 实例由 Executor.submit() 创建。

class concurrent.futures.Future

将可调用对象封装为异步执行。Future 实例由 Executor.submit() 创建,除非测试,不应直接创建。

  • cancel()

    尝试取消调用。 如果调用正在执行或已结束运行不能被取消则该方法将返回 False,否则调用会被取消并且该方法将返回 True

  • cancelled()

    如果调用成功取消返回 True

  • running()

    如果调用正在执行而且不能被取消那么返回 True

  • done()

    如果调用已被取消或正常结束那么返回 True

  • result(timeout=None)

    返回调用返回的值。如果调用还没完成那么这个方法将等待 timeout 秒。如果在 timeout 秒内没有执行完成,concurrent.futures.TimeoutError 将会被触发。timeout 可以是整数或浮点数。如果 timeout 没有指定或为 None,那么等待时间就没有限制。

    如果 futrue 在完成前被取消则 CancelledError 将被触发。

    如果调用引发了一个异常,这个方法也会引发同样的异常。

  • exception(timeout=None)

    返回由调用引发的异常。如果调用还没完成那么这个方法将等待 timeout 秒。如果在 timeout 秒内没有执行完成,concurrent.futures.TimeoutError 将会被触发。timeout 可以是整数或浮点数。如果 timeout 没有指定或为 None,那么等待时间就没有限制。

    如果 futrue 在完成前被取消则 CancelledError 将被触发。

    如果调用正常完成那么返回 None

  • add_done_callback(fn)

    附加可调用 fn 到 future 对象。当 future 对象被取消或完成运行时,将会调用 fn,而这个 future 对象将作为它唯一的参数。

    加入的可调用对象总被属于添加它们的进程中的线程按加入的顺序调用。如果可调用对象引发一个 Exception 子类,它会被记录下来并被忽略掉。如果可调用对象引发一个 BaseException 子类,这个行为没有定义。

    如果 future 对象已经完成或已取消,fn 会被立即调用。

下面这些 Future 方法用于单元测试和 Executor 实现。

  • set_running_or_notify_cancel()

    这个方法只可以在执行关联 Future 工作之前由 Executor 实现调用或由单测试调用。

    如果这个方法返回 False 那么 Future 已被取消,即 Future.cancel() 已被调用并返回 True 。等待 Future 完成 (即通过 as_completed()wait()) 的线程将被唤醒。

    如果这个方法返回 True 那么 Future 不会被取消并已将它变为正在运行状态,也就是说调用 Future.running() 时将返回 True。

    这个方法只可以被调用一次并且不能在调用 Future.set_result()Future.set_exception() 之后再调用。

  • set_result(result)

    设置将 Future 关联工作的结果给 result

    这个方法只可以由 Executor 实现和单元测试使用。

    在 3.8 版更改: 如果 Future 已经完成则此方法会引发 concurrent.futures.InvalidStateError

  • set_exception(exception)

    设置 Future 关联工作的结果给 Exception exception

    这个方法只可以由 Executor 实现和单元测试使用。

    在 3.8 版更改: 如果 Future 已经完成则此方法会引发 concurrent.futures.InvalidStateError

模块函数

concurrent.futures.wait(fs, timeout=None, return_when=ALL_COMPLETED)

等待 fs 指定的 Future 实例(可能由不同的 Executor 实例创建)完成。 返回一个由集合构成的具名 2 元组。 第一个集合名称为 done,包含在等待完成之前已完成的期程(包括正常结束或被取消的 future 对象)。 第二个集合名称为 not_done,包含未完成的 future 对象(包括挂起的或正在运行的 future 对象)。

timeout 可以用来控制返回前最大的等待秒数。 timeout 可以为 int 或 float 类型。 如果 timeout 未指定或为 None ,则不限制等待时间。

return_when 指定此函数应在何时返回。它必须为以下常数之一:

常量 描述
FIRST_COMPLETED 函数将在任意可等待对象结束或取消时返回。
FIRST_EXCEPTION 函数将在任意可等待对象因引发异常而结束时返回。当没有引发任何异常时它就相当于 ALL_COMPLETED
ALL_COMPLETED 函数将在所有可等待对象结束或取消时返回。

concurrent.futures.as_completed(fs, timeout=None)

返回一个包含 fs 所指定的 Future 实例(可能由不同的 Executor 实例创建)的迭代器,这些实例会在完成时生成 future 对象(包括正常结束或被取消的 future 对象)。 任何由 fs 所指定的重复 future 对象将只被返回一次。 任何在 as_completed() 被调用之前完成的 future 对象将优先被生成。 如果 __next__() 被调用并且在对 as_completed() 的原始调用 timeout 秒之后结果仍不可用,则返回的迭代器将引发 concurrent.futures.TimeoutErrortimeout 可以为整数或浮点数。 如果 timeout 未指定或为 None,则不限制等待时间。

参见

PEP 3148 — future 对象 - 异步执行指令。

该提案描述了Python标准库中包含的这个特性。

Exception 类

exception concurrent.futures.CancelledError

future 对象被取消时会触发。

exception concurrent.futures.TimeoutError

future 对象执行超出给定的超时数值时引发。

exception concurrent.futures.BrokenExecutor

当执行器被某些原因中断而且不能用来提交或执行新任务时就会被引发派生于 RuntimeError 的异常类。

3.7 新版功能.

exception concurrent.futures.InvalidStateError

当某个操作在一个当前状态所不允许的 future 上执行时将被引发。

3.8 新版功能.

exception concurrent.futures.thread.BrokenThreadPool

ThreadPoolExecutor 中的其中一个工作者初始化失败时会引发派生于 BrokenExecutor 的异常类。

3.7 新版功能.

exception concurrent.futures.process.BrokenProcessPool

ThreadPoolExecutor 中的其中一个工作者不完整终止时(比如,被外部杀死)会引发派生于 BrokenExecutor ( 原名 RuntimeError ) 的异常类。

3.3 新版功能.

sched —- 事件调度器

源码: Lib/sched.py


sched 模块定义了一个实现通用事件调度程序的类:

class sched.scheduler(timefunc=time.monotonic, delayfunc=time.sleep)

scheduler 类定义了一个调度事件的通用接口。 它需要两个函数来实际处理“外部世界” —— timefunc 应当不带参数地调用,并返回一个数字(“时间”,可以为任意单位)。 delayfunc 函数应当带一个参数调用,与 timefunc 的输出相兼容,并且应当延迟其所指定的时间单位。 每个事件运行后还将调用 delayfunc 并传入参数 0 以允许其他线程有机会在多线程应用中运行。

在 3.3 版更改: timefuncdelayfunc 参数是可选的。

在 3.3 版更改: scheduler 类可以安全的在多线程环境中使用。

示例:

>>> import sched, time
>>> s = sched.scheduler(time.time, time.sleep)
>>> def print_time(a='default'):
...     print("From print_time", time.time(), a)
...
>>> def print_some_times():
...     print(time.time())
...     s.enter(10, 1, print_time)
...     s.enter(5, 2, print_time, argument=('positional',))
...     s.enter(5, 1, print_time, kwargs={'a': 'keyword'})
...     s.run()
...     print(time.time())
...
>>> print_some_times()
930343690.257
From print_time 930343695.274 positional
From print_time 930343695.275 keyword
From print_time 930343700.273 default
930343700.276

调度器对象

scheduler 实例拥有以下方法和属性:

scheduler.enterabs(time, priority, action, argument=(), kwargs={})

安排一个新事件。 time 参数应该有一个数字类型兼容的返回值,与传递给构造函数的 timefunc 函数的返回值兼容。 计划在相同 time 的事件将按其 priority 的顺序执行。 数字越小表示优先级越高。

执行事件意为执行 action(*argument, **kwargs)argument 是包含有 action 的位置参数的序列。 kwargs 是包含 action 的关键字参数的字典。

返回值是一个事件,可用于以后取消事件。

在 3.3 版更改: argument 参数是可选的。

在 3.3 版更改: 添加了 kwargs 形参。

scheduler.enter(delay, priority, action, argument=(), kwargs={})

安排延后 delay 时间单位的事件。 除了相对时间,其他参数、效果和返回值与 enterabs() 的相同。

在 3.3 版更改: argument 参数是可选的。

在 3.3 版更改: 添加了 kwargs 形参。

scheduler.cancel(event)

从队列中删除事件。 如果 event 不是当前队列中的事件,则此方法将引发 ValueError

scheduler.empty()

如果事件队列为空则返回 True

scheduler.run(blocking=True)

运行所有预定事件。 此方法将等待(使用传递给构造函数的 delayfunc() 函数)进行下一个事件,然后执行它,依此类推,直到没有更多的计划事件。

如果 blocking 为false,则执行由于最快到期(如果有)的预定事件,然后在调度程序中返回下一个预定调用的截止时间(如果有)。

actiondelayfunc 都可以引发异常。 在任何一种情况下,调度程序都将保持一致状态并传播异常。 如果 action 引发异常,则在将来调用 run() 时不会尝试该事件。

如果一系列事件的运行时间比下一个事件之前的可用时间长,那么调度程序将完全落后。 不会发生任何事件;调用代码负责取消不再相关的事件。

在 3.3 版更改: 添加了 blocking 形参。

scheduler.queue

只读属性按照将要运行的顺序返回即将发生的事件列表。 每个事件都显示为 named tuple ,包含以下字段:time、priority、action、argument、kwargs。

queue —- 一个同步的队列类

源代码: Lib/queue.py


queue 模块实现了多生产者、多消费者队列。这特别适用于消息必须安全地在多线程间交换的线程编程。模块中的 Queue 类实现了所有所需的锁定语义。

模块实现了三种类型的队列,它们的区别仅仅是条目取回的顺序。在 FIFO 队列中,先添加的任务先取回。在 LIFO 队列中,最近被添加的条目先取回(操作类似一个堆栈)。优先级队列中,条目将保持排序( 使用 heapq 模块 ) 并且最小值的条目第一个返回。

在内部,这三个类型的队列使用锁来临时阻塞竞争线程;然而,它们并未被设计用于线程的重入性处理。

此外,模块实现了一个 “简单的” FIFO 队列类型, SimpleQueue ,这个特殊实现为小功能在交换中提供额外的保障。

queue 模块定义了下列类和异常:

class queue.Queue(maxsize=0)

Constructor for a FIFO queue. maxsize is an integer that sets the upperbound limit on the number of items that can be placed in the queue. Insertion will block once this size has been reached, until queue items are consumed. If maxsize is less than or equal to zero, the queue size is infinite.

class queue.LifoQueue(maxsize=0)

LIFO 队列构造函数。 maxsize 是个整数,用于设置可以放入队列中的项目数的上限。当达到这个大小的时候,插入操作将阻塞至队列中的项目被消费掉。如果 maxsize 小于等于零,队列尺寸为无限大。

class queue.PriorityQueue(maxsize=0)

优先级队列构造函数。 maxsize 是个整数,用于设置可以放入队列中的项目数的上限。当达到这个大小的时候,插入操作将阻塞至队列中的项目被消费掉。如果 maxsize 小于等于零,队列尺寸为无限大。

最小值先被取出( 最小值条目是由 sorted(list(entries))[0] 返回的条目)。条目的典型模式是一个以下形式的元组: (priority_number, data)

如果 data 元素没有可比性,数据将被包装在一个类中,忽略数据值,仅仅比较优先级数字 :

from dataclasses import dataclass, field
from typing import Any
@dataclass(order=True)
class PrioritizedItem:
    priority: int
    item: Any=field(compare=False)

class queue.SimpleQueue

无界的 FIFO 队列构造函数。简单的队列,缺少任务跟踪等高级功能。

3.7 新版功能.

exception queue.Empty

对空的 Queue 对象,调用非阻塞的 get() (or get_nowait()) 时,引发的异常。

exception queue.Full

对满的 Queue 对象,调用非阻塞的 put() (or put_nowait()) 时,引发的异常。

Queue对象

队列对象 (Queue, LifoQueue, 或者 PriorityQueue) 提供下列描述的公共方法。

Queue.qsize()

返回队列的大致大小。注意,qsize() > 0 不保证后续的 get() 不被阻塞,qsize() < maxsize 也不保证 put() 不被阻塞。

Queue.empty()

如果队列为空,返回 True ,否则返回 False 。如果 empty() 返回 True ,不保证后续调用的 put() 不被阻塞。类似的,如果 empty() 返回 False ,也不保证后续调用的 get() 不被阻塞。

Queue.full()

如果队列是满的返回 True ,否则返回 False 。如果 full() 返回 True 不保证后续调用的 get() 不被阻塞。类似的,如果 full() 返回 False 也不保证后续调用的 put() 不被阻塞。

Queue.put(item, block=True, timeout=None)

item 放入队列。如果可选参数 block 是 true 并且 timeoutNone (默认),则在必要时阻塞至有空闲插槽可用。如果 timeout 是个正数,将最多阻塞 timeout 秒,如果在这段时间没有可用的空闲插槽,将引发 Full 异常。反之 (block 是 false),如果空闲插槽立即可用,则把 item 放入队列,否则引发 Full 异常 ( 在这种情况下,timeout 将被忽略)。

Queue.put_nowait(item)

相当于 put(item, False)

Queue.get(block=True, timeout=None)

从队列中移除并返回一个项目。如果可选参数 block 是 true 并且 timeoutNone (默认值),则在必要时阻塞至项目可得到。如果 timeout 是个正数,将最多阻塞 timeout 秒,如果在这段时间内项目不能得到,将引发 Empty 异常。反之 (block 是 false) , 如果一个项目立即可得到,则返回一个项目,否则引发 Empty 异常 (这种情况下,timeout 将被忽略)。

POSIX系统3.0之前,以及所有版本的Windows系统中,如果 block 是 true 并且 timeoutNone , 这个操作将进入基础锁的不间断等待。这意味着,没有异常能发生,尤其是 SIGINT 将不会触发 KeyboardInterrupt 异常。

Queue.get_nowait()

相当于 get(False)

提供了两个方法,用于支持跟踪 排队的任务 是否 被守护的消费者线程 完整的处理。

Queue.task_done()

表示前面排队的任务已经被完成。被队列的消费者线程使用。每个 get() 被用于获取一个任务, 后续调用 task_done() 告诉队列,该任务的处理已经完成。

如果 join() 当前正在阻塞,在所有条目都被处理后,将解除阻塞(意味着每个 put() 进队列的条目的 task_done() 都被收到)。

如果被调用的次数多于放入队列中的项目数量,将引发 ValueError 异常 。

Queue.join()

阻塞至队列中所有的元素都被接收和处理完毕。

当条目添加到队列的时候,未完成任务的计数就会增加。每当消费者线程调用 task_done() 表示这个条目已经被回收,该条目所有工作已经完成,未完成计数就会减少。当未完成计数降到零的时候, join() 阻塞被解除。

如何等待排队的任务被完成的示例:

import threading, queue
q = queue.Queue()
def worker():
    while True:
        item = q.get()
        print(f'Working on {item}')
        print(f'Finished {item}')
        q.task_done()
# turn-on the worker thread
threading.Thread(target=worker, daemon=True).start()
# send thirty task requests to the worker
for item in range(30):
    q.put(item)
print('All task requests sent\n', end='')
# block until all tasks are done
q.join()
print('All work completed')

SimpleQueue 对象

SimpleQueue 对象提供下列描述的公共方法。

SimpleQueue.qsize()

返回队列的大致大小。注意,qsize() > 0 不保证后续的 get() 不被阻塞。

SimpleQueue.empty()

如果队列为空,返回 True ,否则返回 False 。如果 empty() 返回 False ,不保证后续调用的 get() 不被阻塞。

SimpleQueue.put(item, block=True, timeout=None)

item 放入队列。此方法永不阻塞,始终成功(除了潜在的低级错误,例如内存分配失败)。可选参数 blocktimeout 仅仅是为了保持 Queue.put() 的兼容性而提供,其值被忽略。

CPython implementation detail: This method has a C implementation which is reentrant. That is, a put() or get() call can be interrupted by another put() call in the same thread without deadlocking or corrupting internal state inside the queue. This makes it appropriate for use in destructors such as __del__ methods or weakref callbacks.

SimpleQueue.put_nowait(item)

相当于 put(item) ,仅为保持 Queue.put_nowait() 兼容性而提供。

SimpleQueue.get(block=True, timeout=None)

从队列中移除并返回一个项目。如果可选参数 block 是 true 并且 timeoutNone (默认值),则在必要时阻塞至项目可得到。如果 timeout 是个正数,将最多阻塞 timeout 秒,如果在这段时间内项目不能得到,将引发 Empty 异常。反之 (block 是 false) , 如果一个项目立即可得到,则返回一个项目,否则引发 Empty 异常 (这种情况下,timeout 将被忽略)。

SimpleQueue.get_nowait()

相当于 get(False)

contextvars —- 上下文变量

本模块提供了相关API用于管理、存储和访问上下文相关的状态。 ContextVar 类用于声明 上下文变量 并与其一起使用。函数 copy_context() 和类 Context 用于管理当前上下文和异步框架中。

在多并发环境中,有状态上下文管理器应该使用上下文变量,而不是 threading.local() 来防止他们的状态意外泄露到其他代码。

更多信息参见 PEP 567

3.7 新版功能.

上下文变量

class contextvars.ContextVar(name[, **, default*])

此类用于声明一个新的上下文变量,如:

var: ContextVar[int] = ContextVar('var', default=42)

name 参数用于内省和调试,必需。

调用 ContextVar.get() 时,如果上下文中没有找到此变量的值,则返回可选的仅命名参数 default

重要: 上下文变量应该在顶级模块中创建,且永远不要在闭包中创建。 Context 对象拥有对上下文变量的强引用,这可以让上下文变量被垃圾收集器正确回收。

  • name

    上下文变量的名称,只读属性。

    3.7.1 新版功能.

  • get([default])

    返回当前上下文中此上下文变量的值。

    如果当前上下文中此变量没有值,则此方法会:

    • 如果提供了得 话,返回传入的 default 值;或者
    • 返回上下文变量本身的默认值, 如果创建此上下文变量时提供了默认值;或者
    • 抛出 LookupError 异常。
  • set(value)

    调用此方法设置上下文变量在当前上下文中的值。

    必选参数 value 是上下文变量的新值。

    返回一个 Token 对象,可通过 ContextVar.reset() 方法将上下文变量还原为之前某个状态。

  • reset(token)

    将上下文变量重置为调用 ContextVar.set() 之前、创建 token 时候的状态。

    例如:

    var = ContextVar('var')
    token = var.set('new value')
    # code that uses 'var'; var.get() returns 'new value'.
    var.reset(token)
    # After the reset call the var has no value again, so
    # var.get() would raise a LookupError.

class contextvars.Token

ContextVar.set() 方法返回 Token 对象。此对象可以传递给 ContextVar.reset() 方法用于将上下文变量还原为调用 set 前的状态。

  • Token.var

    只读属性。指向创建此 token 的 ContextVar 对象。

  • Token.old_value

    一个只读属性。 会被设为在创建此令牌的 ContextVar.set() 方法调用之前该变量所具有的值。 如果调用之前变量没有设置值,则它指向 Token.MISSING

  • Token.MISSING

    Token.old_value 会用到的一个标记对象。

手动上下文管理

contextvars.copy_context()

返回当前上下文中 Context 对象的拷贝。

以下代码片段会获取当前上下文的拷贝并打印设置到其中的所有变量及其值:

ctx: Context = copy_context()
print(list(ctx.items()))

此函数复杂度为 O(1) ,也就是说对于只包含几个上下文变量和很多上下文变量的情况,他们是一样快的。

class contextvars.Context

ContextVars 中所有值的映射。

Context() 创建一个不包含任何值的空上下文。如果要获取当前上下文的拷贝,使用 copy_context() 函数。

Context 实现了 collections.abc.Mapping 接口。

  • run(callable, \args, *kwargs)

    按照 run 方法中的参数在上下文对象中执行 callable(*args, **kwargs) 代码。返回执行结果,如果发生异常,则将异常透传出来。

    callable 中队上下文变量做出的任何修改会保留在上下文对象中:

    var = ContextVar('var')
    var.set('spam')
    def main():
        # 'var' was set to 'spam' before
        # calling 'copy_context()' and 'ctx.run(main)', so:
        # var.get() == ctx[var] == 'spam'
        var.set('ham')
        # Now, after setting 'var' to 'ham':
        # var.get() == ctx[var] == 'ham'
    ctx = copy_context()
    # Any changes that the 'main' function makes to 'var'
    # will be contained in 'ctx'.
    ctx.run(main)
    # The 'main()' function was run in the 'ctx' context,
    # so changes to 'var' are contained in it:
    # ctx[var] == 'ham'
    # However, outside of 'ctx', 'var' is still set to 'spam':
    # var.get() == 'spam'

    当在多个系统线程或者递归调用同一个上下文对象的此方法,抛出 RuntimeError 异常。

  • copy()

    返回此上下文对象的浅拷贝。

  • var in context

    如果context 中含有名称为 var 的变量,返回 True , 否则返回 False

  • context[var]

    返回名称为 varContextVar 变量。如果上下文对象中不包含这个变量,则抛出 KeyError 异常。

  • get(var[, default])

    如果 var 在上下文对象中具有值则返回 var 的值。 在其他情况下返回 default*。 如果未给出 *default 则返回 None

  • iter(context)

    返回一个存储在上下文对象中的变量的迭代器。

  • len(proxy)

    返回上下文对象中所设的变量的数量。

  • keys()

    返回上下文对象中的所有变量的列表。

  • values()

    返回上下文对象中所有变量值的列表。

  • items()

    返回包含上下文对象中所有变量及其值的 2 元组的列表。

asyncio 支持

上下文变量在 asyncio 中有原生的支持并且无需任何额外配置即可被使用。 例如,以下是一个简单的回显服务器,它使用上下文变量来让远程客户端的地址在处理该客户端的 Task 中可用:

import asyncio
import contextvars
client_addr_var = contextvars.ContextVar('client_addr')
def render_goodbye():
    # The address of the currently handled client can be accessed
    # without passing it explicitly to this function.
    client_addr = client_addr_var.get()
    return f'Good bye, client @ {client_addr}\n'.encode()
async def handle_request(reader, writer):
    addr = writer.transport.get_extra_info('socket').getpeername()
    client_addr_var.set(addr)
    # In any code that we call is now possible to get
    # client's address by calling 'client_addr_var.get()'.
    while True:
        line = await reader.readline()
        print(line)
        if not line.strip():
            break
        writer.write(line)
    writer.write(render_goodbye())
    writer.close()
async def main():
    srv = await asyncio.start_server(
        handle_request, '127.0.0.1', 8081)
    async with srv:
        await srv.serve_forever()
asyncio.run(main())
# To test it you can use telnet:
#     telnet 127.0.0.1 8081

_thread —- 底层多线程 API

该模块提供了操作多个线程(也被称为 轻量级进程任务*)的底层原语 —— 多个控制线程共享全局数据空间。为了处理同步问题,也提供了简单的锁机制(也称为 *互斥锁二进制信号)。threading 模块基于该模块提供了更易用的高级多线程 API。

在 3.7 版更改: 这个模块曾经为可选项,但现在总是可用。

这个模块定义了以下常量和函数:

exception _thread.error

发生线程相关错误时抛出。

在 3.3 版更改: 现在是内建异常 RuntimeError 的别名。

_thread.LockType

锁对象的类型。

_thread.start_new_thread(function, args[, kwargs])

开启一个新线程并返回其标识。 线程执行函数 function 并附带参数列表 args (必须是元组)。 可选的 kwargs 参数指定一个关键字参数字典。

当函数返回时,线程会静默地退出。

当函数因某个未处理异常而终结时,sys.unraisablehook() 会被调用以处理异常。 钩子参数的 object 属性为 function。 在默认情况下,会打印堆栈回溯然后该线程将退出(但其他线程会继续运行)。

当函数引发 SystemExit 异常时,它会被静默地忽略。

在 3.8 版更改: 现在会使用 sys.unraisablehook() 来处理未处理的异常。

_thread.interrupt_main(signum=signal.SIGINT, /)

模拟一个信号到达主线程的效果。 线程可使用此函数来打断主线程,虽然并不保证打断将立即发生。

如果给出 signum,则表示要模拟的信号的编号。 如果未给出 signum,则将模拟 signal.SIGINT

如果 Python 没有处理给定的信号 (它被设为 signal.SIG_DFLsignal.SIG_IGN),此函数将不做任何操作。

在 3.10 版更改: 添加了 signum 参数来定制信号的编号。

注解

这并不会发出对应的信号而是将一个调用排入关联处理句柄的计划任务(如果句柄存在的话)。 如果你想要真的发出信号,请使用 signal.raise_signal()

_thread.exit()

抛出 SystemExit 异常。如果没有捕获的话,这个异常会使线程退出。

_thread.allocate_lock()

返回一个新的锁对象。锁中的方法在后面描述。初始情况下锁处于解锁状态。

_thread.get_ident()

返回当前线程的 “线程标识符”。它是一个非零的整数。它的值没有直接含义,主要是用作 magic cookie,比如作为含有线程相关数据的字典的索引。线程标识符可能会在线程退出,新线程创建时被复用。

_thread.get_native_id()

返回内核分配给当前线程的原生集成线程 ID。 这是一个非负整数。 它的值可被用来在整个系统中唯一地标识这个特定线程(直到线程终结,在那之后该值可能会被 OS 回收再利用)。

可用性: Windows, FreeBSD, Linux, macOS, OpenBSD, NetBSD, AIX。

3.8 新版功能.

_thread.stack_size([size])

返回创建线程时使用的堆栈大小。可选参数 size 指定之后新建的线程的堆栈大小,而且一定要是0(根据平台或者默认配置)或者最小是32,768(32KiB)的一个正整数。如果 size 没有指定,默认是0。如果不支持改变线程堆栈大小,会抛出 RuntimeError 错误。如果指定的堆栈大小不合法,会抛出 ValueError 错误并且不会修改堆栈大小。32KiB是当前最小的能保证解释器有足够堆栈空间的堆栈大小。需要注意的是部分平台对于堆栈大小会有特定的限制,例如要求大于32KiB的堆栈大小或者需要根据系统内存页面的整数倍进行分配 - 应当查阅平台文档有关详细信息(4KiB页面比较普遍,在没有更具体信息的情况下,建议的方法是使用4096的倍数作为堆栈大小)。

适用于: Windows,具有 POSIX 线程的系统。

_thread.TIMEOUT_MAX

Lock.acquire() 方法中 timeout 参数允许的最大值。传入超过这个值的 timeout 会抛出 OverflowError 异常。

3.2 新版功能.

锁对象有以下方法:

lock.acquire(waitflag=1, timeout=- 1)

没有任何可选参数时,该方法无条件申请获得锁,有必要的话会等待其他线程释放锁(同时只有一个线程能获得锁 —— 这正是锁存在的原因)。

如果传入了整型参数 waitflag,具体的行为取决于传入的值:如果是 0 的话,只会在能够立刻获取到锁时才获取,不会等待,如果是非零的话,会像之前提到的一样,无条件获取锁。

如果传入正浮点数参数 timeout,相当于指定了返回之前等待得最大秒数。如果传入负的 *timeout,相当于无限期等待。如果 *waitflag 是 0 的话,不能指定 timeout

如果成功获取到所会返回 True,否则返回 False

在 3.2 版更改: 新的 timeout 形参。

在 3.2 版更改: 现在获取锁的操作可以被 POSIX 信号中断。

lock.release()

释放锁。锁必须已经被获取过,但不一定是同一个线程获取的。

lock.locked()

返回锁的状态:如果已被某个线程获取,返回 True,否则返回 False

除了这些方法之外,锁对象也可以通过 with 语句使用,例如:

import _thread
a_lock = _thread.allocate_lock()
with a_lock:
    print("a_lock is locked while this executes")

注意事项:

  • 线程与中断奇怪地交互:KeyboardInterrupt 异常可能会被任意一个线程捕获。(如果 signal 模块可用的话,中断总是会进入主线程。)
  • 调用 sys.exit() 或是抛出 SystemExit 异常等效于调用 _thread.exit()
  • 不可能中断锁的 acquire() 方法 —— KeyboardInterrupt 一场会在锁获取到之后发生。
  • 当主线程退出时,由系统决定其他线程是否存活。在大多数系统中,这些线程会直接被杀掉,不会执行 tryfinally 语句,也不会执行对象析构函数。
  • 当主线程退出时,不会进行正常的清理工作(除非使用了 tryfinally 语句),标准 I/O 文件也不会刷新。

subprocess —- 子进程管理

源代码:Lib/subprocess.py


subprocess 模块允许你生成新的进程,连接它们的输入、输出、错误管道,并且获取它们的返回码。此模块打算代替一些老旧的模块与功能:

os.system
os.spawn*

在下面的段落中,你可以找到关于 subprocess 模块如何代替这些模块和功能的相关信息。

参见

PEP 324 — 提出 subprocess 模块的 PEP

使用 subprocess 模块

推荐的调用子进程的方式是在任何它支持的用例中使用 run() 函数。对于更进阶的用例,也可以使用底层的 Popen 接口。

run() 函数是在 Python 3.5 被添加的。

subprocess.run(args, **, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None, *other_popen_kwargs)

运行被 arg 描述的指令. 等待指令完成, 然后返回一个 CompletedProcess 实例.

以上显示的参数仅仅是最简单的一些,下面 常用参数 描述(因此在缩写签名中使用仅关键字标示)。完整的函数头和 Popen 的构造函数一样,此函数接受的大多数参数都被传递给该接口。(timeout, input, checkcapture_output 除外)。

如果 capture_output 设为 true,stdout 和 stderr 将会被捕获。在使用时,内置的 Popen 对象将自动用 stdout=PIPEstderr=PIPE 创建。stdoutstderr 参数不应当与 capture_output 同时提供。如果你希望捕获并将两个流合并在一起,使用 stdout=PIPEstderr=STDOUT 来代替 capture_output

timeout 参数将被传递给 Popen.communicate()。如果发生超时,子进程将被杀死并等待。 TimeoutExpired 异常将在子进程中断后被抛出。

input 参数将被传递给 Popen.communicate() 以及子进程的 stdin。 如果使用此参数,它必须是一个字节序列。 如果指定了 encodingerrors 或者将 text 设置为 True,那么也可以是一个字符串。 当使用此参数时,在创建内部 Popen 对象时将自动带上 stdin=PIPE,并且不能再手动指定 stdin 参数。

如果 check 设为 True, 并且进程以非零状态码退出, 一个 CalledProcessError 异常将被抛出. 这个异常的属性将设置为参数, 退出码, 以及标准输出和标准错误, 如果被捕获到.

如果 encoding 或者 error 被指定, 或者 text 被设为 True, 标准输入, 标准输出和标准错误的文件对象将通过指定的 encodingerrors 以文本模式打开, 否则以默认的 io.TextIOWrapper 打开. universal_newline 参数等同于 text 并且提供了向后兼容性. 默认情况下, 文件对象是以二进制模式打开的.

如果 env 不是 None, 它必须是一个字典, 为新的进程设置环境变量; 它用于替换继承的当前进程的环境的默认行为. 它将直接被传递给 Popen.

示例:

>>> subprocess.run(["ls","-l"])# doesn't capture output
CompletedProcess(args=['ls','-l'], returncode=0)
>>> subprocess.run("exit 1", shell=True, check=True)
Traceback(most recent call last):
...
subprocess.CalledProcessError:Command'exit 1' returned non-zero exit status 1
>>> subprocess.run(["ls","-l","/dev/null"], capture_output=True)
CompletedProcess(args=['ls','-l','/dev/null'], returncode=0,
stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n', stderr=b'')

3.5 新版功能.

在 3.6 版更改: 添加了 encodingerrors 形参.

在 3.7 版更改: 添加了 text 形参, 作为 universal_newlines 的一个更好理解的别名. 添加了 capture_output 形参.

classsubprocess.CompletedProcess

run() 的返回值, 代表一个进程已经结束.

  • args

    被用作启动进程的参数. 可能是一个列表或字符串.

  • returncode

    子进程的退出状态码. 通常来说, 一个为 0 的退出码表示进程运行正常.

    一个负值 -N 表示子进程被信号 N 中断 (仅 POSIX).

  • stdout

    从子进程捕获到的标准输出. 一个字节序列, 或一个字符串, 如果 run() 是设置了 encoding, errors 或者 text=True 来运行的. 如果未有捕获, 则为 None.

    如果你通过 stderr=subprocess.STDOUT 运行进程,标准输入和标准错误将被组合在这个属性中,并且 stderr 将为 None

  • stderr

    捕获到的子进程的标准错误. 一个字节序列, 或者一个字符串, 如果 run() 是设置了参数 encoding, errors 或者 text=True 运行的. 如果未有捕获, 则为 None.

  • check_returncode()

    如果 returncode 非零, 抛出 CalledProcessError.

3.5 新版功能.

subprocess.DEVNULL

可被 Popenstdin, stdout 或者 stderr 参数使用的特殊值, 表示使用特殊文件 os.devnull.

3.3 新版功能.

subprocess.PIPE

可被 Popenstdin, stdout 或者 stderr 参数使用的特殊值, 表示打开标准流的管道. 常用于 Popen.communicate().

subprocess.STDOUT

可被 Popenstdinstdout 或者 stderr 参数使用的特殊值, 表示标准错误与标准输出使用同一句柄。

exceptionsubprocess.SubprocessError

此模块的其他异常的基类。

3.3 新版功能.

exceptionsubprocess.TimeoutExpired

SubprocessError 的子类,等待子进程的过程中发生超时时被抛出。

  • cmd

    用于创建子进程的指令。

  • timeout

    超时秒数。

  • output

    子进程的输出, 如果被 run()check_output() 捕获。否则为 None

  • stdout

    对 output 的别名,对应的有 stderr

  • stderr

    子进程的标准错误输出,如果被 run() 捕获。 否则为 None

3.3 新版功能.

在 3.5 版更改: 添加了 stdoutstderr 属性。

exceptionsubprocess.CalledProcessError

SubprocessError 的子类,当一个被 check_call()check_output() 函数运行的子进程返回了非零退出码时被抛出。

  • returncode

    子进程的退出状态。如果程序由一个信号终止,这将会被设为一个负的信号码。

  • cmd

    用于创建子进程的指令。

  • output

    子进程的输出, 如果被 run()check_output() 捕获。否则为 None

  • stdout

    对 output 的别名,对应的有 stderr

  • stderr

    子进程的标准错误输出,如果被 run() 捕获。 否则为 None

在 3.5 版更改: 添加了 stdoutstderr 属性。

常用参数

为了支持丰富的使用案例, Popen 的构造函数(以及方便的函数)接受大量可选的参数。对于大多数典型的用例,许多参数可以被安全地留以它们的默认值。通常需要的参数有:

args 被所有调用需要,应当为一个字符串,或者一个程序参数序列。提供一个参数序列通常更好,它可以更小心地使用参数中的转义字符以及引用(例如允许文件名中的空格)。如果传递一个简单的字符串,则 shell 参数必须为 True (见下文)或者该字符串中将被运行的程序名必须用简单的命名而不指定任何参数。

stdin*, *stdoutstderr 分别指定了执行的程序的标准输入、输出和标准错误文件句柄。合法的值有 PIPEDEVNULL 、 一个现存的文件描述符(一个正整数)、一个现存的文件对象以及 NonePIPE 表示应该新建一个对子进程的管道。 DEVNULL 表示使用特殊的文件 os.devnull。当使用默认设置 None 时,将不会进行重定向,子进程的文件流将继承自父进程。另外, stderr 可能为 STDOUT,表示来自于子进程的标准错误数据应该被 stdout 相同的句柄捕获。

如果 encodingerrors 被指定,或者 text (也名为 universal_newlines*)为真,则文件对象 *stdinstdoutstderr 将会使用在此次调用中指定的 encodingerrors 以文本模式打开或者为默认的 io.TextIOWrapper

当构造函数的 newline 参数为 None 时。对于 stdin*, 输入的换行符 '\n' 将被转换为默认的换行符 os.linesep。对于 *stdoutstderr, 所有输出的换行符都被转换为 '\n'。更多信息。

如果文本模式未被使用, stdin*, *stdoutstderr 将会以二进制流模式打开。没有编码与换行符转换发生。

3.6 新版功能: 添加了 encodingerrors 形参。

3.7 新版功能: 添加了 text 形参作为 universal_newlines 的别名。

注解

文件对象 Popen.stdinPopen.stdoutPopen.stderr 的换行符属性不会被 Popen.communicate() 方法更新。

如果 shell 设为 True,,则使用 shell 执行指定的指令。如果您主要使用 Python 增强的控制流(它比大多数系统 shell 提供的强大),并且仍然希望方便地使用其他 shell 功能,如 shell 管道、文件通配符、环境变量展开以及 ~ 展开到用户家目录,这将非常有用。但是,注意 Python 自己也实现了许多类似 shell 的特性(例如 glob, fnmatch, os.walk(), os.path.expandvars(), os.path.expanduser()shutil)。

在 3.3 版更改: 当 universal_newline 被设为 True,则类使用 locale.getpreferredencoding(False) 编码来代替 locale.getpreferredencoding()

这些选项以及所有其他选项在 Popen 构造函数文档中有更详细的描述。

Popen 构造函数

此模块的底层的进程创建与管理由 Popen 类处理。它提供了很大的灵活性,因此开发者能够处理未被便利函数覆盖的不常见用例。

classsubprocess.Popen(args, bufsize=- 1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), **, group=None, extra_groups=None, user=None, umask=- 1, encoding=None, errors=None, text=None, pipesize=- 1*)

在一个新的进程中执行子程序。 在 POSIX 上,该类会使用类似于 os.execvpe() 的行为来执行子程序。 在 Windows 上,该类会使用 Windows CreateProcess() 函数。 Popen 的参数如下。

args 应当是一个程序参数的序列或者是一个单独的字符串或 path-like object。 默认情况下,如果 args 是序列则要运行的程序为 args 中的第一项。 如果 args 是字符串,则其解读依赖于具体平台,如下所述。 请查看 shellexecutable 参数了解其与默认行为的其他差异。 除非另有说明,否则推荐以序列形式传入 args

警告

为了最大化可靠性,请使用可执行文件的完整限定路径。 要在 PATH 中搜索一个未限定名称,请使用 shutil.which()。 在所有平台上,传入 sys.executable 是再次启动当前 Python 解释器的推荐方式,并请使用 -m 命令行格式来启动已安装的模块。

executable (或 args 的第一项) 路径的解析方式依赖于具体平台。 并要注意当解析或搜索可执行文件路径时,cwd 会覆盖当前工作目录而 env 可以覆盖 PATH 环境变量。 对于 Windows,请参阅 lpApplicationName 的文档以及 lpCommandLine 形参 (传给 WinAPI CreateProcess),并要注意当解析或搜索可执行文件路径时如果传入 shell=False,则 cwd 不会覆盖当前工作目录而 env 无法覆盖 PATH 环境变量。 使用完整路径可避免所有这些变化情况。

向外部函数传入序列形式参数的一个例子如下:

Popen(["/usr/bin/git","commit","-m","Fixes a bug."])

在 POSIX,如果 args 是一个字符串,此字符串被作为将被执行的程序的命名或路径解释。但是,只有在不传递任何参数给程序的情况下才能这么做。

注解

将 shell 命令拆分为参数序列的方式可能并不很直观,特别是在复杂的情况下。 shlex.split() 可以演示如何确定 args 适当的拆分形式:

>>>import shlex, subprocess
>>> command_line = input()
/bin/vikings -input eggs.txt -output "spam spam.txt"-cmd "echo '$MONEY'"
>>> args = shlex.split(command_line)
>>>print(args)
['/bin/vikings','-input','eggs.txt','-output','spam spam.txt','-cmd',"echo '$MONEY'"]
>>> p = subprocess.Popen(args)# Success!

特别注意,由 shell 中的空格分隔的选项(例如 -input*)和参数(例如 *eggs.txt )位于分开的列表元素中,而在需要时使用引号或反斜杠转义的参数在 shell (例如包含空格的文件名或上面显示的 echo 命令)是单独的列表元素。

在 Windows,如果 args 是一个序列,他将通过一个在 在 Windows 上将参数列表转换为一个字符串 描述的方式被转换为一个字符串。这是因为底层的 CreateProcess() 只处理字符串。

在 3.6 版更改: 在 POSIX 上如果 shellFalse 并且序列包含路径类对象则 args 形参可以接受一个 path-like object。

在 3.8 版更改: 如果在Windows 上 shellFalse 并且序列包含字节串和路径类对象则 args 形参可以接受一个 path-like object。

参数 shell (默认为 False)指定是否使用 shell 执行程序。如果 shellTrue,更推荐将 args 作为字符串传递而非序列。

在 POSIX,当 shell=True, shell 默认为 /bin/sh。如果 args 是一个字符串,此字符串指定将通过 shell 执行的命令。这意味着字符串的格式必须和在命令提示符中所输入的完全相同。这包括,例如,引号和反斜杠转义包含空格的文件名。如果 args 是一个序列,第一项指定了命令,另外的项目将作为传递给 shell (而非命令) 的参数对待。也就是说, Popen 等同于:

Popen(['/bin/sh','-c', args[0], args[1],...])

在 Windows,使用 shell=True,环境变量 COMSPEC 指定了默认 shell。在 Windows 你唯一需要指定 shell=True 的情况是你想要执行内置在 shell 中的命令(例如 dir 或者 copy)。在运行一个批处理文件或者基于控制台的可执行文件时,不需要 shell=True

bufsize 将在 open() 函数创建了 stdin/stdout/stderr 管道文件对象时作为对应的参数供应:

  • 0 表示不使用缓冲区 (读取与写入是一个系统调用并且可以返回短内容)
  • 1 表示行缓冲(只有 universal_newlines=True 时才有用,例如,在文本模式中)
  • 任何其他正值表示使用一个约为对应大小的缓冲区
  • 负的 bufsize (默认)表示使用系统默认的 io.DEFAULT_BUFFER_SIZE。

在 3.3.1 版更改: bufsize 现在默认为 -1 来启用缓冲,以符合大多数代码所期望的行为。在 Python 3.2.4 和 3.3.1 之前的版本中,它错误地将默认值设为了为 0,这是无缓冲的并且允许短读取。这是无意的,并且与大多数代码所期望的 Python 2 的行为不一致。

executable 参数指定一个要执行的替换程序。这很少需要。当 shell=Trueexecutable 替换 args 指定运行的程序。但是,原始的 args 仍然被传递给程序。大多数程序将被 args 指定的程序作为命令名对待,这可以与实际运行的程序不同。在 POSIX, args 名作为实际调用程序中可执行文件的显示名称,例如 ps。如果 shell=True,在 POSIX, executable 参数指定用于替换默认 shell /bin/sh 的 shell。

在 3.6 版更改: 在POSIX 上 executable 形参可以接受一个 path-like object。

在 3.8 版更改: 在Windows 上 executable 形参可以接受一个字节串和 path-like object。

stdin, stdoutstderr 分别指定被运行的程序的标准输入、输出和标准错误的文件句柄。合法的值有 PIPEDEVNULL , 一个存在的文件描述符(一个正整数),一个存在的 文件对象 以及 NonePIPE 表示应创建一个新的对子进程的管道。 DEVNULL 表示使用特殊的 os.devnull 文件。使用默认的 None,则不进行成定向;子进程的文件流将继承自父进程。另外, stderr 可设为 STDOUT,表示应用程序的标准错误数据应和标准输出一同捕获。

如果 preexec_fn 被设为一个可调用对象,此对象将在子进程刚创建时被调用。(仅 POSIX)

警告

preexec_fn 形参在应用程序中存在多线程时是不安全的。子进程在调用前可能死锁。如果你必须使用它,保持警惕!最小化你调用的库的数量。

注解

如果你需要修改子进程环境,使用 env 形参而非在 preexec_fn 中进行。 start_new_session 形参可以代替之前常用的 preexec_fn 来在子进程中调用 os.setsid()。

在 3.8 版更改: preexec_fn 形参在子解释器中已不再受支持。 在子解释器中使用此形参将引发 RuntimeError。 这个新限制可能会影响部署在 mod_wsgi, uWSGI 和其他嵌入式环境中的应用。

如果 close_fds 为真,所有文件描述符除了 0, 1, 2 之外都会在子进程执行前关闭。而当 close_fds 为假时,文件描述符遵守它们继承的标志,如 文件描述符的继承 所述。

在 Windows,如果 close_fds 为真, 则子进程不会继承任何句柄,除非在 STARTUPINFO.IpAttributeListhandle_list 的键中显式传递,或者通过标准句柄重定向传递。

在 3.2 版更改: close_fds 的默认值已经从 False 修改为上述值。

在 3.7 版更改: 在 Windows,当重定向标准句柄时 close_fds 的默认值从 False 变为 True。现在重定向标准句柄时有可能设置 close_fdsTrue。(标准句柄指三个 stdio 的句柄)

pass_fds 是一个可选的在父子进程间保持打开的文件描述符序列。提供任何 pass_fds 将强制 close_fdsTrue。(仅 POSIX)

在 3.2 版更改: 加入了 pass_fds 形参。

如果 cwd 不为 None,此函数在执行子进程前会将当前工作目录改为 cwd*。 *cwd 可以是一个字符串、字节串或 路径类对象。 在 POSIX 上,如果可执行文件路径为相对路径则此函数会相对于 cwd 来查找 executable (或 args 的第一项)。

在 3.6 版更改: 在 POSIX 上 cwd 形参接受一个 path-like object。

在 3.7 版更改: 在 Windows 上 cwd 形参接受一个 path-like object。

在 3.8 版更改: 在 Windows 上 cwd 形参接受一个字节串对象。

如果 restore_signals 为 true(默认值),则 Python 设置为 SIG_IGN 的所有信号将在 exec 之前的子进程中恢复为 SIG_DFL。目前,这包括 SIGPIPE ,SIGXFZ 和 SIGXFSZ 信号。 (仅 POSIX)

在 3.2 版更改: restore_signals 被加入。

如果 start_new_session 为 true,则 setsid() 系统调用将在子进程执行之前被执行。(仅 POSIX)

在 3.2 版更改: start_new_session 被添加。

如果 group 不为 None,则 setregid() 系统调用将于子进程执行之前在下级进程中进行。 如果所提供的值为一个字符串,将通过 grp.getgrnam() 来查找它,并将使用 gr_gid 中的值。 如果该值为一个整数,它将被原样传递。 (POSIX 专属)

可用性: POSIX

3.9 新版功能.

如果 extra_groups 不为 None,则 setgroups() 系统调用将于子进程之前在下级进程中进行。 在 extra_groups 中提供的字符串将通过 grp.getgrnam() 来查找,并将使用 gr_gid 中的值。 整数值将被原样传递。 (POSIX 专属)

可用性: POSIX

3.9 新版功能.

如果 user 不为 None,则 setreuid() 系统调用将于子进程执行之前在下级进程中进行。 如果所提供的值为一个字符串,将通过 pwd.getpwnam() 来查找它,并将使用 pw_uid 中的值。 如果该值为一个整数,它将被原样传递。 (POSIX 专属)

可用性: POSIX

3.9 新版功能.

如果 umask 不为负值,则 umask() 系统调用将在子进程执行之前在下级进程中进行。

可用性: POSIX

3.9 新版功能.

如果 env 不为 None,则必须为一个为新进程定义了环境变量的字典;这些用于替换继承的当前进程环境的默认行为。

注解

如果指定, env 必须提供所有被子进程需求的变量。在 Windows,为了运行一个 side-by-side assembly ,指定的 env**必须** 包含一个有效的 SystemRoot

如果 encodingerrors 被指定,或者 text 为 true,则文件对象 stdin, stdoutstderr 将会以指定的编码和 errors 以文本模式打开,如同 常用参数 所述。 universal_newlines 参数等同于 text 并且提供向后兼容性。默认情况下,文件对象都以二进制模式打开。

3.6 新版功能: encodingerrors 被添加。

3.7 新版功能: text 作为 universal_newlines 的一个更具可读性的别名被添加。

如果给出, startupinfo 将是一个将被传递给底层的 CreateProcess 函数的 STARTUPINFO 对象。 creationflags,如果给出,可以是一个或多个以下标志之一:

  • CREATE_NEW_CONSOLE
  • CREATE_NEW_PROCESS_GROUP
  • ABOVE_NORMAL_PRIORITY_CLASS
  • BELOW_NORMAL_PRIORITY_CLASS
  • HIGH_PRIORITY_CLASS
  • IDLE_PRIORITY_CLASS
  • NORMAL_PRIORITY_CLASS
  • REALTIME_PRIORITY_CLASS
  • CREATE_NO_WINDOW
  • DETACHED_PROCESS
  • CREATE_DEFAULT_ERROR_MODE
  • CREATE_BREAKAWAY_FROM_JOB

PIPE 被用作 stdin, stdoutstderrpipesize 可被用于改变管道的大小。 管道的大小仅会在受支持的平台上被改变(当撰写本文档时只有 Linux 支持)。 其他平台将忽略此形参。

3.10 新版功能: 增加了 pipesize 形参。

Popen 对象支持通过 with 语句作为上下文管理器,在退出时关闭文件描述符并等待进程:

withPopen(["ifconfig"], stdout=PIPE)as proc:
    log.write(proc.stdout.read())

引发一个 审计事件subprocess.Popen,附带参数 executable, args, cwd, env

在 3.2 版更改: 添加了上下文管理器支持。

在 3.6 版更改: 现在,如果 Popen 析构时子进程仍然在运行,则析构器会发送一个 ResourceWarning 警告。

在 3.8 版更改: 在某些情况下 Popen 可以使用 os.posix_spawn() 以获得更好的性能。在适用于 Linux 的 Windows 子系统和 QEMU 用户模拟器上,使用 os.posix_spawn() 的 Popen 构造器不再会因找不到程序等错误而引发异常,而是上下级进程失败并返回一个非零的 returncode

异常

在子进程中抛出的异常,在新的进程开始执行前,将会被再次在父进程中抛出。

被引发的最一般异常是 OSError。 例如这会在尝试执行一个不存在的文件时发生。 应用程序应当为 OSError 异常做好准备。 请注意,如果 shell=True,则 OSError 仅会在未找到选定的 shell 本身时被引发。 要确定 shell 是否未找到所请求的应用程序,必须检查来自子进程的返回码或输出。

如果 Popen 调用时有无效的参数,则一个 ValueError 将被抛出。

check_call()check_output() 在调用的进程返回非零退出码时将抛出 CalledProcessError

所有接受 timeout 形参的函数与方法,例如 call()Popen.communicate() 将会在进程退出前超时到期时抛出 TimeoutExpired

此模块中定义的异常都继承自 SubprocessError

3.3 新版功能: 基类 SubprocessError 被添加。

安全考量

不同于某些其他的 popen 函数,这个实现绝不会隐式地调用系统 shell。 这意味着所有字符,包括 shell 元字符,都可以安全地被传递给子进程。 如果 shell 通过 shell=True 被显式地发起调用,则应用程序有责任确保所有空白符和元字符被适当地转义以避免 shell 注入 漏洞。 在 某些平台 上,可以使用 shlex.quote() 来执行这样的转义。

Popen 对象

Popen 类的实例拥有以下方法:

Popen.poll()

检查子进程是否已被终止。设置并返回 returncode 属性。否则返回 None

Popen.wait(timeout=None)

等待子进程被终止。设置并返回 returncode 属性。

如果进程在 timeout 秒后未中断,抛出一个 TimeoutExpired 异常,可以安全地捕获此异常并重新等待。

注解

stdout=PIPE 或者 stderr=PIPE 并且子进程产生了足以阻塞 OS 管道缓冲区接收更多数据的输出到管道时,将会发生死锁。当使用管道时用 Popen.communicate() 来规避它。

注解

此函数使用了一个 busy loop (非阻塞调用以及短睡眠) 实现。使用 asyncio 模块进行异步等待: 参阅 asyncio.create_subprocess_exec

在 3.3 版更改: timeout 被添加

Popen.communicate(input=None, timeout=None)

与进程交互:将数据发送到 stdin。 从 stdout 和 stderr 读取数据,直到抵达文件结尾。 等待进程终止并设置 returncode 属性。 可选的 input 参数应为要发送到下级进程的数据,或者如果没有要发送到下级进程的数据则为 None。 如果流是以文本模式打开的,则 input 必须为字符串。 在其他情况下,它必须为字节串。

communicate() 返回一个 (stdout_data, stderr_data) 元组。如果文件以文本模式打开则为字符串;否则字节。

注意如果你想要向进程的 stdin 传输数据,你需要通过 stdin=PIPE 创建此 Popen 对象。类似的,要从结果元组获取任何非 None 值,你同样需要设置 stdout=PIPE 或者 stderr=PIPE

如果进程在 timeout 秒后未终止,一个 TimeoutExpired 异常将被抛出。捕获此异常并重新等待将不会丢失任何输出。

如果超时到期,子进程不会被杀死,所以为了正确清理一个行为良好的应用程序应该杀死子进程并完成通讯。

proc = subprocess.Popen(...)
try:
    outs, errs = proc.communicate(timeout=15)
exceptTimeoutExpired:
    proc.kill()
    outs, errs = proc.communicate()

注解

内存里数据读取是缓冲的,所以如果数据尺寸过大或无限,不要使用此方法。

在 3.3 版更改: timeout 被添加

Popen.send_signal(signal)

将信号 signal 发送给子进程。

如果进程已完成则不做任何操作。

注解

在 Windows, SIGTERM 是一个 terminate() 的别名。 CTRL_C_EVENT 和 CTRL_BREAK_EVENT 可以被发送给以包含 CREATE_NEW_PROCESScreationflags 形参启动的进程。

Popen.terminate()

停止子进程。 在 POSIX 操作系统上,此方法会发送 SIGTERM 给子进程。 在 Windows 上则会调用 Win32 API 函数 TerminateProcess() 来停止子进程。

Popen.kill()

杀死子进程。 在 POSIX 操作系统上,此函数会发送 SIGKILL 给子进程。 在 Windows 上 kill() 则是 terminate() 的别名。

以下属性也是可用的:

Popen.args

args 参数传递给 Popen — 一个程序参数的序列或者一个简单字符串。

3.3 新版功能.

Popen.stdin

如果 stdin 参数为 PIPE,此属性是一个类似 open() 返回的可写的流对象。如果 encodingerrors 参数被指定或者 universal_newlines 参数为 True,则此流是一个文本流,否则是字节流。如果 stdin 参数非 PIPE, 此属性为 None

Popen.stdout

如果 stdout 参数是 PIPE,此属性是一个类似 open() 返回的可读流。从流中读取子进程提供的输出。如果 encodingerrors 参数被指定或者 universal_newlines 参数为 True,此流为文本流,否则为字节流。如果 stdout 参数非 PIPE,此属性为 None

Popen.stderr

如果 stderr 参数是 PIPE,此属性是一个类似 open() 返回的可读流。从流中读取子进程提供的输出。如果 encodingerrors 参数被指定或者 universal_newlines 参数为 True,此流为文本流,否则为字节流。如果 stderr 参数非 PIPE,此属性为 None

警告

使用 communicate() 而非 .stdin.write.stdout.read 或者 .stderr.read 来避免由于任意其他 OS 管道缓冲区被子进程填满阻塞而导致的死锁。

Popen.pid

子进程的进程号。

注意如果你设置了 shell 参数为 True,则这是生成的子 shell 的进程号。

Popen.returncode

此进程的退出码,由 poll()wait() 设置(以及直接由 communicate() 设置)。一个 None 值 表示此进程仍未结束。

一个负值 -N 表示子进程被信号 N 中断 (仅 POSIX).

Windows Popen 助手

STARTUPINFO 类和以下常数仅在 Windows 有效。

classsubprocess.STARTUPINFO(**, dwFlags=0, hStdInput=None, hStdOutput=None, hStdError=None, wShowWindow=0, lpAttributeList=None*)

Popen 创建时部分支持 Windows 的 STARTUPINFO 结构。接下来的属性仅能通过关键词参数设置。

在 3.7 版更改: 仅关键词参数支持被加入。

  • dwFlags

    一个位字段,用于确定进程在创建窗口时是否使用某些 STARTUPINFO 属性。

    si = subprocess.STARTUPINFO()
    si.dwFlags = subprocess.STARTF_USESTDHANDLES | subprocess.STARTF_USESHOWWINDOW
  • hStdInput

    如果 dwFlags 被指定为 STARTF_USESTDHANDLES,则此属性是进程的标准输入句柄,如果 STARTF_USESTDHANDLES 未指定,则默认的标准输入是键盘缓冲区。

  • hStdOutput

    如果 dwFlags 被指定为 STARTF_USESTDHANDLES,则此属性是进程的标准输出句柄。除此之外,此此属性将被忽略并且默认标准输出是控制台窗口缓冲区。

  • hStdError

    如果 dwFlags 被指定为 STARTF_USESTDHANDLES,则此属性是进程的标准错误句柄。除此之外,此属性将被忽略并且默认标准错误为控制台窗口的缓冲区。

  • wShowWindow

    如果 dwFlags 指定了 STARTF_USESHOWWINDOW,此属性可为能被指定为 函数 ShowWindow 的nCmdShow 的形参的任意值,除了 SW_SHOWDEFAULT。如此之外,此属性被忽略。

    SW_HIDE 被提供给此属性。它在 Popenshell=True 调用时使用。

  • lpAttributeList

    STARTUPINFOEX 给出的用于进程创建的额外属性字典。

    支持的属性:

    • handle_list

      将被继承的句柄的序列。如果非空, close_fds 必须为 true。

      当传递给 Popen 构造函数时,这些句柄必须暂时地能被 os.set_handle_inheritable() 继承,否则 OSError 将以 Windows error ERROR_INVALID_PARAMETER (87) 抛出。

      警告

      在多线程进程中,请谨慎使用,以便在将此功能与对继承所有句柄的其他进程创建函数——例如 os.system() 的并发调用——相结合时,避免泄漏标记为可继承的句柄。这也应用于临时性创建可继承句柄的标准句柄重定向。

    3.7 新版功能.

Windows 常数

subprocess 模块曝出以下常数。

subprocess.STD_INPUT_HANDLE

标准输入设备,这是控制台输入缓冲区 CONIN$

subprocess.STD_OUTPUT_HANDLE

标准输出设备。最初,这是活动控制台屏幕缓冲区 CONOUT$

subprocess.STD_ERROR_HANDLE

标准错误设备。最初,这是活动控制台屏幕缓冲区 CONOUT$

subprocess.SW_HIDE

隐藏窗口。另一个窗口将被激活。

subprocess.STARTF_USESTDHANDLES

指明 STARTUPINFO.hStdInput, STARTUPINFO.hStdOutputSTARTUPINFO.hStdError 属性包含额外的信息。

subprocess.STARTF_USESHOWWINDOW

指明 STARTUPINFO.wShowWindow 属性包含额外的信息。

subprocess.CREATE_NEW_CONSOLE

新的进程将有新的控制台,而不是继承父进程的(默认)控制台。

subprocess.CREATE_NEW_PROCESS_GROUP

用于指明将创建一个新的进程组的 Popen``creationflags 形参。 这个旗标对于在子进程上使用 os.kill() 来说是必须的。

如果指定了 CREATE_NEW_CONSOLE 则这个旗标会被忽略。

subprocess.ABOVE_NORMAL_PRIORITY_CLASS

用于指明一个新进程将具有高于平均的优先级的 Popen``creationflags 形参。

3.7 新版功能.

subprocess.BELOW_NORMAL_PRIORITY_CLASS

用于指明一个新进程将具有低于平均的优先级的 Popen``creationflags 形参。

3.7 新版功能.

subprocess.HIGH_PRIORITY_CLASS

用于指明一个新进程将具有高优先级的 Popen``creationflags 形参。

3.7 新版功能.

subprocess.IDLE_PRIORITY_CLASS

用于指明一个新进程将具有空闲(最低)优先级的 Popen``creationflags 形参。

3.7 新版功能.

subprocess.NORMAL_PRIORITY_CLASS

用于指明一个新进程将具有正常(默认)优先级的 Popen``creationflags 形参。

3.7 新版功能.

subprocess.REALTIME_PRIORITY_CLASS

用于指明一个新进程将具有实时优先级的 Popen``creationflags 形参。 你应当几乎永远不使用 REALTIME_PRIORITY_CLASS,因为这会中断管理鼠标输入、键盘输入以及后台磁盘刷新的系统线程。 这个类只适用于直接与硬件“对话”,或者执行短暂任务具有受限中断的应用。

3.7 新版功能.

subprocess.CREATE_NO_WINDOW

指明一个新进程将不会创建窗口的 Popen``creationflags 形参。

3.7 新版功能.

subprocess.DETACHED_PROCESS

指明一个新进程将不会继承其父控制台的 Popen``creationflags 形参。 这个值不能与 CREATE_NEW_CONSOLE 一同使用。

3.7 新版功能.

subprocess.CREATE_DEFAULT_ERROR_MODE

指明一个新进程不会继承调用方进程的错误模式的 Popen``creationflags 形参。 新进程会转为采用默认的错误模式。 这个特性特别适用于运行时禁用硬错误的多线程 shell 应用。

3.7 新版功能.

subprocess.CREATE_BREAKAWAY_FROM_JOB

指明一个新进程不会关联到任务的 Popen creationflags 形参。

3.7 新版功能.

较旧的高阶 API

在 Python 3.5 之前,这三个函数组成了 subprocess 的高阶 API。 现在你可以在许多情况下使用 run(),但有大量现在代码仍会调用这些函数。

subprocess.call(args, **, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, *other_popen_kwargs)

运行由 args 所描述的命令。 等待命令完成,然后返回 returncode 属性。

需要捕获 stdout 或 stderr 的代码应当改用 run():

run(...).returncode

要屏蔽 stdout 或 stderr,可提供 DEVNULL 这个值。

上面显示的参数只是常见的一些。 完整的函数签名与 Popen 构造器的相同 —— 此函数会将所提供的 timeout 之外的全部参数直接传递给目标接口。

注解

请不要在此函数中使用 stdout=PIPEstderr=PIPE。 如果子进程向管道生成了足以填满 OS 管理缓冲区的输出而管道还未被读取时它将会阻塞。

在 3.3 版更改: timeout 被添加

subprocess.check_call(args, **, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, *other_popen_kwargs)

Run command with arguments. Wait for command to complete. If the return code was zero then return, otherwise raise CalledProcessError. The CalledProcessError object will have the return code in the returncode attribute. If check_call() was unable to start the process it will propagate the exception that was raised.

需要捕获 stdout 或 stderr 的代码应当改用 run():

run(..., check=True)

要屏蔽 stdout 或 stderr,可提供 DEVNULL 这个值。

上面显示的参数只是常见的一些。 完整的函数签名与 Popen 构造器的相同 —— 此函数会将所提供的 timeout 之外的全部参数直接传递给目标接口。

注解

请不要在此函数中使用 stdout=PIPEstderr=PIPE。 如果子进程向管道生成了足以填满 OS 管理缓冲区的输出而管道还未被读取时它将会阻塞。

在 3.3 版更改: timeout 被添加

subprocess.check_output(args, **, stdin=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, universal_newlines=None, timeout=None, text=None, *other_popen_kwargs)

附带参数运行命令并返回其输出。

如果返回码非零则会引发 CalledProcessErrorCalledProcessError 对象将在 returncode 属性中保存返回码并在 output 属性中保存所有输出。

这相当于:

run(..., check=True, stdout=PIPE).stdout

上面显示的参数只是常见的一些。 完整的函数签名与 run() 的大致相同 —— 大部分参数会通过该接口直接传递。 存在一个与 run() 行为不同的 API 差异:传递 input=None 的行为将与 input=b'' (或 input='',具体取决于其他参数) 一样而不是使用父对象的标准输入文件处理。

默认情况下,此函数将把数据返回为已编码的字节串。 输出数据的实际编码格式将取决于发起调用的命令,因此解码为文本的操作往往需要在应用程序层级上进行处理。

此行为可以通过设置 text, encoding, errors 或将 universal_newlines 设为 True 来重载。

要在结果中同时捕获标准错误,请使用 stderr=subprocess.STDOUT:

>>> subprocess.check_output(
..."ls non_existent_file; exit 0",
...     stderr=subprocess.STDOUT,
...     shell=True)
'ls: non_existent_file: No such file or directory\n'

3.1 新版功能.

在 3.3 版更改: timeout 被添加

在 3.4 版更改: 增加了对 input 关键字参数的支持。

在 3.6 版更改: 增加了 encodingerrors

3.7 新版功能: text 作为 universal_newlines 的一个更具可读性的别名被添加。

使用 subprocess 模块替换旧函数

在这一节中,”a 改为 b” 意味着 b 可以被用作 a 的替代。

注解

在这一节中的所有 “a” 函数会在找不到被执行的程序时(差不多)静默地失败;”b” 替代函数则会改为引发 OSError

此外,在使用 check_output() 时如果替代函数所请求的操作产生了非零返回值则将失败并引发 CalledProcessError。 操作的输出仍能以所引发异常的 output 属性的方式被访问。

在下列例子中,我们假定相关的函数都已从 subprocess 模块中导入了。

替代 /bin/sh shell 命令替换

output=$(mycmd myarg)

改为:

output = check_output(["mycmd","myarg"])

替代 shell 管道

output=$(dmesg | grep hda)

改为:

p1 =Popen(["dmesg"], stdout=PIPE)
p2 =Popen(["grep","hda"], stdin=p1.stdout, stdout=PIPE)
p1.stdout.close()# Allow p1 to receive a SIGPIPE if p2 exits.
output = p2.communicate()[0]

启动 p2 之后再执行 p1.stdout.close() 调用很重要,这是为了让 p1 能在 p2 先于 p1 退出时接收到 SIGPIPE。

另外,对于受信任的输入,shell 本身的管道支持仍然可被直接使用:

output=$(dmesg | grep hda)

改为:

output = check_output("dmesg | grep hda", shell=True)

替代 os.system()

sts = os.system("mycmd"+" myarg")
# becomes
retcode = call("mycmd"+" myarg", shell=True)

注释:

  • 通过 shell 来调用程序通常是不必要的。
  • call() 返回值的编码方式与 os.system() 的不同。
  • os.system() 函数在命令运行期间会忽略 SIGINT 和 SIGQUIT 信号,但调用方必须在使用 subprocess 模块时分别执行此操作。

一个更现实的例子如下所示:

try:
    retcode = call("mycmd"+" myarg", shell=True)
if retcode <0:
print("Child was terminated by signal",-retcode, file=sys.stderr)
else:
print("Child returned", retcode, file=sys.stderr)
exceptOSErroras e:
print("Execution failed:", e, file=sys.stderr)

替代 os.spawn 函数族

P_NOWAIT 示例:

pid = os.spawnlp(os.P_NOWAIT,"/bin/mycmd","mycmd","myarg")
==>
pid =Popen(["/bin/mycmd","myarg"]).pid

P_WAIT 示例:

retcode = os.spawnlp(os.P_WAIT,"/bin/mycmd","mycmd","myarg")
==>
retcode = call(["/bin/mycmd","myarg"])

Vector 示例:

os.spawnvp(os.P_NOWAIT, path, args)
==>
Popen([path]+ args[1:])

Environment 示例:

os.spawnlpe(os.P_NOWAIT,"/bin/mycmd","mycmd","myarg", env)
==>
Popen(["/bin/mycmd","myarg"], env={"PATH":"/usr/bin"})

替代 os.popen(), os.popen2(), os.popen3()

(child_stdin, child_stdout)= os.popen2(cmd, mode, bufsize)
==>
p =Popen(cmd, shell=True, bufsize=bufsize,
          stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdin, child_stdout)=(p.stdin, p.stdout)

(child_stdin,
 child_stdout,
 child_stderr)= os.popen3(cmd, mode, bufsize)
==>
p =Popen(cmd, shell=True, bufsize=bufsize,
          stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
(child_stdin,
 child_stdout,
 child_stderr)=(p.stdin, p.stdout, p.stderr)

 (child_stdin, child_stdout_and_stderr)= os.popen4(cmd, mode, bufsize)
==>
p =Popen(cmd, shell=True, bufsize=bufsize,
          stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
(child_stdin, child_stdout_and_stderr)=(p.stdin, p.stdout)

返回码以如下方式处理转写:

pipe = os.popen(cmd,'w')
...
rc = pipe.close()
if rc isnotNoneand rc >>8:
print("There were some errors")
==>
process =Popen(cmd, stdin=PIPE)
...
process.stdin.close()
if process.wait()!=0:
print("There were some errors")

来自 popen2 模块的替代函数

注解

如果 popen2 函数的 cmd 参数是一个字符串,命令会通过 /bin/sh 来执行。 如果是一个列表,命令会被直接执行。

(child_stdout, child_stdin)= popen2.popen2("somestring", bufsize, mode)
==>
p =Popen("somestring", shell=True, bufsize=bufsize,
          stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdout, child_stdin)=(p.stdout, p.stdin)

(child_stdout, child_stdin)= popen2.popen2(["mycmd","myarg"], bufsize, mode)
==>
p =Popen(["mycmd","myarg"], bufsize=bufsize,
          stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdout, child_stdin)=(p.stdout, p.stdin)

popen2.Popen3popen2.Popen4 基本上类似于 subprocess.Popen,不同之处在于:

  • Popen 如果执行失败会引发一个异常。
  • capturestderr 参数被替换为 stderr 参数。
  • 必须指定 stdin=PIPEstdout=PIPE
  • popen2 默认会关闭所有文件描述符,但对于 Popen 你必须指明 close_fds=True 以才能在所有平台或较旧的 Python 版本中确保此行为。

旧式的 Shell 发起函数

此模块还提供了以下来自 2.x commands 模块的旧版函数。 这些操作会隐式地发起调用系统 shell 并且上文所描述的有关安全与异常处理一致性保证都不适用于这些函数。

subprocess.getstatusoutput(cmd)

返回在 shell 中执行 cmd 产生的 (exitcode, output)

在 shell 中以 Popen.check_output() 执行字符串 cmd 并返回一个 2 元组 (exitcode, output)。 会使用当前区域设置的编码格式;

末尾的一个换行符会从输出中被去除。 命令的退出码可被解读为子进程的返回码。 例如:

>>> subprocess.getstatusoutput('ls /bin/ls')
(0,'/bin/ls')
>>> subprocess.getstatusoutput('cat /bin/junk')
(1,'cat: /bin/junk: No such file or directory')
>>> subprocess.getstatusoutput('/bin/junk')
(127,'sh: /bin/junk: not found')
>>> subprocess.getstatusoutput('/bin/kill $$')
(-15,'')

可用性: POSIX 和 Windows。

在 3.3.4 版更改: 添加了 Windows 支持。

此函数现在返回 (exitcode, output) 而不是像 Python 3.3.3 及更早的版本那样返回 (status, output)。 exitcode 的值与 returncode 相同。

subprocess.getoutput(cmd)

返回在 shell 中执行 cmd 产生的输出(stdout 和 stderr)。

类似于 getstatusoutput(),但退出码会被忽略并且返回值为包含命令输出的字符串。 例如:

>>> subprocess.getoutput('ls /bin/ls')
'/bin/ls'

可用性: POSIX 和 Windows。

在 3.3.4 版更改: 添加了 Windows 支持

备注

在 Windows 上将参数列表转换为一个字符串

在 Windows 上,args 序列会被转换为可使用以下规则来解析的字符串(对应于 MS C 运行时所使用的规则):

  1. 参数以空白符分隔,即空格符或制表符。
  2. 用双引号标示的字符串会被解读为单个参数,而不再考虑其中的空白符。 一个参数可以嵌套用引号标示的字符串。
  3. 带有一个反斜杠前缀的双引号会被解读为双引号字面值。
  4. 反斜杠会按字面值解读,除非它是作为双引号的前缀。
  5. 如果反斜杠被作为双引号的前缀,则每个反斜杠对会被解读为一个反斜杠字面值。 如果反斜杠数量为奇数,则最后一个反斜杠会如规则 3 所描述的那样转义下一个双引号。

网络和进程间通信

本章介绍的模块提供了网络和进程间通信的机制。

某些模块仅适用于同一台机器上的两个进程,例如 signalmmap 。 其他模块支持两个或多个进程可用于跨机器通信的网络协议。

本章中描述的模块列表是:

  • asyncio —- 异步 I/O
  • socket —- 底层网络接口
  • ssl —- 套接字对象的 TLS/SSL 包装器
  • select —- 等待 I/O 完成
  • selectors —- 高级 I/O 复用库
  • asyncore —- 异步套接字处理器
  • asynchat —- 异步套接字指令/响应处理程序
  • signal —- 设置异步事件处理程序
  • mmap —- 内存映射文件支持

asyncio —- 异步 I/O

Hello World!

import asyncio
async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')
# Python 3.7+
asyncio.run(main())

asyncio 是用来编写 并发 代码的库,使用 async/await 语法。

asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。

asyncio 往往是构建 IO 密集型和高层级 结构化 网络代码的最佳选择。

asyncio 提供一组 高层级 API 用于:

  • 并发地 运行 Python 协程 并对其执行过程实现完全控制;
  • 执行 网络 IO 和 IPC;
  • 控制 子进程;
  • 通过 队列 实现分布式任务;
  • 同步 并发代码;

此外,还有一些 低层级 API 以支持 库和框架的开发者 实现:

  • 创建和管理 事件循环,以提供异步 API 用于 网络化, 运行 子进程,处理 OS 信号 等等;
  • 使用 transports 实现高效率协议;
  • 通过 async/await 语法 桥接 基于回调的库和代码。

参考

注解

asyncio 的源代码可以在 Lib/asyncio/ 中找到。

socket —- 底层网络接口

源代码: Lib/socket.py


这个模块提供了访问 BSD 套接字 的接口。在所有现代 Unix 系统、Windows、macOS 和其他一些平台上可用。

注解

一些行为可能因平台不同而异,因为调用的是操作系统的套接字API。

这个Python接口是用Python的面向对象风格对Unix系统调用和套接字库接口的直译:函数 socket() 返回一个 套接字对象 ,其方法是对各种套接字系统调用的实现。形参类型一般与C接口相比更高级:例如在Python文件 read()write() 操作中,接收操作的缓冲区分配是自动的,发送操作的缓冲区长度是隐式的。

套接字协议族

根据系统以及构建选项,此模块提供了各种套接字协议簇。

特定的套接字对象需要的地址格式将根据此套接字对象被创建时指定的地址族被自动选择。套接字地址表示如下:

  • 一个绑定在文件系统节点上的 AF_UNIX 套接字的地址表示为一个字符串,使用文件系统字符编码和 'surrogateescape' 错误回调方法(see PEP 383)。一个地址在 Linux 的抽象命名空间被返回为带有初始的 null 字节的 字节类对象 ;注意在这个命名空间种的套接字可能与普通文件系统套接字通信,所以打算运行在 Linux 上的程序可能需要解决两种地址类型。当传递为参数时,一个字符串或字节类对象可以用于任一类型的地址。

    在 3.3 版更改: 之前,AF_UNIX 套接字路径被假设使用 UTF-8 编码。

    在 3.5 版更改: 现在接受可写的 字节类对象。

  • 一对 (host, port) 被用作 AF_INET 地址族,其中 host 是一个表示互联网域名标记形式的主机名例如 'daring.cwi.nl' 或者 IPv4 地址例如 '100.50.200.5' 的字符串,而 port 是一个整数值。

    • 对于 IPv4 地址,有两种可接受的特殊形式被用来代替一个主机地址: '' 代表 INADDR_ANY,用来绑定到所有接口;字符串 '<broadcast>' 代表 INADDR_BROADCAST。此行为不兼容 IPv6,因此,如果你的 Python 程序打算支持 IPv6,则可能需要避开这些。
  • 对于 AF_INET6 地址族,使用一个四元组 (host, port, flowinfo, scope_id),其中 flowinfoscope_id 代表了 C 库 struct sockaddr_in6 中的 sin6_flowinfosin6_scope_id 成员。对于 socket 模块中的方法, flowinfoscope_id 可以被省略,只为了向后兼容。注意,省略 scope_id 可能会导致操作带有领域 (Scope) 的 IPv6 地址时出错。

    在 3.7 版更改: 对于多播地址(其 scope_id 起作用),地址 中可以不包含 %scope_id (或 zone id )部分,这部分是多余的,可以放心省略(推荐)。

  • AF_NETLINK 套接字由一对 (pid, groups) 表示。

  • 指定 AF_TIPC 地址族可以使用仅 Linux 支持的 TIPC 协议。TIPC 是一种开放的、非基于 IP 的网络协议,旨在用于集群计算环境。其地址用元组表示,其中的字段取决于地址类型。一般元组形式为 (addr_type, v1, v2, v3 [, scope]),其中:

    • addr_typeTIPC_ADDR_NAMESEQTIPC_ADDR_NAMETIPC_ADDR_ID 中的一个。

    • scopeTIPC_ZONE_SCOPETIPC_CLUSTER_SCOPETIPC_NODE_SCOPE 中的一个。

    • 如果 addr_typeTIPC_ADDR_NAME,那么 v1 是服务器类型,v2 是端口标识符,v3 应为 0。

      如果 addr_typeTIPC_ADDR_NAMESEQ,那么 v1 是服务器类型,v2 是端口号下限,而 v3 是端口号上限。

      如果 addr_typeTIPC_ADDR_ID,那么 v1 是节点 (node),v2 是 ref,v3 应为 0。

  • AF_CAN 地址族使用元组 (interface, ),其中 interface 是表示网络接口名称的字符串,如 'can0'。网络接口名 '' 可以用于接收本族所有网络接口的数据包。

    • CAN_ISOTP 协议接受一个元组 (interface, rx_addr, tx_addr),其中两个额外参数都是无符号长整数,都表示 CAN 标识符(标准或扩展标识符)。
    • CAN_J1939 协议接受一个元组 (interface, name, pgn, addr),其中额外参数有:表示 ECU 名称的 64 位无符号整数,表示参数组号 (Parameter Group Number, PGN) 的 32 位无符号整数,以及表示地址的 8 位整数。
  • PF_SYSTEM 协议簇的 SYSPROTO_CONTROL 协议接受一个字符串或元组 (id, unit)。其中字符串是内核控件的名称,该控件使用动态分配的 ID。而如果 ID 和内核控件的单元 (unit) 编号都已知,或使用了已注册的 ID,可以采用元组。

    3.3 新版功能.

  • AF_BLUETOOTH 支持以下协议和地址格式:

    • BTPROTO_L2CAP 接受 (bdaddr, psm),其中 bdaddr 为字符串格式的蓝牙地址,psm 是一个整数。

    • BTPROTO_RFCOMM 接受 (bdaddr, channel),其中 bdaddr 为字符串格式的蓝牙地址,channel 是一个整数。

    • BTPROTO_HCI 接受 (device_id,),其中 device_id 为整数或字符串,它表示接口对应的蓝牙地址(具体取决于你的系统,NetBSD 和 DragonFlyBSD 需要蓝牙地址字符串,其他系统需要整数)。

      在 3.2 版更改: 添加了对 NetBSD 和 DragonFlyBSD 的支持。

    • BTPROTO_SCO 接受 bdaddr,其中 bdaddrbytes 对象,其中含有字符串格式的蓝牙地址(如 b'12:23:34:45:56:67' ),FreeBSD 不支持此协议。

  • AF_ALG 是一个仅 Linux 可用的、基于套接字的接口,用于连接内核加密算法。算法套接字可用包括 2 至 4 个元素的元组来配置 (type, name [, feat [, mask]]),其中:

    • type 是表示算法类型的字符串,如 aeadhashskcipherrng
    • name 是表示算法类型和操作模式的字符串,如 sha256hmac(sha256)cbc(aes)drbg_nopr_ctr_aes256
    • featmask 是无符号 32 位整数。

    Availability: Linux 2.6.38, some algorithm types require more recent Kernels.

    3.6 新版功能.

  • AF_VSOCK 用于支持虚拟机与宿主机之间的通讯。该套接字用 (CID, port) 元组表示,其中 Context ID (CID) 和 port 都是整数。

    Availability: Linux >= 4.8 QEMU >= 2.8 ESX >= 4.0 ESX Workstation >= 6.5.

    3.7 新版功能.

  • AF_PACKET 是一个底层接口,直接连接至网卡。数据包使用元组 (ifname, proto[, pkttype[, hatype[, addr]]]) 表示,其中:

    • ifname - 指定设备名称的字符串。
    • proto - 一个用网络字节序表示的整数,指定以太网协议版本号。
    • pkttype - 指定数据包类型的整数(可选):
      • PACKET_HOST (默认) - 寻址到本地主机的数据包。
      • PACKET_BROADCAST - 物理层广播的数据包。
      • PACKET_MULTIHOST - 发送到物理层多播地址的数据包。
      • PACKET_OTHERHOST - 被(处于混杂模式的)网卡驱动捕获的、发送到其他主机的数据包。
      • PACKET_OUTGOING - 来自本地主机的、回环到一个套接字的数据包。
    • hatype - 可选整数,指定 ARP 硬件地址类型。
    • addr - 可选的类字节串对象,用于指定硬件物理地址,其解释取决于各设备。
  • AF_QIPCRTR 是一个仅 Linux 可用的、基于套接字的接口,用于与高通平台中协处理器上运行的服务进行通信。该地址簇用一个 (node, port) 元组表示,其中 nodeport 为非负整数。

    3.8 新版功能.

  • IPPROTO_UDPLITE 是一种 UDP 的变体,允许指定数据包的哪一部分计算入校验码内。它添加了两个可以修改的套接字选项。self.setsockopt(IPPROTO_UDPLITE, UDPLITE_SEND_CSCOV, length) 修改传出数据包的哪一部分计算入校验码内,而 self.setsockopt(IPPROTO_UDPLITE, UDPLITE_RECV_CSCOV, length) 将过滤掉计算入校验码的数据太少的数据包。在这两种情况下,length 都应在 range(8, 2**16, 8) 范围内。

    对于 IPv4,应使用 socket(AF_INET, SOCK_DGRAM, IPPROTO_UDPLITE) 来构造这样的套接字;对于 IPv6,应使用 socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDPLITE) 来构造这样的套接字。

    Availability: Linux >= 2.6.20, FreeBSD >= 10.1-RELEASE

    3.9 新版功能.

如果你在 IPv4/v6 套接字地址的 host 部分中使用了一个主机名,此程序可能会表现不确定行为,因为 Python 使用 DNS 解析返回的第一个地址。套接字地址在实际的 IPv4/v6 中以不同方式解析,根据 DNS 解析和/或 host 配置。为了确定行为,在 host 部分中使用数字的地址。

所有的错误都抛出异常。对于无效的参数类型和内存溢出异常情况可能抛出普通异常;从 Python 3.3 开始,与套接字或地址语义有关的错误抛出 OSError 或它的子类之一(常用 socket.error)。

可以用 setblocking() 设置非阻塞模式。一个基于超时的 generalization 通过 settimeout() 支持。

模块内容

socket 模块包含下列元素。

异常

exception socket.error

一个被弃用的 OSError 的别名。

在 3.3 版更改: 根据 PEP 3151,这个类是 OSError 的别名。

exception socket.herror

OSError 的子类,本异常通常表示与地址相关的错误,比如那些在 POSIX C API 中使用了 h_errno 的函数,包括 gethostbyname_ex()gethostbyaddr()。附带的值是一对 (h_errno, string),代表库调用返回的错误。h_errno 是一个数字,而 string 表示 h_errno 的描述,它们由 C 函数 hstrerror() 返回。

在 3.3 版更改: 此类是 OSError 的子类。

exception socket.gaierror

OSError 的子类,本异常来自 getaddrinfo()getnameinfo(),表示与地址相关的错误。附带的值是一对 (error, string),代表库调用返回的错误。string 表示 error 的描述,它由 C 函数 gai_strerror() 返回。数字值 error 与本模块中定义的 EAI_* 常量之一匹配。

在 3.3 版更改: 此类是 OSError 的子类。

exception socket.timeout

TimeoutError 的已被弃用的别名。

OSError 的子类,当套接字发生超时,且事先已调用过 settimeout() (或隐式地通过 setdefaulttimeout() )启用了超时,则会抛出此异常。附带的值是一个字符串,其值总是 “timed out”。

在 3.3 版更改: 此类是 OSError 的子类。

在 3.10 版更改: 这个类是 TimeoutError 的别名。

常量

AF_* 和 SOCK_* 常量现在都在 AddressFamilySocketKind 这两个 IntEnum 集合内。

3.4 新版功能.

socket.AF_UNIX
socket.AF_INET
socket.AF_INET6

这些常量表示地址(和协议)簇,用于 socket() 的第一个参数。如果 AF_UNIX 常量未定义,即表示不支持该协议。不同系统可能会有更多其他常量可用。

socket.SOCK_STREAM
socket.SOCK_DGRAM
socket.SOCK_RAW
socket.SOCK_RDM
socket.SOCK_SEQPACKET

这些常量表示套接字类型,用于 socket() 的第二个参数。不同系统可能会有更多其他常量可用。(一般只有 SOCK_STREAMSOCK_DGRAM 可用)

socket.SOCK_CLOEXEC
socket.SOCK_NONBLOCK

这两个常量(如果已定义)可以与上述套接字类型结合使用,允许你设置这些原子性相关的 flags (从而避免可能的竞争条件和单独调用的需要)。

参见

Secure File Descriptor Handling (安全地处理文件描述符) 提供了更详尽的解释。

可用性: Linux >= 2.6.27。

3.2 新版功能.

SO_*
socket.SOMAXCONN
MSG_*
SOL_*
SCM_*
IPPROTO_*
IPPORT_*
INADDR_*
IP_*
IPV6_*
EAI_*
AI_*
NI_*
TCP_*

此列表内的许多常量,记载在 Unix 文档中的套接字和/或 IP 协议部分,同时也定义在本 socket 模块中。它们通常用于套接字对象的 setsockopt()getsockopt() 方法的参数中。在大多数情况下,仅那些在 Unix 头文件中有定义的符号会在本模块中定义,部分符号提供了默认值。

在 3.6 版更改: 添加了 SO_DOMAIN, SO_PROTOCOL, SO_PEERSEC, SO_PASSSEC, TCP_USER_TIMEOUT, TCP_CONGESTION

在 3.6.5 版更改: 在 Windows 上,如果 Windows 运行时支持,则 TCP_FASTOPENTCP_KEEPCNT 可用。

在 3.7 版更改: 添加了 TCP_NOTSENT_LOWAT

在 Windows 上,如果 Windows 运行时支持,则 TCP_KEEPIDLETCP_KEEPINTVL 可用。

在 3.10 版更改: 添加了 IP_RECVTOS。 还添加了 TCP_KEEPALIVE。 这个常量在 MacOS 上可以与在 Linux 上使用 TCP_KEEPIDLE 的相同方式被使用。

socket.AF_CAN
socket.PF_CAN
SOL_CAN_*
CAN_*

此列表内的许多常量,记载在 Linux 文档中,同时也定义在本 socket 模块中。

可用性: Linux >= 2.6.25。

3.3 新版功能.

socket.CAN_BCM
CAN_BCM_*

CAN 协议簇内的 CAN_BCM 是广播管理器(Bbroadcast Manager — BCM)协议,广播管理器常量在 Linux 文档中有所记载,在本 socket 模块中也有定义。

可用性: Linux >= 2.6.25。

注解

CAN_BCM_CAN_FD_FRAME 旗标仅在 Linux >= 4.8 时可用。

3.4 新版功能.

socket.CAN_RAW_FD_FRAMES

在 CAN_RAW 套接字中启用 CAN FD 支持,默认是禁用的。它使应用程序可以发送 CAN 和 CAN FD 帧。但是,从套接字读取时,也必须同时接受 CAN 和 CAN FD 帧。

此常量在 Linux 文档中有所记载。

可用性: Linux >= 3.6。

3.5 新版功能.

socket.CAN_RAW_JOIN_FILTERS

加入已应用的 CAN 过滤器,这样只有与所有 CAN 过滤器匹配的 CAN 帧才能传递到用户空间。

此常量在 Linux 文档中有所记载。

可用性: Linux >= 4.1。

3.9 新版功能.

socket.CAN_ISOTP

CAN 协议簇中的 CAN_ISOTP 就是 ISO-TP (ISO 15765-2) 协议。ISO-TP 常量在 Linux 文档中有所记载。

可用性: Linux >= 2.6.25。

3.7 新版功能.

socket.CAN_J1939

CAN 协议族中的 CAN_J1939 即 SAE J1939 协议。 J1939 常量记录在 Linux 文档中。

可用性: Linux >= 5.4。

3.9 新版功能.

socket.AF_PACKET
socket.PF_PACKET
PACKET_*

此列表内的许多常量,记载在 Linux 文档中,同时也定义在本 socket 模块中。

可用性: Linux >= 2.2。

socket.AF_RDS
socket.PF_RDS
socket.SOL_RDS
RDS_*

此列表内的许多常量,记载在 Linux 文档中,同时也定义在本 socket 模块中。

可用性: Linux >= 2.6.30。

3.3 新版功能.

socket.SIO_RCVALL
socket.SIO_KEEPALIVE_VALS
socket.SIO_LOOPBACK_FAST_PATH
RCVALL_*

Windows 的 WSAIoctl() 的常量。这些常量用于套接字对象的 ioctl() 方法的参数。

在 3.6 版更改: 添加了 SIO_LOOPBACK_FAST_PATH

TIPC_*

TIPC 相关常量,与 C socket API 导出的常量一致。更多信息请参阅 TIPC 文档。

socket.AF_ALG
socket.SOL_ALG
ALG_*

用于 Linux 内核加密算法的常量。

可用性: Linux >= 2.6.38。

3.6 新版功能.

socket.AF_VSOCK
socket.IOCTL_VM_SOCKETS_GET_LOCAL_CID
VMADDR*
SO_VM*

用于 Linux 宿主机/虚拟机通讯的常量。

可用性: Linux >= 4.8。

3.7 新版功能.

socket.AF_LINK

Availability: BSD, macOS.

3.4 新版功能.

socket.has_ipv6

本常量为一个布尔值,该值指示当前平台是否支持 IPv6。

socket.BDADDR_ANY
socket.BDADDR_LOCAL

这些是字符串常量,包含蓝牙地址,这些地址具有特殊含义。例如,当用 BTPROTO_RFCOMM 指定绑定套接字时, BDADDR_ANY 表示“任何地址”。

socket.HCI_FILTER
socket.HCI_TIME_STAMP
socket.HCI_DATA_DIR

BTPROTO_HCI 一起使用。 HCI_FILTER 在 NetBSD 或 DragonFlyBSD 上不可用。 HCI_TIME_STAMPHCI_DATA_DIR 在 FreeBSD、NetBSD 或 DragonFlyBSD 上不可用。

socket.AF_QIPCRTR

高通 IPC 路由协议的常数,用于与提供远程处理器的服务进行通信。

可用性: Linux >= 4.7。

函数

创建套接字

下列函数都能创建 套接字对象.

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

使用给定的地址族、套接字类型和协议号创建一个新的套接字。 地址族应为 AF_INET (默认值), AF_INET6, AF_UNIX, AF_CAN, AF_PACKETAF_RDS 之一。 套接字类型应为 SOCK_STREAM (默认值), SOCK_DGRAM, SOCK_RAW 或其他可能的 SOCK_ 常量之一。 协议号通常为零并且可以省略,或在协议族为 AF_CAN 的情况下,协议应为 CAN_RAW, CAN_BCM, CAN_ISOTPCAN_J1939 之一。

如果指定了 fileno,那么将从这一指定的文件描述符中自动检测 *family、*typeproto 的值。如果调用本函数时显式指定了 familytypeproto 参数,可以覆盖自动检测的值。这只会影响 Python 表示诸如 socket.getpeername() 一类函数的返回值的方式,而不影响实际的操作系统资源。与 socket.fromfd() 不同,fileno 将返回原先的套接字,而不是复制出新的套接字。这有助于在分离的套接字上调用 socket.close() 来关闭它。

新创建的套接字是 不可继承的。

引发一个 审计事件 socket.__new__ 附带参数 selffamilytypeprotocol

在 3.3 版更改: 添加了 AF_CAN 簇。添加了 AF_RDS 簇。

在 3.4 版更改: 添加了 CAN_BCM 协议。

在 3.4 版更改: 返回的套接字现在是不可继承的。

在 3.7 版更改: 添加了 CAN_ISOTP 协议。

在 3.7 版更改: 当将 SOCK_NONBLOCKSOCK_CLOEXEC 标志位应用于 type 上时,它们会被清除,且 socket.type 反映不出它们。但它们仍将传递给底层系统的 socket() 调用。因此,

sock = socket.socket(
    socket.AF_INET,
    socket.SOCK_STREAM | socket.SOCK_NONBLOCK)

仍将在支持 SOCK_NONBLOCK 的系统上创建一个非阻塞的套接字,但是 sock.type 会被置为 socket.SOCK_STREAM

在 3.9 版更改: 添加了 CAN_J1939 协议。

在 3.10 版更改: 添加了 IPPROTO_MPTCP 协议。

socket.socketpair([family[, type[, proto]]])

构建一对已连接的套接字对象,使用给定的地址簇、套接字类型和协议号。地址簇、套接字类型和协议号与上述 socket() 函数相同。默认地址簇为 AF_UNIX (需要当前平台支持,不支持则默认为 AF_INET )。

新创建的套接字都是 不可继承的。

在 3.2 版更改: 现在,返回的套接字对象支持全部套接字 API,而不是全部 API 的一个子集。

在 3.4 版更改: 返回的套接字现在都是不可继承的。

在 3.5 版更改: 添加了 Windows 支持。

socket.create_connection(address[, timeout[, source_address]])

连接到一个在互联网 address (以 (host, port) 2 元组表示) 上侦听的 TCP 服务,并返回套接字对象。 这是一个相比 socket.connect() 层级更高的函数:如果 host 是非数字的主机名,它将尝试将其解析为 AF_INETAF_INET6,然后依次尝试连接到所有可能的地址直到连接成功。 这使编写兼容 IPv4 和 IPv6 的客户端变得很容易。

传入可选参数 timeout 可以在套接字实例上设置超时(在尝试连接前)。如果未提供 timeout,则使用由 getdefaulttimeout() 返回的全局默认超时设置。

如果提供了 source_address,它必须为二元组 (host, port),以便套接字在连接之前绑定为其源地址。如果 host 或 port 分别为 ‘’ 或 0,则使用操作系统默认行为。

在 3.2 版更改: 添加了source_address 参数

socket.create_server(address, **, family=AF_INET, backlog=None, reuse_port=False, dualstack_ipv6=False*)

便捷函数,创建绑定到 address (二元组 (host, port) )的 TCP 套接字,返回套接字对象。

family 应设置为 AF_INETAF_INET6backlog 是传递给 socket.listen() 的队列大小,当它为 0 则表示默认的合理值。reuse_port 表示是否设置 SO_REUSEPORT 套接字选项。

如果 dualstack_ipv6 为 true 且平台支持,则套接字能接受 IPv4 和 IPv6 连接,否则将抛出 ValueError 异常。大多数 POSIX 平台和 Windows 应该支持此功能。启用此功能后,socket.getpeername() 在进行 IPv4 连接时返回的地址将是一个(映射到 IPv4 的)IPv6 地址。在默认启用该功能的平台上(如 Linux),如果 dualstack_ipv6 为 false,即显式禁用此功能。该参数可以与 has_dualstack_ipv6() 结合使用:

import socket
addr = ("", 8080)  # all interfaces, port 8080
if socket.has_dualstack_ipv6():
    s = socket.create_server(addr, family=socket.AF_INET6, dualstack_ipv6=True)
else:
    s = socket.create_server(addr)

注解

在 POSIX 平台上,设置 SO_REUSEADDR 套接字选项是为了立即重用以前绑定在同一 address 上并保持 TIME_WAIT 状态的套接字。

3.8 新版功能.

socket.has_dualstack_ipv6()

如果平台支持创建 IPv4 和 IPv6 连接都可以处理的 TCP 套接字,则返回 True

3.8 新版功能.

socket.fromfd(fd, family, type, proto=0)

复制文件描述符 fd (一个由文件对象的 fileno() 方法返回的整数),然后从结果中构建一个套接字对象。地址簇、套接字类型和协议号与上述 socket() 函数相同。文件描述符应指向一个套接字,但不会专门去检查——如果文件描述符是无效的,则对该对象的后续操作可能会失败。本函数很少用到,但是在将套接字作为标准输入或输出传递给程序(如 Unix inet 守护程序启动的服务器)时,可以使用本函数获取或设置套接字选项。套接字将处于阻塞模式。

新创建的套接字是 不可继承的。

在 3.4 版更改: 返回的套接字现在是不可继承的。

socket.fromshare(data)

根据 socket.share() 方法获得的数据实例化套接字。套接字将处于阻塞模式。

可用性: Windows。

3.3 新版功能.

socket.SocketType

这是一个 Python 类型对象,表示套接字对象的类型。它等同于 type(socket(...))

其他功能

socket 模块还提供多种网络相关服务:

socket.close(fd)

关闭一个套接字文件描述符。它类似于 os.close(),但专用于套接字。在某些平台上(特别是在 Windows 上),os.close() 对套接字文件描述符无效。

3.7 新版功能.

socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)

host/port 参数转换为 5 元组的序列,其中包含创建(连接到某服务的)套接字所需的所有参数。host 是域名,是字符串格式的 IPv4/v6 地址或 Noneport 是字符串格式的服务名称,如 'http' 、端口号(数字)或 None。传入 None 作为 hostport 的值,相当于将 NULL 传递给底层 C API。

可以指定 familytypeproto 参数,以缩小返回的地址列表。向这些参数分别传入 0 表示保留全部结果范围。flags 参数可以是 AI_* 常量中的一个或多个,它会影响结果的计算和返回。例如,AI_NUMERICHOST 会禁用域名解析,此时如果 host 是域名,则会抛出错误。

本函数返回一个列表,其中的 5 元组具有以下结构:

(family, type, proto, canonname, sockaddr)

在这些元组中,family, type, proto 都是整数且其作用是被传入 socket() 函数。 如果 AI_CANONNAMEflags 参数的一部分则 canonname 将是表示 host 规范名称的字符串;否则 canonname 将为空。 sockaddr 是一个描述套接字地址的元组,其具体格式取决于返回的 family (对于 AF_INET(address, port) 2 元组,对于 AF_INET6 则为 (address, port, flowinfo, scope_id) 4 元组),其作用是被传入 socket.connect() 方法。

引发一个 审计事件 socket.getaddrinfo 附带参数 hostportfamilytypeprotocol

下面的示例获取了 TCP 连接地址信息,假设该连接通过 80 端口连接至 example.org (如果系统未启用 IPv6,则结果可能会不同):

>>> socket.getaddrinfo("example.org", 80, proto=socket.IPPROTO_TCP)
[(<AddressFamily.AF_INET6: 10>, <AddressFamily.SOCK_STREAM: 1>,
 6, '', ('2606:2800:220:1:248:1893:25c8:1946', 80, 0, 0)),
 (<AddressFamily.AF_INET: 2>, <AddressFamily.SOCK_STREAM: 1>,
 6, '', ('93.184.216.34', 80))]

在 3.2 版更改: 现在可以使用关键字参数的形式来传递参数。

在 3.7 版更改: 对于 IPv6 多播地址,表示地址的字符串将不包含 %scope_id 部分。

socket.getfqdn([name])

Return a fully qualified domain name for name. If name is omitted or empty, it is interpreted as the local host. To find the fully qualified name, the hostname returned by gethostbyaddr() is checked, followed by aliases for the host, if available. The first name which includes a period is selected. In case no fully qualified domain name is available and name was provided, it is returned unchanged. If name was empty or equal to '0.0.0.0', the hostname from gethostname() is returned.

socket.gethostbyname(hostname)

将主机名转换为 IPv4 地址格式。IPv4 地址以字符串格式返回,如 '100.50.200.5'。如果主机名本身是 IPv4 地址,则原样返回。更完整的接口请参考 gethostbyname_ex()gethostbyname() 不支持 IPv6 名称解析,应使用 getaddrinfo() 来支持 IPv4/v6 双协议栈。

引发一个 审计事件 socket.gethostbyname,附带参数 hostname

socket.gethostbyname_ex(hostname)

Translate a host name to IPv4 address format, extended interface. Return a triple (hostname, aliaslist, ipaddrlist) where hostname is the host’s primary host name, aliaslist is a (possibly empty) list of alternative host names for the same address, and ipaddrlist is a list of IPv4 addresses for the same interface on the same host (often but not always a single address). gethostbyname_ex() does not support IPv6 name resolution, and getaddrinfo() should be used instead for IPv4/v6 dual stack support.

引发一个 审计事件 socket.gethostbyname,附带参数 hostname

socket.gethostname()

返回一个字符串,包含当前正在运行 Python 解释器的机器的主机名。

引发一个 审计事件 socket.gethostname,没有附带参数。

注意: gethostname() 并不总是返回全限定域名,必要的话请使用 getfqdn()

socket.gethostbyaddr(ip_address)

返回三元组 (hostname, aliaslist, ipaddrlist),其中 hostname 是响应给定 ip_address 的主要主机名,aliaslist 是相同地址的其他可用主机名的列表(可能为空),而 ipaddrlist 是 IPv4/v6 地址列表,包含相同主机名、相同接口的不同地址(很可能仅包含一个地址)。要查询全限定域名,请使用函数 getfqdn()gethostbyaddr() 支持 IPv4 和 IPv6。

引发一个 审计事件 socket.gethostbyaddr,附带参数 ip_address

socket.getnameinfo(sockaddr, flags)

将套接字地址 sockaddr 转换为二元组 (host, port)host 的形式可能是全限定域名,或是由数字表示的地址,具体取决于 flags 的设置。同样,port 可以包含字符串格式的端口名称或数字格式的端口号。

对于 IPv6 地址,如果 sockaddr 包含有意义的 scope_id,则 %scope_id 会被附加到主机部分。 这种情况通常发生在多播地址上。

引发一个 审计事件 socket.getnameinfo,附带参数 sockaddr

socket.getprotobyname(protocolname)

将一个互联网协议名称 (如 'icmp') 转换为能被作为 (可选的) 第三个参数传给 socket() 函数的常量。 这通常仅对以 “raw” 模式 (SOCK_RAW) 打开的套接字来说是必要的;对于正常的套接字模式,当该协议名称被省略或为零时会自动选择正确的协议。

socket.getservbyname(servicename[, protocolname])

将一个互联网服务名称和协议名称转换为该服务的端口号。 如果给出了可选的协议名称,它应为 'tcp''udp',否则将匹配任意的协议。

引发一个 审计事件 socket.getservbyname,附带参数 servicenameprotocolname

socket.getservbyport(port[, protocolname])

将一个互联网端口号和协议名称转换为该服务的服务名称。 如果给出了可选的协议名称,它应为 'tcp''udp',否则将匹配任意的协议。

引发一个 审计事件 socket.getservbyport,附带参数 portprotocolname

socket.ntohl(x)

将 32 位正整数从网络字节序转换为主机字节序。在主机字节序与网络字节序相同的计算机上,这是一个空操作。字节序不同将执行 4 字节交换操作。

socket.ntohs(x)

将 16 位正整数从网络字节序转换为主机字节序。在主机字节序与网络字节序相同的计算机上,这是一个空操作。字节序不同将执行 2 字节交换操作。

在 3.10 版更改: 如果 x 不能转为 16 位无符号整数则会引发 OverflowError

socket.htonl(x)

将 32 位正整数从主机字节序转换为网络字节序。在主机字节序与网络字节序相同的计算机上,这是一个空操作。字节序不同将执行 4 字节交换操作。

socket.htons(x)

将 16 位正整数从主机字节序转换为网络字节序。在主机字节序与网络字节序相同的计算机上,这是一个空操作。字节序不同将执行 2 字节交换操作。

在 3.10 版更改: 如果 x 不能转为 16 位无符号整数则会引发 OverflowError

socket.inet_aton(ip_string)

将 IPv4 地址从点分十进制字符串格式(如 ‘123.45.67.89’ )转换为 32 位压缩二进制格式,转换后为字节对象,长度为四个字符。与那些使用标准 C 库,且需要 struct in_addr 类型的对象的程序交换信息时,此功能很有用。 该类型即此函数返回的 32 位压缩二进制的 C 类型。

inet_aton() 也接受句点数少于三的字符串

如果传入本函数的 IPv4 地址字符串无效,则抛出 OSError。注意,具体什么样的地址有效取决于 inet_aton() 的底层 C 实现。

inet_aton() 不支持 IPv6,在 IPv4/v6 双协议栈下应使用 inet_pton() 来代替。

socket.inet_ntoa(packed_ip)

将 32 位压缩 IPv4 地址(一个 类字节对象,长 4 个字节)转换为标准的点分十进制字符串形式(如 ‘123.45.67.89’ )。与那些使用标准 C 库,且需要 struct in_addr 类型的对象的程序交换信息时,本函数很有用。 该类型即是本函数参数中的 32 位压缩二进制数据的 C 类型。

如果传入本函数的字节序列长度不是 4 个字节,则抛出 OSErrorinet_ntoa() 不支持 IPv6,在 IPv4/v6 双协议栈下应使用 inet_ntop() 来代替。

在 3.5 版更改: 现在接受可写的 字节类对象。

socket.inet_pton(address_family, ip_string)

将特定地址簇的 IP 地址(字符串)转换为压缩二进制格式。当库或网络协议需要接受 struct in_addr 类型的对象(类似 inet_aton() )或 struct in6_addr 类型的对象时,inet_pton() 很有用。

目前 address_family 支持 AF_INETAF_INET6。如果 IP 地址字符串 ip_string 无效,则抛出 OSError。注意,具体什么地址有效取决于 address_family 的值和 inet_pton() 的底层实现。

可用性: Unix(可能非所有平台都可用)、Windows。

在 3.4 版更改: 添加了 Windows 支持

socket.inet_ntop(address_family, packed_ip)

将压缩 IP 地址(一个 类字节对象,数个字节长)转换为标准的、特定地址簇的字符串形式(如 '7.10.0.5''5aef:2b::8' )。当库或网络协议返回 struct in_addr 类型的对象(类似 inet_ntoa() )或 struct in6_addr 类型的对象时,inet_ntop() 很有用。

目前 address_family 支持 AF_INETAF_INET6。如果字节对象 packed_ip 与指定的地址簇长度不符,则抛出 ValueError。针对 inet_ntop() 调用的错误则抛出 OSError

可用性: Unix(可能非所有平台都可用)、Windows。

在 3.4 版更改: 添加了 Windows 支持

在 3.5 版更改: 现在接受可写的 字节类对象。

socket.CMSG_LEN(length)

返回给定 length 所关联数据的辅助数据项的总长度(不带尾部填充)。此值通常用作 recvmsg() 接收一个辅助数据项的缓冲区大小,但是 RFC 3542 要求可移植应用程序使用 CMSG_SPACE(),以此将尾部填充的空间计入,即使该项在缓冲区的最后。如果 length 超出允许范围,则抛出 OverflowError

可用性: 大多数 Unix 平台,其他平台也可能可用。

3.3 新版功能.

socket.CMSG_SPACE(length)

返回 recvmsg() 所需的缓冲区大小,以接收给定 length 所关联数据的辅助数据项,带有尾部填充。接收多个项目所需的缓冲区空间是关联数据长度的 CMSG_SPACE() 值的总和。如果 length 超出允许范围,则抛出 OverflowError

请注意,某些系统可能支持辅助数据,但不提供本函数。还需注意,如果使用本函数的结果来设置缓冲区大小,可能无法精确限制可接收的辅助数据量,因为可能会有其他数据写入尾部填充区域。

可用性: 大多数 Unix 平台,其他平台也可能可用。

3.3 新版功能.

socket.getdefaulttimeout()

返回用于新套接字对象的默认超时(以秒为单位的浮点数)。值 None 表示新套接字对象没有超时。首次导入 socket 模块时,默认值为 None

socket.setdefaulttimeout(timeout)

设置用于新套接字对象的默认超时(以秒为单位的浮点数)。首次导入 socket 模块时,默认值为 None。可能的取值及其各自的含义请参阅 settimeout()

socket.sethostname(name)

将计算机的主机名设置为 name。如果权限不足将抛出 OSError

引发一个 审计事件 socket.sethostname,附带参数 name

可用性: Unix。

3.3 新版功能.

socket.if_nameindex()

返回一个列表,包含网络接口(网卡)信息二元组(整数索引,名称字符串)。系统调用失败则抛出 OSError

可用性: Unix, Windows。

3.3 新版功能.

在 3.8 版更改: 添加了 Windows 支持。

注解

在 Windows 中网络接口在不同上下文中具有不同的名称(所有名称见对应示例):

  • UUID: {FB605B73-AAC2-49A6-9A2F-25416AEA0573}
  • 名称: ethernet_32770
  • 友好名称: vEthernet (nat)
  • 描述: Hyper-V Virtual Ethernet Adapter

此函数返回列表中第二种形式的名称,在此示例中为 ethernet_32770

socket.if_nametoindex(if_name)

返回网络接口名称相对应的索引号。如果没有所给名称的接口,则抛出 OSError

可用性: Unix, Windows。

3.3 新版功能.

在 3.8 版更改: 添加了 Windows 支持。

参见

“Interface name” 为 if_nameindex() 中所描述的名称。

socket.if_indextoname(if_index)

返回网络接口索引号相对应的接口名称。如果没有所给索引号的接口,则抛出 OSError

可用性: Unix, Windows。

3.3 新版功能.

在 3.8 版更改: 添加了 Windows 支持。

参见

“Interface name” 为 if_nameindex() 中所描述的名称。

socket.send_fds(sock, buffers, fds[, flags[, address]])

将文件描述符列表 fds 通过一个 AF_UNIX 套接字 sock 进行发送。 fds 形参是由文件描述符构成的序列。 请查看 sendmsg() 获取这些形参的文档。

可用性: 支持 sendmsg()SCM_RIGHTS 机制的 Unix 系统。

3.9 新版功能.

socket.recv_fds(sock, bufsize, maxfds[, flags])

接收至多 maxfds 个来自 AF_UNIX 套接字 sock 的文件描述符。 返回 (msg, list(fds), flags, addr)。 请查看 recvmsg() 获取有些形参的文档。

可用性: 支持 recvmsg()SCM_RIGHTS 机制的 Unix 系统。

3.9 新版功能.

注解

位于文件描述符列表末尾的任何被截断整数。

套接字对象

套接字对象具有以下方法。除了 makefile(),其他都与套接字专用的 Unix 系统调用相对应。

在 3.2 版更改: 添加了对 上下文管理器 协议的支持。退出上下文管理器与调用 close() 等效。

socket.accept()

接受一个连接。此 socket 必须绑定到一个地址上并且监听连接。返回值是一个 (conn, address) 对,其中 conn 是一个 的套接字对象,用于在此连接上收发数据,address 是连接另一端的套接字所绑定的地址。

新创建的套接字是 不可继承的。

在 3.4 版更改: 该套接字现在是不可继承的。

在 3.5 版更改: 如果系统调用被中断,但信号处理程序没有触发异常,此方法现在会重试系统调用,而不是触发 InterruptedError 异常 (原因详见 PEP 475)。

socket.bind(address)

将套接字绑定到 address*。套接字必须尚未绑定。( *address 的格式取决于地址簇 —— 参见上文)

引发一个 审计事件 socket.bind,附带参数 selfaddress

socket.close()

将套接字标记为关闭。当 makefile() 创建的所有文件对象都关闭时,底层系统资源(如文件描述符)也将关闭。一旦上述情况发生,将来对套接字对象的所有操作都会失败。对端将接收不到任何数据(清空队列数据后)。

垃圾回收时,套接字会自动关闭,但建议显式 close() 它们,或在它们周围使用 with 语句。

在 3.6 版更改: 现在,如果底层的 close() 调用出错,会抛出 OSError

注解

close() 释放与连接相关联的资源,但不一定立即关闭连接。如果需要及时关闭连接,请在调用 close() 之前调用 shutdown()

socket.connect(address)

连接到 address 处的远程套接字。( address 的格式取决于地址簇 —— 参见上文)

如果连接被信号中断,则本方法将等待直至连接完成,或者如果信号处理句柄未引发异常并且套接字被阻塞或已超时则会在超时后引发 TimeoutError。 对于非阻塞型套接字,如果连接被信号中断则本方法将引发 InterruptedError 异常(或信号处理句柄所引发的异常)。

引发一个 审计事件 socket.connect,附带参数 selfaddress

在 3.5 版更改: 本方法现在将等待,直到连接完成,而不是在以下情况抛出 InterruptedError 异常。该情况为,连接被信号中断,信号处理程序未抛出异常,且套接字阻塞中或已超时(具体解释请参阅 PEP 475 )。

socket.connect_ex(address)

类似于 connect(address),但是对于 C 级别的 connect() 调用返回的错误,本函数将返回错误指示器,而不是抛出异常(对于其他问题,如“找不到主机”,仍然可以抛出异常)。如果操作成功,则错误指示器为 0,否则为 errno 变量的值。这对支持如异步连接很有用。

引发一个 审计事件 socket.connect,附带参数 selfaddress

socket.detach()

将套接字对象置于关闭状态,而底层的文件描述符实际并不关闭。返回该文件描述符,使其可以重新用于其他目的。

3.2 新版功能.

socket.dup()

创建套接字的副本。

新创建的套接字是 不可继承的。

在 3.4 版更改: 该套接字现在是不可继承的。

socket.fileno()

返回套接字的文件描述符(一个小整数),失败返回 -1。配合 select.select() 使用很有用。

在 Windows 下,此方法返回的小整数在允许使用文件描述符的地方无法使用(如 os.fdopen() )。Unix 无此限制。

socket.get_inheritable()

获取套接字文件描述符或套接字句柄的 可继承标志 :如果子进程可以继承套接字则为 True,否则为 False

3.4 新版功能.

socket.getpeername()

返回套接字连接到的远程地址。举例而言,这可以用于查找远程 IPv4/v6 套接字的端口号。(返回的地址格式取决于地址簇 —— 参见上文。)部分系统不支持此函数。

socket.getsockname()

返回套接字本身的地址。举例而言,这可以用于查找 IPv4/v6 套接字的端口号。(返回的地址格式取决于地址簇 —— 参见上文。)

socket.getsockopt(level, optname[, buflen])

返回指定套接字选项的值(参阅 Unix 手册页 getsockopt(2) )。所需的符号常量( SO_* 等)已定义在本模块中。如果未指定 buflen,则认为该选项值为整数,由本函数返回该整数值。如果指定 buflen,则它定义了用于存放选项值的缓冲区的最大长度,且该缓冲区将作为字节对象返回。对缓冲区的解码工作由调用者自行完成(针对编码为字节串的 C 结构,其解码方法请参阅可选的内置模块 struct )。

socket.getblocking()

如果套接字处于阻塞模式,返回 True,非阻塞模式返回 False

这相当于检测 socket.gettimeout() == 0

3.7 新版功能.

socket.gettimeout()

返回套接字操作相关的超时秒数(浮点数),未设置超时则返回 None。它反映最后一次调用 setblocking()settimeout() 后的设置。

socket.ioctl(control, option)

  • 平台

    Windows

ioctl() 方法是 WSAIoctl 系统接口的有限接口。请参考 Win32 文档 以获取更多信息。

在其他平台上,可以使用通用的 fcntl.fcntl()fcntl.ioctl() 函数,它们接受套接字对象作为第一个参数。

当前仅支持以下控制码: SIO_RCVALLSIO_KEEPALIVE_VALSSIO_LOOPBACK_FAST_PATH

在 3.6 版更改: 添加了 SIO_LOOPBACK_FAST_PATH

socket.listen([backlog])

启动一个服务器用于接受连接。如果指定 backlog,则它最低为 0(小于 0 会被置为 0),它指定系统允许暂未 accept 的连接数,超过后将拒绝新连接。未指定则自动设为合理的默认值。

在 3.5 版更改: backlog 参数现在是可选的。

socket.makefile(mode=’r’, buffering=None, **, encoding=None, errors=None, newline=None*)

返回与套接字关联的 文件对象。返回的对象的具体类型取决于 makefile() 的参数。这些参数的解释方式与内置的 open() 函数相同,其中 mode 的值仅支持 'r' (默认),'w''b'

套接字必须处于阻塞模式,它可以有超时,但是如果发生超时,文件对象的内部缓冲区可能会以不一致的状态结尾。

关闭 makefile() 返回的文件对象不会关闭原始套接字,除非所有其他文件对象都已关闭且在套接字对象上调用了 socket.close()

注解

在 Windows 上,由 makefile() 创建的文件类对象无法作为带文件描述符的文件对象使用,如无法作为 subprocess.Popen() 的流参数。

socket.recv(bufsize[, flags])

从套接字接收数据。返回值是一个字节对象,表示接收到的数据。bufsize 指定一次接收的最大数据量。可选参数 flags 的含义请参阅 Unix 手册页 *recv(2)*,它默认为零。

注解

为了最佳匹配硬件和网络的实际情况,bufsize 的值应为 2 的相对较小的幂,如 4096。

在 3.5 版更改: 如果系统调用被中断,但信号处理程序没有触发异常,此方法现在会重试系统调用,而不是触发 InterruptedError 异常 (原因详见 PEP 475)。

socket.recvfrom(bufsize[, flags])

从套接字接收数据。返回值是一对 (bytes, address),其中 bytes 是字节对象,表示接收到的数据,address 是发送端套接字的地址。可选参数 flags 的含义请参阅 Unix 手册页 recv(2)*,它默认为零。( *address 的格式取决于地址簇 —— 参见上文)

在 3.5 版更改: 如果系统调用被中断,但信号处理程序没有触发异常,此方法现在会重试系统调用,而不是触发 InterruptedError 异常 (原因详见 PEP 475)。

在 3.7 版更改: 对于多播 IPv6 地址,address 的第一项不会再包含 %scope_id 部分。 要获得完整的 IPv6 地址请使用 getnameinfo()

socket.recvmsg(bufsize[, ancbufsize[, flags]])

从套接字接收普通数据(至多 bufsize 字节)和辅助数据。ancbufsize 参数设置用于接收辅助数据的内部缓冲区的大小(以字节为单位),默认为 0,表示不接收辅助数据。可以使用 CMSG_SPACE()CMSG_LEN() 计算辅助数据缓冲区的合适大小,无法放入缓冲区的项目可能会被截断或丢弃。flags 参数默认为 0,其含义与 recv() 中的相同。

返回值是一个四元组: (data, ancdata, msg_flags, address)data 项是一个 bytes 对象,用于保存接收到的非辅助数据。ancdata 项是零个或多个元组 (cmsg_level, cmsg_type, cmsg_data) 组成的列表,表示接收到的辅助数据(控制消息):cmsg_levelcmsg_type 是分别表示协议级别和协议类型的整数,而 cmsg_data 是保存相关数据的 bytes 对象。msg_flags 项由各种标志按位或组成,表示接收消息的情况,详细信息请参阅系统文档。如果接收端套接字断开连接,则 address 是发送端套接字的地址(如果有),否则该值无指定。

某些系统上可以利用 AF_UNIX 套接字通过 sendmsg()recvmsg() 在进程之间传递文件描述符。使用此功能时(通常仅限于 SOCK_STREAM 套接字),recvmsg() 将在其辅助数据中返回以下格式的项 (socket.SOL_SOCKET, socket.SCM_RIGHTS, fds),其中 fds 是一个 bytes 对象,是新文件描述符表示为原生 C int 类型的二进制数组。如果 recvmsg() 在系统调用返回后抛出异常,它将首先关闭此机制接收到的所有文件描述符。

对于仅接收到一部分的辅助数据项,一些系统没有指示其截断长度。如果某个项目可能超出了缓冲区的末尾,recvmsg() 将发出 RuntimeWarning,并返回其在缓冲区内的部分,前提是该对象被截断于关联数据开始后。

在支持 SCM_RIGHTS 机制的系统上,下方的函数将最多接收 maxfds 个文件描述符,返回消息数据和包含描述符的列表(同时忽略意外情况,如接收到无关的控制消息)。

import socket, array
def recv_fds(sock, msglen, maxfds):
    fds = array.array("i")   # Array of ints
    msg, ancdata, flags, addr = sock.recvmsg(msglen, socket.CMSG_LEN(maxfds * fds.itemsize))
    for cmsg_level, cmsg_type, cmsg_data in ancdata:
        if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS:
            # Append data, ignoring any truncated integers at the end.
            fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
    return msg, list(fds)

可用性: 大多数 Unix 平台,其他平台也可能可用。

3.3 新版功能.

在 3.5 版更改: 如果系统调用被中断,但信号处理程序没有触发异常,此方法现在会重试系统调用,而不是触发 InterruptedError 异常 (原因详见 PEP 475)。

socket.recvmsg_into(buffers[, ancbufsize[, flags]])

从套接字接收普通数据和辅助数据,其行为与 recvmsg() 相同,但将非辅助数据分散到一系列缓冲区中,而不是返回新的字节对象。buffers 参数必须是可迭代对象,它迭代出可供写入的缓冲区(如 bytearray 对象),这些缓冲区将被连续的非辅助数据块填充,直到数据全部写完或缓冲区用完为止。在允许使用的缓冲区数量上,操作系统可能会有限制( sysconf()SC_IOV_MAX 值)。ancbufsizeflags 参数的含义与 recvmsg() 中的相同。

返回值为四元组: (nbytes, ancdata, msg_flags, address),其中 nbytes 是写入缓冲区的非辅助数据的字节总数,而 ancdatamsg_flagsaddressrecvmsg() 中的相同。

示例:

>>> import socket
>>> s1, s2 = socket.socketpair()
>>> b1 = bytearray(b'----')
>>> b2 = bytearray(b'0123456789')
>>> b3 = bytearray(b'--------------')
>>> s1.send(b'Mary had a little lamb')
22
>>> s2.recvmsg_into([b1, memoryview(b2)[2:9], b3])
(22, [], 0, None)
>>> [b1, b2, b3]
[bytearray(b'Mary'), bytearray(b'01 had a 9'), bytearray(b'little lamb---')]

可用性: 大多数 Unix 平台,其他平台也可能可用。

3.3 新版功能.

socket.recvfrom_into(buffer[, nbytes[, flags]])

从套接字接收数据,将其写入 buffer 而不是创建新的字节串。返回值是一对 (nbytes, address),其中 nbytes 是收到的字节数,address 是发送端套接字的地址。可选参数 flags 的含义请参阅 Unix 手册页 recv(2)*,它默认为零。( *address 的格式取决于地址簇 —— 参见上文)

socket.recv_into(buffer[, nbytes[, flags]])

从套接字接收至多 nbytes 个字节,将其写入缓冲区而不是创建新的字节串。如果 nbytes 未指定(或指定为 0),则接收至所给缓冲区的最大可用大小。返回接收到的字节数。可选参数 flags 的含义请参阅 Unix 手册页 *recv(2)*,它默认为零。

socket.send(bytes[, flags])

发送数据给套接字。本套接字必须已连接到远程套接字。可选参数 flags 的含义与上述 recv() 中的相同。本方法返回已发送的字节数。应用程序要负责检查所有数据是否已发送,如果仅传输了部分数据,程序需要自行尝试传输其余数据。

在 3.5 版更改: 如果系统调用被中断,但信号处理程序没有触发异常,此方法现在会重试系统调用,而不是触发 InterruptedError 异常 (原因详见 PEP 475)。

socket.sendall(bytes[, flags])

发送数据给套接字。本套接字必须已连接到远程套接字。可选参数 flags 的含义与上述 recv() 中的相同。与 send() 不同,本方法持续从 bytes 发送数据,直到所有数据都已发送或发生错误为止。成功后会返回 None。出错后会抛出一个异常,此时并没有办法确定成功发送了多少数据。

在 3.5 版更改: 每次成功发送数据后,套接字超时不再重置。现在,套接字超时是发送所有数据的最大总持续时间。

在 3.5 版更改: 如果系统调用被中断,但信号处理程序没有触发异常,此方法现在会重试系统调用,而不是触发 InterruptedError 异常 (原因详见 PEP 475)。

socket.sendto(bytes, address)

socket.sendto(bytes, flags, address)

发送数据给套接字。本套接字不应连接到远程套接字,而应由 address 指定目标套接字。可选参数 flags 的含义与上述 recv() 中的相同。本方法返回已发送的字节数。( address 的格式取决于地址簇 —— 参见上文。)

引发一个 审计事件 socket.sendto,附带参数 selfaddress

在 3.5 版更改: 如果系统调用被中断,但信号处理程序没有触发异常,此方法现在会重试系统调用,而不是触发 InterruptedError 异常 (原因详见 PEP 475)。

socket.sendmsg(buffers[, ancdata[, flags[, address]]])

将普通数据和辅助数据发送给套接字,将从一系列缓冲区中收集非辅助数据,并将其拼接为一条消息。buffers 参数指定的非辅助数据应为可迭代的 字节类对象 (如 bytes 对象),在允许使用的缓冲区数量上,操作系统可能会有限制( sysconf()SC_IOV_MAX 值)。ancdata 参数指定的辅助数据(控制消息)应为可迭代对象,迭代出零个或多个 (cmsg_level, cmsg_type, cmsg_data) 元组,其中 cmsg_levelcmsg_type 是分别指定协议级别和协议类型的整数,而 cmsg_data 是保存相关数据的字节类对象。请注意,某些系统(特别是没有 CMSG_SPACE() 的系统)可能每次调用仅支持发送一条控制消息。flags 参数默认为 0,与 send() 中的含义相同。如果 address 指定为除 None 以外的值,它将作为消息的目标地址。返回值是已发送的非辅助数据的字节数。

在支持 SCM_RIGHTS 机制的系统上,下方的函数通过一个 AF_UNIX 套接字来发送文件描述符列表 fds

import socket, array
def send_fds(sock, msg, fds):
    return sock.sendmsg([msg], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", fds))])

可用性: 大多数 Unix 平台,其他平台也可能可用。

引发一个 审计事件 socket.sendmsg,附带参数 selfaddress

3.3 新版功能.

在 3.5 版更改: 如果系统调用被中断,但信号处理程序没有触发异常,此方法现在会重试系统调用,而不是触发 InterruptedError 异常 (原因详见 PEP 475)。

socket.sendmsg_afalg([msg, ]**, op[, iv[, assoclen[, flags*]]])

AF_ALG 套接字定制的 sendmsg() 版本。可为 AF_ALG 套接字设置模式、IV、AEAD 关联数据的长度和标志位。

可用性: Linux >= 2.6.38。

3.6 新版功能.

socket.sendfile(file, offset=0, count=None)

使用高性能的 os.sendfile 发送文件,直到达到文件的 EOF 为止,返回已发送的字节总数。file 必须是一个以二进制模式打开的常规文件对象。如果 os.sendfile 不可用(如 Windows)或 file 不是常规文件,将使用 send() 代替。offset 指示从哪里开始读取文件。如果指定了 count,它确定了要发送的字节总数,而不会持续发送直到达到文件的 EOF。返回时或发生错误时,文件位置将更新,在这种情况下,file.tell() 可用于确定已发送的字节数。套接字必须为 SOCK_STREAM 类型。不支持非阻塞的套接字。

3.5 新版功能.

socket.set_inheritable(inheritable)

设置套接字文件描述符或套接字句柄的 可继承标志。

3.4 新版功能.

socket.setblocking(flag)

设置套接字为阻塞或非阻塞模式:如果 flag 为 false,则将套接字设置为非阻塞,否则设置为阻塞。

本方法是某些 settimeout() 调用的简写:

  • sock.setblocking(True) 相当于 sock.settimeout(None)
  • sock.setblocking(False) 相当于 sock.settimeout(0.0)

在 3.7 版更改: 本方法不再对 socket.type 属性设置 SOCK_NONBLOCK 标志。

socket.settimeout(value)

为阻塞套接字的操作设置超时。value 参数可以是非负浮点数,表示秒,也可以是 None。如果赋为一个非零值,那么如果在操作完成前超过了超时时间 value,后续的套接字操作将抛出 timeout 异常。如果赋为 0,则套接字将处于非阻塞模式。如果指定为 None,则套接字将处于阻塞模式。

在 3.7 版更改: 本方法不再修改 socket.type 属性的 SOCK_NONBLOCK 标志。

socket.setsockopt(level, optname, value: int)

socket.setsockopt(level, optname, value: buffer)

socket.setsockopt(level, optname, None, optlen: int)

设置给定套接字选项的值(参阅 Unix 手册页 setsockopt(2) )。所需的符号常量( SO_* 等)已定义在本 socket 模块中。该值可以是整数、None 或表示缓冲区的 字节类对象。在后一种情况下,由调用者确保字节串中包含正确的数据位(关于将 C 结构体编码为字节串的方法,请参阅可选的内置模块 struct )。当 value 设置为 None 时,必须设置 optlen 参数。这相当于调用 setsockopt() C 函数时使用了 optval=NULLoptlen=optlen 参数。

在 3.5 版更改: 现在接受可写的 字节类对象。

在 3.6 版更改: 添加了 setsockopt(level, optname, None, optlen: int) 调用形式。

socket.shutdown(how)

关闭一半或全部的连接。如果 howSHUT_RD,则后续不再允许接收。如果 howSHUT_WR,则后续不再允许发送。如果 howSHUT_RDWR,则后续的发送和接收都不允许。

socket.share(process_id)

复制套接字,并准备将其与目标进程共享。目标进程必须以 process_id 形式提供。然后可以利用某种形式的进程间通信,将返回的字节对象传递给目标进程,还可以使用 fromshare() 在新进程中重新创建套接字。一旦本方法调用完毕,就可以安全地将套接字关闭,因为操作系统已经为目标进程复制了该套接字。

可用性: Windows。

3.3 新版功能.

注意此处没有 read()write() 方法,请使用不带 flags 参数的 recv()send() 来替代。

套接字对象还具有以下(只读)属性,这些属性与传入 socket 构造函数的值相对应。

socket.family

套接字的协议簇。

socket.type

套接字的类型。

socket.proto

套接字的协议。

关于套接字超时的说明

一个套接字对象可以处于以下三种模式之一:阻塞、非阻塞或超时。套接字默认以阻塞模式创建,但是可以调用 setdefaulttimeout() 来更改。

  • blocking mode (阻塞模式)中,操作将阻塞,直到操作完成或系统返回错误(如连接超时)。
  • non-blocking mode (非阻塞模式)中,如果操作无法立即完成,则操作将失败(不幸的是,不同系统返回的错误不同):位于 select 中的函数可用于了解套接字何时以及是否可以读取或写入。
  • timeout mode (超时模式)下,如果无法在指定的超时内完成操作(抛出 timeout 异常),或如果系统返回错误,则操作将失败。

注解

在操作系统层面上,超时模式 下的套接字在内部都设置为非阻塞模式。同时,阻塞和超时模式在文件描述符和套接字对象之间共享,这些描述符和对象均应指向同一个网络端点。如果,比如你决定使用套接字的 fileno(),这一实现细节可能导致明显的结果。

超时与 connect 方法

connect() 操作也受超时设置的约束,通常建议在调用 connect() 之前调用 settimeout(),或将超时参数直接传递给 create_connection()。但是,无论 Python 套接字超时设置如何,系统网络栈都有可能返回自带的连接超时错误。

超时与 accept 方法

如果 getdefaulttimeout() 的值不是 None,则 accept() 方法返回的套接字将继承该超时值。若是 None,返回的套接字行为取决于侦听套接字的设置:

  • 如果侦听套接字处于 阻塞模式超时模式,则 accept() 返回的套接字处于 阻塞模式
  • 如果侦听套接字处于 非阻塞模式,那么 accept() 返回的套接字是阻塞还是非阻塞取决于操作系统。如果要确保跨平台时的正确行为,建议手动覆盖此设置。

示例

以下是 4 个使用 TCP/IP 协议的最小示例程序:一台服务器,它将收到的所有数据原样返回(仅服务于一个客户端),还有一个使用该服务器的客户端。注意,服务器必须按序执行 socket(), bind(), listen(), accept() (可能需要重复执行 accept() 以服务多个客户端),而客户端仅需要按序执行 socket(), connect()。还须注意,服务器不在侦听套接字上发送 sendall()/recv(),而是在 accept() 返回的新套接字上发送。

前两个示例仅支持 IPv4。

# Echo server program
import socket
HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen(1)
    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        while True:
            data = conn.recv(1024)
            if not data: break
            conn.sendall(data)

# Echo client program
import socket
HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b'Hello, world')
    data = s.recv(1024)
print('Received', repr(data))

下两个例子与上两个很像,但是同时支持 IPv4 和 IPv6。 服务端将监听第一个可用的地址族(它本应同时监听两个)。 在大多数支持 IPv6 的系统上,IPv6 将有优先权并且服务端可能不会接受 IPv4 流量。 客户端将尝试连接到作为名称解析结果被返回的所有地址,并将流量发送给连接成功的第一个地址。

# Echo server program
import socket
import sys
HOST = None               # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC,
                              socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
    except OSError as msg:
        s = None
        continue
    try:
        s.bind(sa)
        s.listen(1)
    except OSError as msg:
        s.close()
        s = None
        continue
    break
if s is None:
    print('could not open socket')
    sys.exit(1)
conn, addr = s.accept()
with conn:
    print('Connected by', addr)
    while True:
        data = conn.recv(1024)
        if not data: break
        conn.send(data)


# Echo client program
import socket
import sys
HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = None
for res in socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_STREAM):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
    except OSError as msg:
        s = None
        continue
    try:
        s.connect(sa)
    except OSError as msg:
        s.close()
        s = None
        continue
    break
if s is None:
    print('could not open socket')
    sys.exit(1)
with s:
    s.sendall(b'Hello, world')
    data = s.recv(1024)
print('Received', repr(data))

下面的例子演示了如何在 Windows 上使用原始套接字编写一个非常简单的网络嗅探器。 这个例子需要管理员权限来修改接口:

import socket
# the public network interface
HOST = socket.gethostbyname(socket.gethostname())
# create a raw socket and bind it to the public interface
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
s.bind((HOST, 0))
# Include IP headers
s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
# receive all packages
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)
# receive a package
print(s.recvfrom(65565))
# disabled promiscuous mode
s.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

下面的例子演示了如何使用 socket 接口与采用原始套接字协议的 CAN 网络进行通信。 要改为通过广播管理器协议来使用 CAN,则要用以下方式打开一个 socket:

socket.socket(socket.AF_CAN, socket.SOCK_DGRAM, socket.CAN_BCM)

在绑定 (CAN_RAW) 或连接 (CAN_BCM) socket 之后,你将可以在 socket 对象上正常使用 socket.send() 以及 socket.recv() 操作(及同类操作)。

最后一个例子可能需要特别的权限:

import socket
import struct
# CAN frame packing/unpacking (see 'struct can_frame' in <linux/can.h>)
can_frame_fmt = "=IB3x8s"
can_frame_size = struct.calcsize(can_frame_fmt)
def build_can_frame(can_id, data):
    can_dlc = len(data)
    data = data.ljust(8, b'\x00')
    return struct.pack(can_frame_fmt, can_id, can_dlc, data)
def dissect_can_frame(frame):
    can_id, can_dlc, data = struct.unpack(can_frame_fmt, frame)
    return (can_id, can_dlc, data[:can_dlc])
# create a raw socket and bind it to the 'vcan0' interface
s = socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW)
s.bind(('vcan0',))
while True:
    cf, addr = s.recvfrom(can_frame_size)
    print('Received: can_id=%x, can_dlc=%x, data=%s' % dissect_can_frame(cf))
    try:
        s.send(cf)
    except OSError:
        print('Error sending CAN frame')
    try:
        s.send(build_can_frame(0x01, b'\x01\x02\x03'))
    except OSError:
        print('Error sending CAN frame')

多次运行一个示例,且每次执行之间等待时间过短,可能导致这个错误:

OSError: [Errno 98] Address already in use

这是因为前一次运行使套接字处于 TIME_WAIT 状态,无法立即重用。

要防止这种情况,需要设置一个 socket 标志 socket.SO_REUSEADDR:

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))

SO_REUSEADDR 标志告诉内核将处于 TIME_WAIT 状态的本地套接字重新使用,而不必等到固有的超时到期。

参见

关于套接字编程(C 语言)的介绍,请参阅以下文章:

  • An Introductory 4.3BSD Interprocess Communication Tutorial,作者 Stuart Sechrest
  • An Advanced 4.3BSD Interprocess Communication Tutorial,作者 Samuel J. Leffler et al,

两篇文章都在 UNIX 开发者手册,补充文档 1(第 PS1:7 和 PS1:8 节)中。那些特定于平台的参考资料,它们包含与套接字有关的各种系统调用,也是套接字语义细节的宝贵信息来源。对于 Unix,请参考手册页。对于 Windows,请参阅 WinSock(或 Winsock 2)规范。如果需要支持 IPv6 的 API,读者可能希望参考 RFC 3493,标题为 Basic Socket Interface Extensions for IPv6。

ssl —- 套接字对象的 TLS/SSL 包装器

源代码: Lib/ssl.py


This module provides access to Transport Layer Security (often known as “Secure Sockets Layer”) encryption and peer authentication facilities for network sockets, both client-side and server-side. This module uses the OpenSSL library. It is available on all modern Unix systems, Windows, macOS, and probably additional platforms, as long as OpenSSL is installed on that platform.

注解

某些行为可能与平台相关,因为调用了操作系统的套接字 API。已安装的OpenSSL 版本也可能会导致不同的行为。比如 TLSv 1.3 与 Open SSL 1.1.1 就不一样。

警告

在阅读 安全考量 前不要使用此模块。 这样做可能会导致虚假的安全感,因为ssl模块的默认设置不一定适合你的应用程序。

文档本文档记录ssl 模块的对象和函数;更多关于TLS,SSL,和证书的信息,请参阅下方的“详情”选项

本模块提供了一个类 ssl.SSLSocket,它派生自 socket.socket 类型,并提供类似套接字的包装器,也能够对通过带 SSL 套接字的数据进行加密和解密。 它支持一些额外方法例如 getpeercert(),该方法可从连接的另一端获取证书,还有 cipher(),该方法可获取安全连接所使用的密码。

对于更复杂的应用程序,ssl.SSLContext 类有助于管理设置项和证书,进而可以被使用 SSLContext.wrap_socket() 方法创建的 SSL 套接字继承。

在 3.5.3 版更改: 更新以支持和 OpenSSL 1.1.0 的链接

在 3.6 版更改: OpenSSL 0.9.8、1.0.0 和 1.0.1 已过时,将不再被支持。在 ssl 模块未来的版本中,最低需要 OpenSSL 1.0.2 或 1.1.0。

在 3.10 版更改: PEP 644 已经实现。ssl 模块需要 OpenSSL 1.1.1 以上版本的支持。

使用废弃的常量和函数会导致废弃警告。

方法、常量和异常处理

套接字创建

从 Python 3.2 和 2.7.9 开始,建议使用 SSLContext 实例的 SSLContext.wrap_socket() 来将套接字包装为 SSLSocket 对象。 辅助函数 create_default_context() 会返回一个新的带有安全默认设置的上下文。 旧的 wrap_socket() 函数已被弃用,因为它效率较差并且不支持服务器名称提示(SNI)和主机匹配。

客户端套接字实例,采用默认上下文和IPv4/IPv6双栈:

import socket
import ssl
hostname = 'www.python.org'
context = ssl.create_default_context()
with socket.create_connection((hostname, 443)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        print(ssock.version())

客户端套接字示例,带有自定义上下文和IPv4:

hostname = 'www.python.org'
# PROTOCOL_TLS_CLIENT requires valid cert chain and hostname
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations('path/to/cabundle.pem')
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        print(ssock.version())

服务器套接字实例,在localhost上监听IPv4:

context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.load_cert_chain('/path/to/certchain.pem', '/path/to/private.key')
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
    sock.bind(('127.0.0.1', 8443))
    sock.listen(5)
    with context.wrap_socket(sock, server_side=True) as ssock:
        conn, addr = ssock.accept()
        ...

上下文创建

便捷函数,可以帮助创建 SSLContext 对象,用于常见的目的。

ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)

返回一个新的 SSLContext 对象,使用给定 purpose 的默认设置。 该设置由 ssl 模块选择,并且通常是代表一个比直接调用 SSLContext 构造器时更高的安全等级。

cafile, capath, cadata 代表用于进行证书核验的可选受信任 CA 证书,与 SSLContext.load_verify_locations() 的一致。 如果三个参数均为 None,此函数可以转而选择信任系统的默认 CA 证书。

设置为: PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVEROP_NO_SSLv2OP_NO_SSLv3,带有不含 RC4 及未认证的高强度加密密码套件。 传入 SERVER_AUTH 作为 purpose,会将 verify_mode 设为 CERT_REQUIRED,并加载 CA 证书(若给出 *cafile、*capathcadata 之一)或用 SSLContext.load_default_certs() 加载默认CA证书。

keylog_filename 受支持并且设置了环境变量 SSLKEYLOGFILE 时,create_default_context() 会启用密钥日志记录。

注解

协议、选项、密码和其他设置可随时更改为更具约束性的值而无须事先弃用。 这些值代表了兼容性和安全性之间的合理平衡。

如果你的应用需要特定的设置,你应当创建一个 SSLContext 并自行应用设置。

注解

如果你发现当某些较旧的客户端或服务器尝试与用此函数创建的 SSLContext 进行连接时收到了报错提示 “Protocol or cipher suite mismatch”,这可能是因为它们只支持 SSL3.0 而它被此函数用 OP_NO_SSLv3 排除掉了。 SSL3.0 被广泛认为 完全不可用。 如果你仍希望继续使用此函数但仍允许 SSL 3.0 连接,你可以使用以下代码重新启用它们:

ctx = ssl.create_default_context(Purpose.CLIENT_AUTH)
ctx.options &= ~ssl.OP_NO_SSLv3

3.4 新版功能.

在 3.4.4 版更改: RC4 被从默认密码字符串中丢弃。

在 3.6 版更改: ChaCha20/Poly1305 被添加到默认密码字符串中。

3DES 被从默认密码字符串中丢弃。

在 3.8 版更改: 增加了对密钥日志记录至 SSLKEYLOGFILE 的支持。

在 3.10 版更改: 当前上下文使用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 协议而非通用的 PROTOCOL_TLS

异常

exception ssl.SSLError

引发此异常以提示来自下层 SSL 实现(目前由 OpenSSL 库提供)的错误。 它表示在下层网络连接之上叠加的高层级加密和验证层存在某种问题。 此错误是 OSError 的一个子类型。 SSLError 实例的错误和消息是由 OpenSSL 库提供的。

在 3.3 版更改: SSLError 曾经是 socket.error 的一个子类型。

  • library

    一个字符串形式的助记符,用来指明发生错误的 OpenSSL 子模块,例如 SSL, PEMX509。 可能的取值范围依赖于 OpenSSL 的版本。

    3.3 新版功能.

  • reason

    一个字符串形式的助记符,用来指明发生错误的原因,例如 CERTIFICATE_VERIFY_FAILED。 可能的取值范围依赖于 OpenSSL 的版本。

    3.3 新版功能.

exception ssl.SSLZeroReturnError

SSLError 的子类,当尝试读取或写入且 SSL 连接已被完全关闭时会被引发。 请注意这并不意味着下层的传输(读取 TCP)已被关闭。

3.3 新版功能.

exception ssl.SSLWantReadError

SSLError 的子类,当尝试读取或写入数据,但在请求被满足之前还需要在下层的 TCP 传输上接收更多数据时会被 非阻塞型 SSL 套接字 引发。

3.3 新版功能.

exception ssl.SSLWantWriteError

SSLError 的子类,当尝试读取或写入数据,但在请求被满足之前还需要在下层的 TCP 传输上发送更多数据时会被 非阻塞型 SSL 套接字 引发。

3.3 新版功能.

exception ssl.SSLSyscallError

SSLError 的子类,当尝试在 SSL 套接字上执行操作时遇到系统错误时会被引发。 不幸的是,没有简单的方式能检查原始 errno 编号。

3.3 新版功能.

exception ssl.SSLEOFError

SSLError 的子类,当 SSL 连接被突然终止时会被引发。 通常,当遇到此错误时你不应再尝试重用下层的传输。

3.3 新版功能.

exception ssl.SSLCertVerificationError

SSLError 的子类,当证书验证失败时会被引发。

3.7 新版功能.

  • verify_code

    一个数字形式的错误编号,用于表示验证错误。

  • verify_message

    用于表示验证错误的人类可读的字符串。

exception ssl.CertificateError

SSLCertVerificationError 的别名。

在 3.7 版更改: 此异常现在是 SSLCertVerificationError 的别名。

随机生成

ssl.RAND_bytes(num)

返回 num 个高加密强度伪随机字节数据。 如果 PRNG 未使用足够的数据作为随机种子或者如果当前 RAND 方法不支持该操作则会引发 SSLErrorRAND_status() 可被用来检查 PRNG 的状态而 RAND_add() 可被用来为 PRNG 设置随机种子。

对于几乎所有应用程序都更推荐使用 os.urandom()

请阅读维基百科文章 Cryptographically secure pseudorandom number generator (CSPRNG) 以了解对于高加密强度生成器的具体要求。

3.3 新版功能.

ssl.RAND_pseudo_bytes(num)

返回 (bytes, is_cryptographic): bytes 是 num 个伪随机字节数据,如果所生成的字节数据为高加密强度则 is_cryptographic 为 True。 如果当前 RAND 方法不支持此操作则会引发 SSLError

所生成的伪随机字节序列如果具有足够的长度则将会具有唯一性,并是并非不可预测。 它们可被用于非加密目的以及加密协议中的特定目的,但通常不可被用于密钥生成等目的。

对于几乎所有应用程序都更推荐使用 os.urandom()

3.3 新版功能.

3.6 版后已移除: OpenSSL 已弃用了 ssl.RAND_pseudo_bytes(),请改用 ssl.RAND_bytes()

ssl.RAND_status()

如果 SSL 伪随机数生成器已使用‘足够的’随机性作为种子则返回 True,否则返回 False。 你可以使用 ssl.RAND_egd()ssl.RAND_add() 来增加伪随机数生成器的随机性。

ssl.RAND_add(bytes, entropy)

将给定的 bytes 混合到 SSL 伪随机数生成器中。 形参 entropy (float 类型) 是数据所包含的熵的下界 (因此你可以总是使用 0.0)。 请查看 RFC 1750 了解有关熵源的更多信息。

在 3.5 版更改: 现在接受可写的 字节类对象。

证书处理

ssl.match_hostname(cert, hostname)

验证 cert (使用 SSLSocket.getpeercert() 所返回的已解码格式) 是否匹配给定的 hostname。 所应用的规则是在 RFC 2818, RFC 5280RFC 6125 中描述的检查 HTTPS 服务器身份的规则。 除了 HTTPS,此函数还应当适用于各种基于 SSL 协议的服务器身份检查操作,例如 FTPS, IMAPS, POPS 等等。

失败时引发 CertificateError。 成功时此函数无返回值:

>>> cert = {'subject': ((('commonName', 'example.com'),),)}
>>> ssl.match_hostname(cert, "example.com")
>>> ssl.match_hostname(cert, "example.org")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/py3k/Lib/ssl.py", line 130, in match_hostname
ssl.CertificateError: hostname 'example.org' doesn't match 'example.com'

3.2 新版功能.

在 3.3.3 版更改: 此函数现在遵循 RFC 6125, 6.4.3 小节,它不会匹配多个通配符 (例如 *.*.com*a*.example.org) 也不匹配国际化域名 (IDN) 片段内部的通配符。 IDN A 标签例如 www*.xn--pthon-kva.org 仍然受支持,但 x*.python.org 不再能匹配 xn--tda.python.org

在 3.5 版更改: 现在支持匹配存在于证书的 subjectAltName 字段中的 IP 地址。

在 3.7 版更改: 此函数不再被用于 TLS 连接。 主机匹配现在是由 OpenSSL 执行的。

允许位于段的最左端且为唯一字符的通配符。 部分通配符例如 www*.example.com 已不再受支持。

3.7 版后已移除.

ssl.cert_time_to_seconds(cert_time)

返回距离 Unix 纪元零时的秒数,给定的 cert_time 字符串代表来自证书的 “notBefore” 或 “notAfter” 日期值,采用 "%b %d %H:%M:%S %Y %Z" strptime 格式(C 区域)。

以下为示例代码:

>>> import ssl
>>> timestamp = ssl.cert_time_to_seconds("Jan  5 09:34:43 2018 GMT")
>>> timestamp  
1515144883
>>> from datetime import datetime
>>> print(datetime.utcfromtimestamp(timestamp))  
2018-01-05 09:34:43

“notBefore” 或 “notAfter” 日期值必须使用 GMT (RFC 5280)。

在 3.5 版更改: 将输入时间解读为 UTC 时间,基于输入字符串中指明的 ‘GMT’ 时区。 在之前使用的是本地时区。 返回一个整数(不带输入格式中秒的分数部分)

ssl.get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, ca_certs=None[, timeout])

给出一个受 SSL 保护的服务器的地址 addr,形式为 (hostname, port-number) ,获取该服务器的证书,并以 PEM 编码的字符串返回。如果指定了 ssl_version,则使用该版本的 SSL 协议尝试连接服务器。如果指定了 ca_certs,它应该是一个包含根证书列表的文件,与 SSLContext.wrap_socket() 中同名参数的格式相同。该调用将尝试根据该根证书集来验证服务器的证书,如果验证失败则调用失败。参数 timeout 可用于指定超时时间。

在 3.3 版更改: 此函数现在是 IPv6 兼容的。-compatible.

在 3.5 版更改: 默认的 ssl_versionPROTOCOL_SSLv3 改为 PROTOCOL_TLS 以保证与现代服务器的最大兼容性。

在 3.10 版更改: 加入 timeout 参数。

ssl.DER_cert_to_PEM_cert(DER_cert_bytes)

根据给定的 DER 编码字节块形式的证书,返回同一证书的 PEM 编码字符串版本。

ssl.PEM_cert_to_DER_cert(PEM_cert_string)

根据给定的 ASCII PEM 字符串形式的证书,返回同一证书的 DER 编码字节序列。

ssl.get_default_verify_paths()

返回包含 OpenSSL 的默认 cafile 和 capath 的路径的命名元组。 此路径与 SSLContext.set_default_verify_paths() 所使用的相同。 返回值是一个 named tuple DefaultVerifyPaths:

  • cafile - 解析出的 cafile 路径或者如果文件不存在则为 None,
  • capath - 解析出的 capath 路径或者如果目录不存在则为 None,
  • openssl_cafile_env - 指向一个 cafile 的 OpenSSL 环境键,
  • openssl_cafile - 一个 cafile 的硬编码路径,
  • openssl_capath_env - 指向一个 capath 的 OpenSSL 环境键,
  • openssl_capath - 一个 capath 目录的硬编码路径

可用性: LibreSSL 会忽略环境变量 openssl_cafile_envopenssl_capath_env

3.4 新版功能.

ssl.enum_certificates(store_name)

从 Windows 的系统证书库中检索证书。 store_name 可以是 CA, ROOTMY 中的一个。 Windows 也可能会提供额外的证书库。

此函数返回一个包含 (cert_bytes, encoding_type, trust) 元组的列表。 encoding_type 指明 cert_bytes 的编码格式。 它可以为 x509_asn 以表示 X.509 ASN.1 数据或是 pkcs_7_asn 以表示 PKCS#7 ASN.1 数据。 trust 以 OIDS 集合的形式指明证书的目的,或者如果证书对于所有目的都可以信任则为 True

示例:

>>> ssl.enum_certificates("CA")
[(b'data...', 'x509_asn', {'1.3.6.1.5.5.7.3.1', '1.3.6.1.5.5.7.3.2'}),
 (b'data...', 'x509_asn', True)]

可用性: Windows。

3.4 新版功能.

ssl.enum_crls(store_name)

Windows 的系统证书库中检索 CRL。 store_name 可以是 CA, ROOTMY 中的一个。 Windows 也可能会提供额外的证书库。

此函数返回一个包含 (cert_bytes, encoding_type, trust) 元组的列表。 encoding_type 指明 cert_bytes 的编码格式。 它可以为 x509_asn 以表示 X.509 ASN.1 数据或是 pkcs_7_asn 以表示 PKCS#7 ASN.1 数据。

可用性: Windows。

3.4 新版功能.

ssl.wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=CERT_NONE, ssl_version=PROTOCOL_TLS, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None)

接受一个 socket.socket 的实例 sock,并返回一个 ssl.SSLSocket 的实例,该类型是 socket.socket 的子类型,它将下层的套接字包装在一个 SSL 上下文中。 sock 必须是一个 SOCK_STREAM 套接字;其他套接字类型不被支持。

在内部,该函数会创建一个 SSLContext,其协议版本为 ssl_versionSSLContext.options 设为 cert_reqs*。 如果设置了 *keyfile, certfile, ca_certsciphers 等形参,则参数值会被传给 SSLContext.load_cert_chain(), SSLContext.load_verify_locations() 以及 SSLContext.set_ciphers()

参数 server_side, do_handshake_on_connectsuppress_ragged_eofs 具有与 SSLContext.wrap_socket() 相同的含义。

3.7 版后已移除: 从 Python 3.2 和 2.7.9 开始,建议使用 SSLContext.wrap_socket() 来代替 wrap_socket()。 模块级函数的功能受限并且将创建不安全的客户端套接字,不带服务器名称提示或主机名匹配。

常量

所有常量现在都是 enum.IntEnumenum.IntFlag 多项集的成员。

3.6 新版功能.

ssl.CERT_NONE

SSLContext.verify_modewrap_socket()cert_reqs 形参可能的取值。 PROTOCOL_TLS_CLIENT 除外,这是默认的模式。 对于客户端套接字,几乎任何证书都是可接受的。 验证错误例如不受信任或过期的证书错误会被忽略并且不会中止 TLS/SSL 握手。

在服务器模式下,不会从客户端请求任何证书,因此客户端不会发送任何用于客户端证书身份验证的证书。

参见下文对于 安全考量 的讨论。

ssl.CERT_OPTIONAL

SSLContext.verify_modewrap_socket()cert_reqs 形参可能的取值。 CERT_OPTIONAL 具有与 CERT_REQUIRED 相同的含义。 对于客户端套接字推荐改用 CERT_REQUIRED

在服务器模式下,客户端证书请求会被发送给客户端。 客户端可以忽略请求也可以发送一个证书以执行 TLS 客户端证书身份验证。 如果客户端选择发送证书,则将对其执行验证。 任何验证错误都将立即中止 TLS 握手。

使用此设置要求将一组有效的 CA 证书传递给 SSLContext.load_verify_locations() 或是作为 wrap_socket()ca_certs 形参值。

ssl.CERT_REQUIRED

SSLContext.verify_modewrap_socket()cert_reqs 形参可能的取值。 在此模式下,需要从套接字连接的另一端获取证书;如果未提供证书或验证失败则将引发 SSLError。 此模式 不能 在客户端模式下对证书进行验证,因为它不会匹配主机名。 check_hostname 也必须被启用以验证证书的真实性。 PROTOCOL_TLS_CLIENT 会使用 CERT_REQUIRED 并默认启用 check_hostname

对于服务器套接字,此模式会提供强制性的 TLS 客户端证书验证。 客户端证书请求会被发送给客户端并且客户端必须提供有效且受信任的证书。

使用此设置要求将一组有效的 CA 证书传递给 SSLContext.load_verify_locations() 或是作为 wrap_socket()ca_certs 形参值。

class ssl.VerifyMode

CERT_* 常量的 enum.IntEnum 多项集。

3.6 新版功能.

ssl.VERIFY_DEFAULT

SSLContext.verify_flags 可能的取值。 在此模式下,证书吊销列表(CRL)并不会被检查。 OpenSSL 默认不要求也不验证 CRL。

3.4 新版功能.

ssl.VERIFY_CRL_CHECK_LEAF

SSLContext.verify_flags 可能的取值。 在此模式下, 只会检查对等证书而不检查任何中间 CA 证书。 此模式要求提供由对等证书颁发者(其直接上级 CA)签名的有效 CRL。 如果未使用 SSLContext.load_verify_locations 加载正确的 CRL,则验证将失败。

3.4 新版功能.

ssl.VERIFY_CRL_CHECK_CHAIN

SSLContext.verify_flags 可能的取值。 在此模式下,会检查对等证书链中所有证书的 CRL。

3.4 新版功能.

ssl.VERIFY_X509_STRICT

SSLContext.verify_flags 可能的取值,用于禁用已损坏 X.509 证书的绕过操作。

3.4 新版功能.

ssl.VERIFY_ALLOW_PROXY_CERTS

SSLContext.verify_flags 的可能取值,启用代理证书验证。

3.10 新版功能.

ssl.VERIFY_X509_TRUSTED_FIRST

SSLContext.verify_flags 可能的取值。 它指示 OpenSSL 在构建用于验证某个证书的信任链时首选受信任的证书。 此旗标将默认被启用。

3.4.4 新版功能.

ssl.VERIFY_X509_PARTIAL_CHAIN

SSLContext.verify_flags 的可能取值。它指示 OpenSSL 接受信任存储中的中间 CA 作为信任锚,与自我签名的根 CA 证书的方式相同。这样就能信任中间 CA 颁发的证书,而不一定非要去信任其祖先的根 CA。

3.10 新版功能.

class ssl.VerifyFlags

VERIFY_* 常量的 enum.IntFlag 多项集。

3.6 新版功能.

ssl.PROTOCOL_TLS

选择客户端和服务器均支持的最高协议版本。 此选项名称并不准确,实际上 “SSL” 和 “TLS” 协议均可被选择。

3.6 新版功能.

3.10 版后已移除: TLS 客户端和服务器需要不同的默认设置来实现安全通信。通用的 TLS 协议常量已废弃,而采用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER

ssl.PROTOCOL_TLS_CLIENT

自动协商为客户端和服务器都支持的最高版本协议,并配置当前上下文客户端的连接。该协议默认启用 CERT_REQUIREDcheck_hostname

3.6 新版功能.

ssl.PROTOCOL_TLS_SERVER

自动协商为客户端和服务器都支持的最高版本协议,并配置上下文服务器端的连接。

3.6 新版功能.

ssl.PROTOCOL_SSLv23

PROTOCOL_TLS 的别名。

3.6 版后已移除: 请改用 PROTOCOL_TLS

ssl.PROTOCOL_SSLv2

选择 SSL 版本 2 作为通道加密协议。

如果 OpenSSL 编译时使用了 OPENSSL_NO_SSL2 旗标则此协议将不可用。

警告

SSL 版本 2 并不安全。 极不建议使用它。

3.6 版后已移除: OpenSSL 已经移除了对 SSLv2 的支持。

ssl.PROTOCOL_SSLv3

选择 SSL 版本 3 作为通道加密协议。

如果 OpenSSL 编译时使用了 OPENSSL_NO_SSLv3 旗标则此协议将不可用。

警告

SSL 版本 3 并不安全。 极不建议使用它。

3.6 版后已移除: OpenSSL 已经废弃了所有特定于版本的协议。请换用带有 SSLContext.minimum_versionSSLContext.maximum_version 的默认协议 PROTOCOL_TLS_SERVERPROTOCOL_TLS_CLIENT

ssl.PROTOCOL_TLSv1

选择 TLS 版本 1.0 作为通道加密协议。

3.6 版后已移除: OpenSSL 已经废弃了所有特定于版本的协议。

ssl.PROTOCOL_TLSv1_1

选择 TLS 版本 1.1 作为通道加密协议。 仅适用于 openssl 版本 1.0.1+。

3.4 新版功能.

3.6 版后已移除: OpenSSL 已经废弃了所有特定于版本的协议。

ssl.PROTOCOL_TLSv1_2

选用 TLS 1.2 版本作为隧道加密协议。只适用于 openssl 1.0.1 以上版本。

3.4 新版功能.

3.6 版后已移除: OpenSSL 已经废弃了所有特定于版本的协议。

ssl.OP_ALL

对存在于其他 SSL 实现中的各种缺陷启用绕过操作。 默认会设置此选项。 没有必要设置与 OpenSSL 的 SSL_OP_ALL 常量同名的旗标。

3.2 新版功能.

ssl.OP_NO_SSLv2

阻止 SSLv2 连接。 此选项仅可与 PROTOCOL_TLS 结合使用。 它会阻止对等方选择 SSLv2 作为协议版本。

3.2 新版功能.

3.6 版后已移除: SSLv2 已被弃用

ssl.OP_NO_SSLv3

阻止 SSLv3 连接。 此选项仅可与 PROTOCOL_TLS 结合使用。 它会阻止对等方选择 SSLv3 作为协议版本。

3.2 新版功能.

3.6 版后已移除: SSLv3 已被弃用

ssl.OP_NO_TLSv1

阻止 TLSv1 连接。 此选项仅可与 PROTOCOL_TLS 结合使用。 它会阻止对等方选择 TLSv1 作为协议版本。

3.2 新版功能.

3.7 版后已移除: 此选项自 OpenSSL 1.1.0 起已被弃用,请改用新的 SSLContext.minimum_versionSSLContext.maximum_version

ssl.OP_NO_TLSv1_1

阻止 TLSv1.1 连接。 此选项仅可与 PROTOCOL_TLS 结合使用。 它会阻止对等方选择 TLSv1.1 作为协议版本。 仅适用于 openssl 版本 1.0.1+。

3.4 新版功能.

3.7 版后已移除: 此选项自 OpenSSL 1.1.0 起已被弃用。

ssl.OP_NO_TLSv1_2

阻止 TLSv1.2 连接。 此选项仅可与 PROTOCOL_TLS 结合使用。 它会阻止对等方选择 TLSv1.2 作为协议版本。 仅适用于 openssl 版本 1.0.1+。

3.4 新版功能.

3.7 版后已移除: 此选项自 OpenSSL 1.1.0 起已被弃用。

ssl.OP_NO_TLSv1_3

阻止 TLSv1.3 连接。 此选项仅可与 PROTOCOL_TLS 结合使用。 它会阻止对等方选择 TLSv1.3 作为协议版本。 TLS 1.3 适用于 OpenSSL 1.1.1 或更新的版本。 当 Python 编译是基于较旧版本的 OpenSSL 时,该旗标默认为 0

3.7 新版功能.

3.7 版后已移除: 此选项自 OpenSSL 1.1.0 起已被弃用。 它被添加到 2.7.15, 3.6.3 和 3.7.0 是为了向下兼容 OpenSSL 1.0.2。

ssl.OP_NO_RENEGOTIATION

禁用所有 TLSv1.2 和更早版本的重协商操作。 不发送 HelloRequest 消息,并忽略通过 ClientHello 发起的重协商请求。

此选项仅适用于 OpenSSL 1.1.0h 及更新的版本。

3.7 新版功能.

ssl.OP_CIPHER_SERVER_PREFERENCE

使用服务器的密码顺序首选项,而不是客户端的首选项。 此选项在客户端套接字和 SSLv2 服务器套接字上无效。

3.3 新版功能.

ssl.OP_SINGLE_DH_USE

阻止对于单独的 SSL 会话重用相同的 DH 密钥。 这会提升前向保密性但需要更多的计算资源。 此选项仅适用于服务器套接字。

3.3 新版功能.

ssl.OP_SINGLE_ECDH_USE

阻止对于单独的 SSL 会话重用相同的 ECDH 密钥。 这会提升前向保密性但需要更多的计算资源。 此选项仅适用于服务器套接字。

3.3 新版功能.

ssl.OP_ENABLE_MIDDLEBOX_COMPAT

在 TLS 1.3 握手中发送虚拟更改密码规格(CCS)消息以使得 TLS 1.3 连接看起来更像是 TLS 1.2 连接。

此选项仅适用于 OpenSSL 1.1.1 及更新的版本。

3.8 新版功能.

ssl.OP_NO_COMPRESSION

在 SSL 通道上禁用压缩。 这适用于应用协议支持自己的压缩方案的情况。

3.3 新版功能.

class ssl.Options

OP_* 常量的 enum.IntFlag 多项集。

ssl.OP_NO_TICKET

阻止客户端请求会话凭据。

3.6 新版功能.

ssl.OP_IGNORE_UNEXPECTED_EOF

忽略 TLS 连接的意外关闭。

此选项仅适用于 OpenSSL 3.0.0 及更新的版本。

3.10 新版功能.

ssl.HAS_ALPN

OpenSSL 库是否具有对 RFC 7301 中描述的 应用层协议协商 TLS 扩展的内置支持。

3.5 新版功能.

ssl.HAS_NEVER_CHECK_COMMON_NAME

OpenSSL 库是否具有对不检测目标通用名称的内置支持且 SSLContext.hostname_checks_common_name 为可写状态。

3.7 新版功能.

ssl.HAS_ECDH

OpenSSL 库是否具有对基于椭圆曲线的 Diffie-Hellman 密钥交换的内置支持。 此常量应当为真值,除非发布者明确地禁用了此功能。

3.3 新版功能.

ssl.HAS_SNI

OpenSSL 库是否具有对 服务器名称提示 扩展(在 RFC 6066 中定义)的内置支持。

3.2 新版功能.

ssl.HAS_NPN

OpenSSL 库是否具有对 应用层协议协商 中描述的 下一协议协商 的内置支持。 当此常量为真值时,你可以使用 SSLContext.set_npn_protocols() 方法来公告你想要支持的协议。

3.3 新版功能.

ssl.HAS_SSLv2

OpenSSL 库是否具有对 SSL 2.0 协议的内置支持。

3.7 新版功能.

ssl.HAS_SSLv3

OpenSSL 库是否具有对 SSL 3.0 协议的内置支持。

3.7 新版功能.

ssl.HAS_TLSv1

OpenSSL 库是否具有对 TLS 1.0 协议的内置支持。

3.7 新版功能.

ssl.HAS_TLSv1_1

OpenSSL 库是否具有对 TLS 1.1 协议的内置支持。

3.7 新版功能.

ssl.HAS_TLSv1_2

OpenSSL 库是否具有对 TLS 1.2 协议的内置支持。

3.7 新版功能.

ssl.HAS_TLSv1_3

OpenSSL 库是否具有对 TLS 1.3 协议的内置支持。

3.7 新版功能.

ssl.CHANNEL_BINDING_TYPES

受支持的 TLS 通道绑定类型组成的列表。 此列表中的字符串可被用作传给 SSLSocket.get_channel_binding() 的参数。

3.3 新版功能.

ssl.OPENSSL_VERSION

解释器所加载的 OpenSSL 库的版本字符串:

>>> ssl.OPENSSL_VERSION
'OpenSSL 1.0.2k  26 Jan 2017'

3.2 新版功能.

ssl.OPENSSL_VERSION_INFO

代表 OpenSSL 库的版本信息的五个整数所组成的元组:

>>> ssl.OPENSSL_VERSION_INFO
(1, 0, 2, 11, 15)

3.2 新版功能.

ssl.OPENSSL_VERSION_NUMBER

OpenSSL 库的原始版本号,以单个整数表示:

>>> ssl.OPENSSL_VERSION_NUMBER
268443839
>>> hex(ssl.OPENSSL_VERSION_NUMBER)
'0x100020bf'

3.2 新版功能.

ssl.ALERT_DESCRIPTION_HANDSHAKE_FAILURE
ssl.ALERT_DESCRIPTION_INTERNAL_ERROR
ALERT_DESCRIPTION_*

来自 RFC 5246 等文档的警报描述。 IANA TLS Alert Registry 中包含了这个列表及对定义其含义的 RFC 引用。

被用作 SSLContext.set_servername_callback() 中的回调函数的返回值。

3.4 新版功能.

class ssl.AlertDescription

ALERT_DESCRIPTION_* 常量的 enum.IntEnum 多项集。

3.6 新版功能.

Purpose.SERVER_AUTH

用于 create_default_context()SSLContext.load_default_certs() 的参数。表示上下文可用于验证网络服务器(因此,它将被用于创建客户端套接字)。

3.4 新版功能.

Purpose.CLIENT_AUTH

用于 create_default_context()SSLContext.load_default_certs() 的参数。 表示上下文可用于验证网络客户(因此,它将被用于创建服务器端套接字)。

3.4 新版功能.

class ssl.SSLErrorNumber

SSL_ERROR_* 常量的 enum.IntEnum 多项集。

3.6 新版功能.

class ssl.TLSVersion

SSLContext.maximum_versionSSLContext.minimum_version 中的 SSL 和 TLS 版本的 enum.IntEnum 多项集。

3.7 新版功能.

TLSVersion.MINIMUM_SUPPORTED
TLSVersion.MAXIMUM_SUPPORTED

受支持的最低和最高 SSL 或 TLS 版本。 这些常量被称为魔术常量。 它们的值并不反映可用的最低和最高 TLS/SSL 版本。

TLSVersion.SSLv3
TLSVersion.TLSv1
TLSVersion.TLSv1_1
TLSVersion.TLSv1_2
TLSVersion.TLSv1_3

SSL 3.0 至 TLS 1.3。

3.10 版后已移除: 所有 TLSVersion 成员,除 TLSVersion.TLSv1_2TLSVersion.TLSv1_3 之外均已废弃。

SSL 套接字

class ssl.SSLSocket(socket.socket)

SSL 套接字提供了 套接字对象 的下列方法:

  • accept()
  • bind()
  • close()
  • connect()
  • detach()
  • fileno()
  • getpeername(), getsockname()
  • getsockopt(), setsockopt()
  • gettimeout(), settimeout(), setblocking()
  • listen()
  • makefile()
  • recv(), recv_into() (but passing a non-zero flags argument is not allowed)
  • send(), sendall() (with the same limitation)
  • sendfile() (but os.sendfile will be used for plain-text sockets only, else send() will be used)
  • shutdown()

但是,由于 SSL(和 TLS)协议在 TCP 之上具有自己的框架,因此 SSL 套接字抽象在某些方面可能与常规的 OS 层级套接字存在差异。

SSLSocket 的实例必须使用 SSLContext.wrap_socket() 方法来创建。

在 3.5 版更改: 新增了 sendfile() 方法。

在 3.5 版更改: shutdown() 不会在每次接收或发送字节数据后重置套接字超时。 现在套接字超时为关闭的最大总持续时间。

3.6 版后已移除: 直接创建 SSLSocket 实例的做法已被弃用,请使用 SSLContext.wrap_socket() 来包装套接字。

在 3.7 版更改: SSLSocket 的实例必须使用 wrap_socket() 来创建。 在较早的版本中,直接创建实例是可能的。 但这从未被记入文档或是被正式支持。

在 3.10 版更改: Python 内部现在使用 SSL_read_exSSL_write_ex。这些函数支持读取和写入大于 2GB 的数据。写入零长数据不再出现违反协议的错误。

SSL 套接字还具有下列方法和属性:

SSLSocket.read(len=1024, buffer=None)

从 SSL 套接字读取至多 len 个字节的数据并将结果作为 bytes 实例返回。 如果指定了 buffer,则改为读取到缓冲区,并返回所读取的字节数。

如果套接字为 非阻塞型 则会引发 SSLWantReadErrorSSLWantWriteError 且读取将阻塞。

由于在任何时候重新协商都是可能的,因此调用 read() 也可能导致写入操作。

在 3.5 版更改: 套接字超时在每次接收或发送字节数据后不会再被重置。 现在套接字超时为读取至多 len 个字节数据的最大总持续时间。

3.6 版后已移除: 请使用 recv() 来代替 read()

SSLSocket.write(buf)

buf 写入到 SSL 套接字并返回所写入的字节数。 buf 参数必须为支持缓冲区接口的对象。

如果套接字为 非阻塞型 则会引发 SSLWantReadErrorSSLWantWriteError 且读取将阻塞。

由于在任何时候重新协商都是可能的,因此调用 write() 也可能导致读取操作。

在 3.5 版更改: 套接字超时在每次接收或发送字节数据后不会再被重置。 现在套接字超时为写入 buf 的最大总持续时间。

3.6 版后已移除: 请使用 send() 来代替 write()

注解

read()write() 方法是读写未加密的应用级数据,并将其解密/加密为带加密的线路级数据的低层级方法。 这些方法需要有激活的 SSL 连接,即握手已完成而 SSLSocket.unwrap() 尚未被调用。

通常你应当使用套接字 API 方法例如 recv()send() 来代替这些方法。

SSLSocket.do_handshake()

执行 SSL 设置握手。

在 3.4 版更改: 当套接字的 contextcheck_hostname 属性为真值时此握手方法还会执行 match_hostname()

在 3.5 版更改: 套接字超时在每次接收或发送字节数据时不会再被重置。 现在套接字超时为握手的最大总持续时间。

在 3.7 版更改: 主机名或 IP 地址会在握手期间由 OpenSSL 进行匹配。 函数 match_hostname() 将不再被使用。 在 OpenSSL 拒绝主机名和 IP 地址的情况下,握手将提前被中止并向对等方发送 TLS 警告消息。

SSLSocket.getpeercert(binary_form=False)

如果连接另一端的对等方没有证书,则返回 None。 如果 SSL 握手还未完成,则会引发 ValueError

如果 binary_form 形参为 False,并且从对等方接收到了证书,此方法将返回一个 dict 实例。 如果证书未通过验证,则字典将为空。 如果证书通过验证,它将返回由多个密钥组成的字典,其中包括 subject (证书颁发给的主体) 和 issuer (颁发证书的主体)。 如果证书包含一个 Subject Alternative Name 扩展的实例 (see RFC 3280),则字典中还将有一个 subjectAltName 键。

subjectissuer 字段都是包含在证书中相应字段的数据结构中给出的相对专有名称(RDN)序列的元组,每个 RDN 均为 name-value 对的序列。 这里是一个实际的示例:

{'issuer': ((('countryName', 'IL'),),
            (('organizationName', 'StartCom Ltd.'),),
            (('organizationalUnitName',
              'Secure Digital Certificate Signing'),),
            (('commonName',
              'StartCom Class 2 Primary Intermediate Server CA'),)),
 'notAfter': 'Nov 22 08:15:19 2013 GMT',
 'notBefore': 'Nov 21 03:09:52 2011 GMT',
 'serialNumber': '95F0',
 'subject': ((('description', '571208-SLe257oHY9fVQ07Z'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'California'),),
             (('localityName', 'San Francisco'),),
             (('organizationName', 'Electronic Frontier Foundation, Inc.'),),
             (('commonName', '*.eff.org'),),
             (('emailAddress', 'hostmaster@eff.org'),)),
 'subjectAltName': (('DNS', '*.eff.org'), ('DNS', 'eff.org')),
 'version': 3}

注解

要验证特定服务的证书,你可以使用 match_hostname() 函数。

如果 binary_form 形参为 True,并且提供了证书,此方法会将整个证书的 DER 编码形式作为字节序列返回,或者如果对等方未提供证书则返回 None。 对等方是否提供证书取决于 SSL 套接字的角色:

  • 对于客户端 SSL 套接字,服务器将总是提供证书,无论是否需要进行验证;
  • 对于服务器 SSL 套接字,客户端将仅在服务器要求时才提供证书;因此如果你使用了 CERT_NONE (而不是 CERT_OPTIONALCERT_REQUIRED) 则 getpeercert() 将返回 None

在 3.2 版更改: 返回的字典包括额外的条目例如 issuernotBefore

在 3.4 版更改: 如果握手未完成则会引发 ValueError。 返回的字典包括额外的 X509v3 扩展条目例如 crlDistributionPoints, caIssuersOCSP URI。

在 3.9 版更改: IPv6 地址字符串不再附带末尾换行符。

SSLSocket.cipher()

返回由三个值组成的元组,其中包含所使用的密码名称,定义其使用方式的 SSL 协议版本,以及所使用的加密比特位数。 如果尚未建立连接,则返回 None

SSLSocket.shared_ciphers()

返回在握手期间由客户端共享的密码列表。 所返回列表的每个条目都是由三个值组成的元组,其中包括密码名称,定义其使用方式的 SSL 协议版本,以及密码所使用的加密比特位数。 如果尚未建立连接或套接字为客户端套接字则 shared_ciphers() 将返回 None

3.5 新版功能.

SSLSocket.compression()

以字符串形式返回所使用的压缩算法,或者如果连接没有使用压缩则返回 None

如果高层级的协议支持自己的压缩机制,你可以使用 OP_NO_COMPRESSION 来禁用 SSL 层级的压缩。

3.3 新版功能.

SSLSocket.get_channel_binding(cb_type=’tls-unique’)

为当前连接获取字节串形式的通道绑定数据。 如果尚未连接或握手尚未完成则返回 None

cb_type 形参允许选择需要的通道绑定类型。 有效的通道绑定类型在 CHANNEL_BINDING_TYPES 列表中列出。 目前只支持由 RFC 5929 所定义的 ‘tls-unique’ 通道绑定。 如果请求了一个不受支持的通道绑定类型则将引发 ValueError

3.3 新版功能.

SSLSocket.selected_alpn_protocol()

返回在 TLS 握手期间所选择的协议。 如果 SSLContext.set_alpn_protocols() 未被调用,如果另一方不支持 ALPN,如果此套接字不支持任何客户端所用的协议,或者如果握手尚未发生,则将返回 None

3.5 新版功能.

SSLSocket.selected_npn_protocol()

返回在Return the higher-level protocol that was selected during the TLS/SSL 握手期间所选择的高层级协议。 如果 SSLContext.set_npn_protocols() 未被调用,或者如果另一方不支持 NPN,或者如果握手尚未发生,则将返回 None

3.3 新版功能.

3.10 版后已移除: NPN 已被 ALPN 取代。

SSLSocket.unwrap()

执行 SSL 关闭握手,这会从下层的套接字中移除 TLS 层,并返回下层的套接字对象。 这可被用来通过一个连接将加密操作转为非加密。 返回的套接字应当总是被用于同连接另一方的进一步通信,而不是原始的套接字。

SSLSocket.verify_client_post_handshake()

向一个 TLS 1.3 客户端请求握手后身份验证(PHA)。 只有在初始 TLS 握手之后且双方都启用了 PHA 的情况下才能为服务器端套接字的 TLS 1.3 连接启用 PHA。

此方法不会立即执行证书交换。 服务器端会在下一次写入事件期间发送 CertificateRequest 并期待客户端在下一次读取事件期间附带证书进行响应。

如果有任何前置条件未被满足(例如非 TLS 1.3,PHA 未启用),则会引发 SSLError

注解

仅在 OpenSSL 1.1.1 且 TLS 1.3 被启用时可用。 没有 TLS 1.3 支持,此方法将引发 NotImplementedError

3.8 新版功能.

SSLSocket.version()

以字符串形式返回由连接协商确定的实际 SSL 协议版本,或者如果未建立安全连接则返回 None。 在撰写本文档时,可能的返回值包括 "SSLv2", "SSLv3", "TLSv1", "TLSv1.1""TLSv1.2"。 最新的 OpenSSL 版本可能会定义更多的返回值。

3.5 新版功能.

SSLSocket.pending()

返回在连接上等待被读取的已解密字节数。

SSLSocket.context

此 SSL 套接字所联结的 SSLContext 对象。 如果 SSL 套接字是使用已弃用的 wrap_socket() 函数 (而非 SSLContext.wrap_socket()) 创建的,则这将是为此 SSL 套接字创建的自定义上下文对象。

3.2 新版功能.

SSLSocket.server_side

一个布尔值,对于服务器端套接字为 True 而对于客户端套接字则为 False

3.2 新版功能.

SSLSocket.server_hostname

服务器的主机名: str 类型,对于服务器端套接字或者如果构造器中未指定主机名则为 None

3.2 新版功能.

在 3.7 版更改: 现在该属性将始终为 ASCII 文本。 当 server_hostname 为一个国际化域名(IDN)时,该属性现在会保存为 A 标签形式 ("xn--pythn-mua.org") 而非 U 标签形式 ("pythön.org")。

SSLSocket.session

用于 SSL 连接的 SSLSession。 该会话将在执行 TLS 握手后对客户端和服务器端套接字可用。 对于客户端套接字该会话可以在调用 do_handshake() 之前被设置以重用一个会话。

3.6 新版功能.

SSLSocket.session_reused

3.6 新版功能.

SSL 上下文

3.2 新版功能.

SSL 上下文可保存各种比单独 SSL 连接寿命更长的数据,例如 SSL 配置选项,证书和私钥等。 它还可为服务器端套接字管理缓存,以加快来自相同客户端的重复连接。

class ssl.SSLContext(protocol=None)

创建一个新的 SSL 上下文。 你可以传入 protocol,它必须为此模块中定义的 PROTOCOL_* 常量之一。 该形参指定要使用哪个 SSL 协议版本。 通常,服务器会选择一个特定的协议版本,而客户端必须适应服务器的选择。 大多数版本都不能与其他版本互操作。 如果未指定,则默认值为 PROTOCOL_TLS;它提供了与其他版本的最大兼容性。

这个表显示了客户端(横向)的哪个版本能够连接服务器(纵向)的哪个版本。

客户端 / 服务器 SSLv2 SSLv3 TLS TLSv1 TLSv1.1 TLSv1.2
SSLv2
SSLv3
TLS (SSLv23)
TLSv1
TLSv1.1
TLSv1.2

参见

create_default_context()ssl 为特定目标选择安全设置。

在 3.6 版更改: 上下文会使用安全默认值来创建。 默认设置的选项有 OP_NO_COMPRESSION, OP_CIPHER_SERVER_PREFERENCE, OP_SINGLE_DH_USE, OP_SINGLE_ECDH_USE, OP_NO_SSLv2 (except for PROTOCOL_SSLv2) 和 OP_NO_SSLv3 (except for PROTOCOL_SSLv3)。 初始密码集列表只包含 HIGH 密码,不包含 NULL 密码和 MD5 密码 (PROTOCOL_SSLv2 除外)。

3.10 版后已移除: 不带协议参数的 SSLContext 已废弃。将来,上下文类会要求使用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 协议。

在 3.10 版更改: 现在默认的密码套件只包含安全的 AES 和 ChaCha20 密码,具有前向保密性和安全级别2。禁止使用少于 2048 位的 RSA 和 DH 密钥以及少于 224 位的ECC密钥。 PROTOCOL_TLSPROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 至少使用 TLS 1.2 版本。

SSLContext 对象具有以下方法和属性:

SSLContext.cert_store_stats()

获取以字典表示的有关已加载的 X.509 证书数量,被标记为 CA 证书的 X.509 证书数量以及证书吊销列表的统计信息。

具有一个 CA 证书和一个其他证书的上下文示例:

>>> context.cert_store_stats()
{'crl': 0, 'x509_ca': 1, 'x509': 2}

3.4 新版功能.

SSLContext.load_cert_chain(certfile, keyfile=None, password=None)

加载一个私钥及对应的证书。 certfile 字符串必须为以 PEM 格式表示的单个文件路径,该文件中包含证书以及确立证书真实性所需的任意数量的 CA 证书。 如果存在 keyfile 字符串,它必须指向一个包含私钥的文件。 否则私钥也将从 certfile 中提取。

password 参数可以是一个函数,调用时将得到用于解密私钥的密码。 它在私钥被加密且需要密码时才会被调用。 它调用时将不带任何参数,并且应当返回一个字符串、字节串或字节数组。 如果返回值是一个字符串,在用它解密私钥之前它将以 UTF-8 进行编码。 或者也可以直接将字符串、字节串或字节数组值作为 password 参数提供。 如果私钥未被加密且不需要密码则它将被忽略。

如果未指定 password 参数且需要一个密码,将会使用 OpenSSL 内置的密码提示机制来交互式地提示用户输入密码。

如果私钥不能匹配证书则会引发 SSLError

在 3.3 版更改: 新增可选参数 password

SSLContext.load_default_certs(purpose=Purpose.SERVER_AUTH)

从默认位置加载一组默认的 “证书颁发机构” (CA) 证书。 在 Windows 上它将从 CAROOT 系统存储中加载 CA 证书。 在其他系统上它会调用 SSLContext.set_default_verify_paths()。 将来该方法也可能会从其他位置加载 CA 证书。

purpose 旗标指明要加载哪一类 CA 证书。 默认设置 Purpose.SERVER_AUTH 加载被标记且被信任用于 TLS Web 服务器验证(客户端套接字)的证书。 Purpose.CLIENT_AUTH 则加载用于在服务器端进行客户端证书验证的 CA 证书。

3.4 新版功能.

SSLContext.load_verify_locations(cafile=None, capath=None, cadata=None)

verify_mode 不为 CERT_NONE 时加载一组用于验证其他对等方证书的 “证书颁发机构” (CA) 证书。 必须至少指定 cafilecapath 中的一个。

此方法还可加载 PEM 或 DER 格式的证书吊销列表 (CRL),为此必须正确配置 SSLContext.verify_flags

如果存在 cafile 字符串,它应为 PEM 格式的级联 CA 证书文件的路径。

如果存在 capath 字符串,它应为包含多个 PEM 格式的 CA 证书的目录的路径,并遵循 OpenSSL 专属布局

如果存在 cadata 对象,它应为一个或多个 PEM 编码的证书的 ASCII 字符串或者 DER 编码的证书的 bytes-like object。 与 capath 一样 PEM 编码的证书之外的多余行会被忽略,但至少要有一个证书。

在 3.4 版更改: 新增可选参数 cadata

SSLContext.get_ca_certs(binary_form=False)

获取已离开法人 “证书颁发机构” (CA) 证书列表。 如果 binary_form 形参为 False 则每个列表条目都是一个类似于 SSLSocket.getpeercert() 输出的字典。 在其他情况下此方法将返回一个 DER 编码的证书的列表。 返回的列表不包含来自 capath 的证书,除非 SSL 连接请求并加载了一个证书。

注解

capath 目录中的证书不会被加载,除非它们已至少被使用过一次。

3.4 新版功能.

SSLContext.get_ciphers()

获取已启用密码的列表。 该列表将按密码的优先级排序。

示例:

>>> ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
>>> ctx.set_ciphers('ECDHE+AESGCM:!ECDSA')
>>> ctx.get_ciphers()
[{'aead': True,
  'alg_bits': 256,
  'auth': 'auth-rsa',
  'description': 'ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  '
                 'Enc=AESGCM(256) Mac=AEAD',
  'digest': None,
  'id': 50380848,
  'kea': 'kx-ecdhe',
  'name': 'ECDHE-RSA-AES256-GCM-SHA384',
  'protocol': 'TLSv1.2',
  'strength_bits': 256,
  'symmetric': 'aes-256-gcm'},
 {'aead': True,
  'alg_bits': 128,
  'auth': 'auth-rsa',
  'description': 'ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  '
                 'Enc=AESGCM(128) Mac=AEAD',
  'digest': None,
  'id': 50380847,
  'kea': 'kx-ecdhe',
  'name': 'ECDHE-RSA-AES128-GCM-SHA256',
  'protocol': 'TLSv1.2',
  'strength_bits': 128,
  'symmetric': 'aes-128-gcm'}]

3.6 新版功能.

SSLContext.set_default_verify_paths()

从构建 OpenSSL 库时定义的文件系统路径中加载一组默认的 “证书颁发机构” (CA) 证书。 不幸的是,没有一种简单的方式能知道此方法是否执行成功:如果未找到任何证书也不会返回错误。 不过,当 OpenSSL 库是作为操作系统的一部分被提供时,它的配置应当是正确的。

SSLContext.set_ciphers(ciphers)

为使用此上下文创建的套接字设置可用密码。 它应当为 OpenSSL 密码列表格式 的字符串。 如果没有可被选择的密码(由于编译时选项或其他配置禁止使用所指定的任何密码),则将引发 SSLError

注解

在连接后,SSL 套接字的 SSLSocket.cipher() 方法将给出当前所选择的密码。

TLS 1.3 密码套件不能通过 set_ciphers() 禁用。

SSLContext.set_alpn_protocols(protocols)

指定在 SSL/TLS 握手期间套接字应当通告的协议。 它应为由 ASCII 字符串组成的列表,例如 ['http/1.1', 'spdy/2'],按首选顺序排列。 协议的选择将在握手期间发生,并依据 RFC 7301 来执行。 在握手成功后,SSLSocket.selected_alpn_protocol() 方法将返回已达成一致的协议。

如果 HAS_ALPNFalse 则此方法将引发 NotImplementedError

3.5 新版功能.

SSLContext.set_npn_protocols(protocols)

指定在Specify which protocols the socket should advertise during the SSL/TLS 握手期间套接字应当通告的协议。 它应为由字符串组成的列表,例如 ['http/1.1', 'spdy/2'],按首选顺序排列。 协议的选择将在握手期间发生,并将依据 应用层协议协商 来执行。 在握手成功后,SSLSocket.selected_npn_protocol() 方法将返回已达成一致的协议。

如果 HAS_NPNFalse 则此方法将引发 NotImplementedError

3.3 新版功能.

3.10 版后已移除: NPN 已被 ALPN 取代。

SSLContext.sni_callback

注册一个回调函数,当 TLS 客户端指定了一个服务器名称提示时,该回调函数将在 SSL/TLS 服务器接收到 TLS Client Hello 握手消息后被调用。 服务器名称提示机制的定义见 RFC 6066 section 3 - Server Name Indication。

每个 SSLContext 只能设置一个回调。 如果 sni_callback 被设置为 None 则会禁用回调。 对该函数的后续调用将禁用之前注册的回调。

此回调函数将附带三个参数来调用;第一个参数是 ssl.SSLSocket,第二个参数是代表客户端准备与之通信的服务器的字符串 (或者如果 TLS Client Hello 不包含服务器名称则为 None) 而第三个参数是原来的 SSLContext。 服务器名称参数为文本形式。 对于国际化域名,服务器名称是一个 IDN A 标签 ("xn--pythn-mua.org")。

此回调的一个典型用法是将 ssl.SSLSocketSSLSocket.context 属性修改为一个 SSLContext 类型的新对象,该对象代表与服务器相匹配的证书链。

由于 TLS 连接处于早期协商阶段,因此仅能使用有限的方法和属性例如 SSLSocket.selected_alpn_protocol()SSLSocket.contextSSLSocket.getpeercert(), SSLSocket.getpeercert(), SSLSocket.cipher()SSLSocket.compress() 方法要求 TLS 连接已经过 TLS Client Hello 因而将既不包含返回有意义的值,也不能安全地调用它们。

sni_callback 函数必须返回 None 以允许 TLS 协商继续进行。 如果想要 TLS 失败,则可以返回常量 ALERT_DESCRIPTION_*。 其他返回值将导致 TLS 的致命错误 ALERT_DESCRIPTION_INTERNAL_ERROR

如果从 sni_callback 函数引发了异常,则 TLS 连接将终止并发出 TLS 致命警告消息 ALERT_DESCRIPTION_HANDSHAKE_FAILURE

如果 OpenSSL library 库在构建时定义了 OPENSSL_NO_TLSEXT 则此方法将返回 NotImplementedError

3.7 新版功能.

SSLContext.set_servername_callback(server_name_callback)

这是被保留用于向下兼容的旧式 API。 在可能的情况下,你应当改用 sni_callback。 给出的 server_name_callback 类似于 sni_callback,不同之处在于当服务器主机名是 IDN 编码的国际化域名时,server_name_callback 会接收到一个已编码的 U 标签 ("pythön.org")。

如果发生了服务器名称解码错误。 TLS 连接将终止并向客户端发出 ALERT_DESCRIPTION_INTERNAL_ERROR 最严重 TLS 警告消息。

3.4 新版功能.

SSLContext.load_dh_params(dhfile)

加载密钥生成参数用于 Diffie-Hellman (DH) 密钥交换。 使用 DH 密钥交换能以消耗(服务器和客户端的)计算资源为代价提升前向保密性。 dhfile 参数应当为指向一个包含 PEM 格式的 DH 形参的文件的路径。

此设置不会应用于客户端套接字。 你还可以使用 OP_SINGLE_DH_USE 选项来进一步提升安全性。

3.3 新版功能.

SSLContext.set_ecdh_curve(curve_name)

为基于椭圆曲线的 Elliptic Curve-based Diffie-Hellman (ECDH) 密钥交换设置曲线名称。 ECDH 显著快于常规 DH 同时据信同样安全。 curve_name 形参应为描述某个知名椭圆曲线的字符串,例如受到广泛支持的曲线 prime256v1

此设置不会应用于客户端套接字。 你还可以使用 OP_SINGLE_ECDH_USE 选项来进一步提升安全性。

如果 HAS_ECDHFalse 则此方法将不可用。

3.3 新版功能.

参见

SSLContext.wrap_socket(sock, server_side=False, do_handshake_on_connect=True, suppress_ragged_eofs=True, server_hostname=None, session=None)

包装一个现有的 Python 套接字 sock 并返回一个 SSLContext.sslsocket_class 的实例 (默认为 SSLSocket)。 返回的 SSL 套接字会绑定上下文、设置以及证书。 sock 必须是一个 SOCK_STREAM 套接字;其他套接字类型不被支持。

形参 server_side 是一个布尔值,它标明希望从该套接字获得服务器端行为还是客户端行为。

对于客户端套接字,上下文的构造会延迟执行;如果下层的套接字尚未连接,上下文的构造将在对套接字调用 connect() 之后执行。 对于服务器端套接字,如果套接字没有远端对等方,它会被视为一个监听套接字,并且服务器端 SSL 包装操作会在通过 accept() 方法所接受的客户端连接上自动执行。 此方法可能会引发 SSLError

在客户端连接上,可选形参 server_hostname 指定所要连接的服务的主机名。 这允许单个服务器托管具有单独证书的多个基于 SSL 的服务,很类似于 HTTP 虚拟主机。 如果 server_side 为真值则指定 server_hostname 将引发 ValueError

形参 do_handshake_on_connect 指明是否要在调用 socket.connect() 之后自动执行 SSL 握手,还是要通过发起调用 SSLSocket.do_handshake() 方法让应用程序显式地调用它。 显式地调用 SSLSocket.do_handshake() 可给予程序对握手中所涉及的套接字 I/O 阻塞行为的控制。

形参 suppress_ragged_eofs 指明 SSLSocket.recv() 方法应当如何从连接的另一端发送非预期的 EOF 信号。 如果指定为 True (默认值),它将返回正常的 EOF (空字节串对象) 来响应从下层套接字引发的非预期的 EOF 错误;如果指定为 False,它将向调用方引发异常。

session,参见 session

在 3.5 版更改: 总是允许传送 server_hostname,即使 OpenSSL 没有 SNI。

在 3.6 版更改: 增加了 session 参数。

在 3.7 版更改: 此方法返回 SSLContext.sslsocket_class 的实例而非硬编码的 SSLSocket

SSLContext.sslsocket_class

SSLContext.wrap_socket() 的返回类型,默认为 SSLSocket。 该属性可以在类实例上被重载以便返回自定义的 SSLSocket 的子类。

3.7 新版功能.

SSLContext.wrap_bio(incoming, outgoing, server_side=False, server_hostname=None, session=None)

包装 BIO 对象 incomingoutgoing 并返回一个 SSLContext.sslobject_class (默认为 SSLObject) 的实例。 SSL 例程将从 BIO 中读取输入数据并将数据写入到 outgoing BIO。

server_side, server_hostnamesession 形参具有与 SSLContext.wrap_socket() 中相同的含义。

在 3.6 版更改: 增加了 session 参数。

在 3.7 版更改: 此方法返回 SSLContext.sslobject_class 的实例则非硬编码的 SSLObject

SSLContext.sslobject_class

SSLContext.wrap_bio() 的返回类型,默认为 SSLObject。 该属性可以在类实例上被重载以便返回自定义的 SSLObject 的子类。

3.7 新版功能.

SSLContext.session_stats()

获取该上下文创建或管理的 SSL 会话的统计数据。返回一个字典,将每块信息 <https://www.openssl.org/docs/man1.1.1/ssl/SSL_CTX_sess_number.html>_ 映射到数字值。例如,下面是自该上下文创建以来会话缓存中的总点击率和失误率:

>>> stats = context.session_stats()
>>> stats['hits'], stats['misses']
(0, 0)

是否要将匹配 SSLSocket.do_handshake() 中对等方证书的主机名。 该上下文的 verify_mode 必须被设为 CERT_OPTIONALCERT_REQUIRED,并且你必须将 server_hostname 传给 wrap_socket() 以便匹配主机名。 启用主机名检查会自动将 verify_modeCERT_NONE 设为 CERT_REQUIRED。 只要启用了主机名检查就无法将其设回 CERT_NONEPROTOCOL_TLS_CLIENT 协议默认启用主机名检查。 对于其他协议,则必须显式地启用主机名检查。

示例:

import socket, ssl
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
context.load_default_certs()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(s, server_hostname='www.verisign.com')
ssl_sock.connect(('www.verisign.com', 443))

3.4 新版功能.

在 3.7 版更改: 现在当主机名检查被启用且 verify_modeCERT_NONEverify_mode 会自动更改为 CERT_REQUIRED。 在之前版本中同样的操作将失败并引发 ValueError

SSLContext.keylog_filename

每当生成或接收到密钥时,将 TLS 密钥写入到一个密钥日志文件。 密钥日志文件的设计仅适用于调试目的。 文件的格式由 NSS 指明并为许多流量分析工具例如 Wireshark 所使用。 日志文件会以追加模式打开。 写入操作会在线程之间同步,但不会在进程之间同步。

3.8 新版功能.

SSLContext.maximum_version

一个代表所支持的最高 TLS 版本的 TLSVersion 枚举成员。 该值默认为 TLSVersion.MAXIMUM_SUPPORTED。 这个属性对于 PROTOCOL_TLS, PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 以外的其他协议来说都是只读的。

maximum_version, minimum_versionSSLContext.options 等属性都会影响上下文所支持的 SSL 和 TLS 版本。 这个实现不会阻止无效的组合。 例如一个 optionsOP_NO_TLSv1_2maximum_version 设为 TLSVersion.TLSv1_2 的上下文将无法建立 TLS 1.2 连接。

3.7 新版功能.

SSLContext.minimum_version

SSLContext.maximum_version 类似,区别在于它是所支持的最低版本或为 TLSVersion.MINIMUM_SUPPORTED

3.7 新版功能.

SSLContext.num_tickets

控制 TLS_PROTOCOL_SERVER 上下文的 TLS 1.3 会话凭据数量。这个设置不会影响 TLS 1.0 - 1.2 的连接。

3.8 新版功能.

SSLContext.options

一个代表此上下文中所启用的 SSL 选项集的整数。 默认值为 OP_ALL,但你也可以通过在选项间进行 OR 运算来指定其他选项例如 OP_NO_SSLv2

在 3.6 版更改: SSLContext.options 返回 Options 旗标:

>>> ssl.create_default_context().options  
<Options.OP_ALL|OP_NO_SSLv3|OP_NO_SSLv2|OP_NO_COMPRESSION: 2197947391>

3.7 版后已移除: 自 OpenSSL 1.1.0 起,所有 OP_NO_SSL*OP_NO_TLS* 选项已被弃用,请改用新的 SSLContext.minimum_versionSSLContext.maximum_version

SSLContext.post_handshake_auth

启用 TLS 1.3 握手后客户端身份验证。 握手后验证默认是被禁用的,服务器只能在初始握手期间请求 TLS 客户端证书。 当启用时,服务器可以在握手之后的任何时候请求 TLS 客户端证书。

当在客户端套接字上启用时,客户端会向服务器发信号说明它支持握手后身份验证。

当在服务器端套接字上启用时,SSLContext.verify_mode 也必须被设为 CERT_OPTIONALCERT_REQUIRED。 实际的客户端证书交换会被延迟直至 SSLSocket.verify_client_post_handshake() 被调用并执行了一些 I/O 操作后再进行。

3.8 新版功能.

SSLContext.protocol

构造上下文时所选择的协议版本。 这个属性是只读的。

SSLContext.hostname_checks_common_name

在没有目标替代名称扩展的情况下 check_hostname 是否要回退为验证证书的通用名称(默认为真值)。

3.7 新版功能.

在 3.10 版更改: 此旗标在 OpenSSL 1.1.1k 之前的版本上不起作用。 Python 3.8.9, 3.9.3, 和 3.10 包含了针对之前版本的变通处理。

SSLContext.security_level

整数值,代表上下文的 安全级别。 本属性只读。

3.10 新版功能.

SSLContext.verify_flags

证书验证操作的标志位。可以用“或”的方式组合在一起设置 VERIFY_CRL_CHECK_LEAF 这类标志。默认情况下,OpenSSL 既不需要也不验证证书吊销列表(CRL)。

3.4 新版功能.

在 3.6 版更改: SSLContext.verify_flags 返回 VerifyFlags 旗标:

>>> ssl.create_default_context().verify_flags  
<VerifyFlags.VERIFY_X509_TRUSTED_FIRST: 32768>

是否要尝试验证其他对等方的证书以及如果验证失败应采取何种行为。 该属性值必须为 CERT_NONE, CERT_OPTIONALCERT_REQUIRED 之一。

在 3.6 版更改: SSLContext.verify_mode 返回 VerifyMode 枚举:

>>> ssl.create_default_context().verify_mode
<VerifyMode.CERT_REQUIRED: 2>

证书

总的来说证书是公钥/私钥系统的一个组成部分。 在这个系统中,每 个 主体 (可能是一台机器、一个人或者一个组织) 都会分配到唯一的包含两部分的加密密钥。 一部分密钥是公开的,称为 公钥;另一部分密钥是保密的,称为 私钥。 这两个部分是互相关联的,就是说如果你用其中一个部分来加密一条消息,你将能用并且 只能 用另一个部分来解密它。

在一个证书中包含有两个主体的相关信息。 它包含 目标方 的名称和目标方的公钥。 它还包含由第二个主体 颁发方 所发布的声明:目标方的身份与他们所宣称的一致,包含的公钥也确实是目标方的公钥。 颁发方的声明使用颁发方的私钥进行签名,该私钥的内容只有颁发方自己才知道。 但是,任何人都可以找到颁发方的公钥,用它来解密这个声明,并将其与证书中的其他信息进行比较来验证颁发方声明的真实性。 证书还包含有关其有效期限的信息。 这被表示为两个字段,即 “notBefore” 和 “notAfter”。

在 Python 中应用证书时,客户端或服务器可以用证书来证明自己的身份。 还可以要求网络连接的另一方提供证书,提供的证书可以用于验证以满足客户端或服务器的验证要求。 如果验证失败,连接尝试可被设置为引发一个异常。 验证是由下层的 OpenSSL 框架来自动执行的;应用程序本身不必关注其内部的机制。 但是应用程序通常需要提供一组证书以允许此过程的发生。

Python 使用文件来包含证书。 它们应当采用 “PEM” 格式 (参见 RFC 1422),这是一种带有头部行和尾部行的 base-64 编码包装形式:

-----BEGIN CERTIFICATE-----
... (certificate in base64 PEM encoding) ...
-----END CERTIFICATE-----

证书链

包含证书的 Python 文件可以包含一系列的证书,有时被称为 证书链*。 这个证书链应当以 “作为” 客户端或服务器的主体的专属证书打头,然后是证书颁发方的证书,然后是 *上述 证书的颁发方的证书,证书链就这样不断上溯直到你得到一个 自签名 的证书,即具有相同目标方和颁发方的证书,有时也称为 根证书。 在证书文件中这些证书应当被拼接为一体。 例如,假设我们有一个包含三个证书的证书链,以我们的服务器证书打头,然后是为我们的服务器证书签名的证书颁发机构的证书,最后是为证书颁发机构的证书颁发证书的机构的根证书:

-----BEGIN CERTIFICATE-----
... (certificate for your server)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (the certificate for the CA)...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
... (the root certificate for the CA's issuer)...
-----END CERTIFICATE-----

CA 证书

如果你想要求对连接的另一方的证书进行验证,你必须提供一个 “CA 证书” 文件,其中包含了你愿意信任的每个颁发方的证书链。 同样地,这个文件的内容就是这些证书链拼接在一起的结果。 为了进行验证,Python 将使用它在文件中找到的第一个匹配的证书链。 可以通过调用 SSLContext.load_default_certs() 来使用系统平台的证书文件,这可以由 create_default_context() 自动完成。

合并的密钥和证书

私钥往往与证书存储在相同的文件中;在此情况下,只需要将 certfile 形参传给 SSLContext.load_cert_chain()wrap_socket()。 如果私钥是与证书一起存储的,则它应当放在证书链的第一个证书之前:

-----BEGIN RSA PRIVATE KEY-----
... (private key in base64 encoding) ...
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
... (certificate in base64 PEM encoding) ...
-----END CERTIFICATE-----

自签名证书

如果你准备创建一个提供 SSL 加密连接服务的服务器,你需要为该服务获取一份证书。 有许多方式可以获取合适的证书,例如从证书颁发机构购买。 另一种常见做法是生成自签名证书。 生成自签名证书的最简单方式是使用 OpenSSL 软件包,代码如下所示:

% openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
Generating a 1024 bit RSA private key
.......++++++
.............................++++++
writing new private key to 'cert.pem'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:MyState
Locality Name (eg, city) []:Some City
Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Organization, Inc.
Organizational Unit Name (eg, section) []:My Group
Common Name (eg, YOUR name) []:myserver.mygroup.myorganization.com
Email Address []:ops@myserver.mygroup.myorganization.com
%

自签名证书的缺点在于它是它自身的根证书,因此不会存在于别人的已知(且信任的)根证书缓存当中。

例子

检测 SSL 支持

要检测一个 Python 安装版中是否带有 SSL 支持,用户代码应当使用以下例程:

try:
    import ssl
except ImportError:
    pass
else:
    ...  # do something that requires SSL support

客户端操作

这个例子创建了一个 SSL 上下文并使用客户端套接字的推荐安全设置,包括自动证书验证:

>>> context = ssl.create_default_context()

如果你喜欢自行调整安全设置,你可能需要从头创建一个上下文(但是请请注意避免不正确的设置):

>>> context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> context.load_verify_locations("/etc/ssl/certs/ca-bundle.crt")

(这段代码假定你的操作系统将所有 CA 证书打包存放于 /etc/ssl/certs/ca-bundle.crt;如果不是这样,你将收到报错信息,必须修改此位置)

PROTOCOL_TLS_CLIENT 协议配置用于证书验证和主机名验证的上下文。 verify_mode 设为 CERT_REQUIREDcheck_hostname 设为 True。 所有其他协议都会使用不安全的默认值创建 SSL 上下文。

当你使用此上下文去连接服务器时,CERT_REQUIREDcheck_hostname 会验证服务器证书;它将确认服务器证书使用了某个 CA 证书进行签名,检查签名是否正确,并验证其他属性例如主机名的有效性和身份真实性:

>>> conn = context.wrap_socket(socket.socket(socket.AF_INET),
...                            server_hostname="www.python.org")
>>> conn.connect(("www.python.org", 443))

你可以随后获取该证书:

>>> cert = conn.getpeercert()

可视化检查显示证书能够证明目标服务 (即 HTTPS 主机 www.python.org) 的身份:

>>> pprint.pprint(cert)
{'OCSP': ('http://ocsp.digicert.com',),
 'caIssuers': ('http://cacerts.digicert.com/DigiCertSHA2ExtendedValidationServerCA.crt',),
 'crlDistributionPoints': ('http://crl3.digicert.com/sha2-ev-server-g1.crl',
                           'http://crl4.digicert.com/sha2-ev-server-g1.crl'),
 'issuer': ((('countryName', 'US'),),
            (('organizationName', 'DigiCert Inc'),),
            (('organizationalUnitName', 'www.digicert.com'),),
            (('commonName', 'DigiCert SHA2 Extended Validation Server CA'),)),
 'notAfter': 'Sep  9 12:00:00 2016 GMT',
 'notBefore': 'Sep  5 00:00:00 2014 GMT',
 'serialNumber': '01BB6F00122B177F36CAB49CEA8B6B26',
 'subject': ((('businessCategory', 'Private Organization'),),
             (('1.3.6.1.4.1.311.60.2.1.3', 'US'),),
             (('1.3.6.1.4.1.311.60.2.1.2', 'Delaware'),),
             (('serialNumber', '3359300'),),
             (('streetAddress', '16 Allen Rd'),),
             (('postalCode', '03894-4801'),),
             (('countryName', 'US'),),
             (('stateOrProvinceName', 'NH'),),
             (('localityName', 'Wolfeboro'),),
             (('organizationName', 'Python Software Foundation'),),
             (('commonName', 'www.python.org'),)),
 'subjectAltName': (('DNS', 'www.python.org'),
                    ('DNS', 'python.org'),
                    ('DNS', 'pypi.org'),
                    ('DNS', 'docs.python.org'),
                    ('DNS', 'testpypi.org'),
                    ('DNS', 'bugs.python.org'),
                    ('DNS', 'wiki.python.org'),
                    ('DNS', 'hg.python.org'),
                    ('DNS', 'mail.python.org'),
                    ('DNS', 'packaging.python.org'),
                    ('DNS', 'pythonhosted.org'),
                    ('DNS', 'www.pythonhosted.org'),
                    ('DNS', 'test.pythonhosted.org'),
                    ('DNS', 'us.pycon.org'),
                    ('DNS', 'id.python.org')),
 'version': 3}

现在 SSL 通道已建立并已验证了证书,你可以继续与服务器对话了:

>>> conn.sendall(b"HEAD / HTTP/1.0\r\nHost: linuxfr.org\r\n\r\n")
>>> pprint.pprint(conn.recv(1024).split(b"\r\n"))
[b'HTTP/1.1 200 OK',
 b'Date: Sat, 18 Oct 2014 18:27:20 GMT',
 b'Server: nginx',
 b'Content-Type: text/html; charset=utf-8',
 b'X-Frame-Options: SAMEORIGIN',
 b'Content-Length: 45679',
 b'Accept-Ranges: bytes',
 b'Via: 1.1 varnish',
 b'Age: 2188',
 b'X-Served-By: cache-lcy1134-LCY',
 b'X-Cache: HIT',
 b'X-Cache-Hits: 11',
 b'Vary: Cookie',
 b'Strict-Transport-Security: max-age=63072000; includeSubDomains',
 b'Connection: close',
 b'',
 b'']

服务器端操作

对于服务器操作,通常你需要在文件中存放服务器证书和私钥各一份。 你将首先创建一个包含密钥和证书的上下文,这样客户端就能检查你的身份真实性。 然后你将打开一个套接字,将其绑定到一个端口,在其上调用 listen(),并开始等待客户端连接:

import socket, ssl
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.load_cert_chain(certfile="mycertfile", keyfile="mykeyfile")
bindsocket = socket.socket()
bindsocket.bind(('myaddr.mydomain.com', 10023))
bindsocket.listen(5)

当有客户端连接时,你将在套接字上调用 accept() 以从另一端获取新的套接字,并使用上下文的 SSLContext.wrap_socket() 方法来为连接创建一个服务器端 SSL 套接字:

while True:
    newsocket, fromaddr = bindsocket.accept()
    connstream = context.wrap_socket(newsocket, server_side=True)
    try:
        deal_with_client(connstream)
    finally:
        connstream.shutdown(socket.SHUT_RDWR)
        connstream.close()

随后你将从 connstream 读取数据并对其进行处理,直至你结束与客户端的会话(或客户端结束与你的会话):

def deal_with_client(connstream):
    data = connstream.recv(1024)
    # empty data means the client is finished with us
    while data:
        if not do_something(connstream, data):
            # we'll assume do_something returns False
            # when we're finished with client
            break
        data = connstream.recv(1024)
    # finished with client

并返回至监听新的客户端连接(当然,真正的服务器应当会在单独的线程中处理每个客户端连接,或者将套接字设为 非阻塞模式 并使用事件循环)。

关于非阻塞套接字的说明

在非阻塞模式下 SSL 套接字的行为与常规套接字略有不同。 当使用非阻塞模式时,你需要注意下面这些事情:

  • 如果一个 I/O 操作会阻塞,大多数 SSLSocket 方法都将引发 SSLWantWriteErrorSSLWantReadError 而非 BlockingIOError。 如果有必要在下层套接字上执行读取操作将引发 SSLWantReadError,在下层套接字上执行写入操作则将引发 SSLWantWriteError。 请注意尝试 写入 到 SSL 套接字可能需要先从下层套接字 读取*,而尝试从 SSL 套接字 *读取 则可能需要先向下层套接字 写入

    在 3.5 版更改: 在较早的 Python 版本中,SSLSocket.send() 方法会返回零值而非引发 SSLWantWriteErrorSSLWantReadError

  • 调用 select() 将告诉你可以从 OS 层级的套接字读取(或向其写入),但这并不意味着在上面的 SSL 层有足够的数据。 例如,可能只有部分 SSL 帧已经到达。 因此,你必须准备好处理 SSLSocket.recv()SSLSocket.send() 失败的情况,并在再次调用 select() 之后重新尝试。

  • 相反地,由于 SSL 层具有自己的帧机制,一个 SSL 套接字可能仍有可读取的数据而 select() 并不知道这一点。 因此,你应当先调用 SSLSocket.recv() 取走所有潜在的可用数据,然后只在必要时对 select() 调用执行阻塞。

    (当然,类似的保留规则在使用其他原语例如 poll(),或 selectors 模块中的原语时也适用)

  • SSL 握手本身将是非阻塞的: SSLSocket.do_handshake() 方法必须不断重试直至其成功返回。 下面是一个使用 select() 来等待套接字就绪的简短例子:

    while True:
        try:
            sock.do_handshake()
            break
        except ssl.SSLWantReadError:
            select.select([sock], [], [])
        except ssl.SSLWantWriteError:
            select.select([], [sock], [])

参见

asyncio 模块支持 非阻塞 SSL 套接字 并提供了更高层级的 API。 它会使用 selectors 模块来轮询事件并处理 SSLWantWriteError, SSLWantReadErrorBlockingIOError 等异常。 它还会异步地执行 SSL 握手。

内存 BIO 支持

3.5 新版功能.

自从 SSL 模块在 Python 2.6 起被引入之后,SSLSocket 类提供了两个互相关联但彼此独立的功能分块:

  • SSL 协议处理
  • 网络 IO

网络 IO API 与 socket.socket 所提供的功能一致,SSLSocket 也是从那里继承而来的。 这允许 SSL 套接字被用作常规套接字的替代,使得向现有应用程序添加 SSL 支持变得非常容易。

将 SSL 协议处理与网络 IO 结合使用通常都能运行良好,但在某些情况下则不能。 此情况的一个例子是 async IO 框架,该框架要使用不同的 IO 多路复用模型而非 (基于就绪状态的) “在文件描述器上执行选择/轮询” 模型,该模型是 socket.socket 和内部 OpenSSL 套接字 IO 例程正常运行的假设前提。 这种情况在该模型效率不高的 Windows 平台上最为常见。 为此还提供了一个 SSLSocket 的简化形式,称为 SSLObject

class ssl.SSLObject

SSLSocket 的简化形式,表示一个不包含任何网络 IO 方法的 SSL 协议实例。 这个类通常由想要通过内存缓冲区为 SSL 实现异步 IO 的框架作者来使用。

这个类在低层级 SSL 对象上实现了一个接口,与 OpenSSL 所实现的类似。 此对象会捕获 SSL 连接的状态但其本身不提供任何网络 IO。 IO 需要通过单独的 “BIO” 对象来执行,该对象是 OpenSSL 的 IO 抽象层。

这个类没有公有构造器。 SSLObject 实例必须使用 wrap_bio() 方法来创建。 此方法将创建 SSLObject 实例并将其绑定到一个 BIO 对。 其中 incoming BIO 用来将数据从 Python 传递到 SSL 协议实例,而 outgoing BIO 用来进行数据反向传递。

可以使用以下方法:

  • context
  • server_side
  • server_hostname
  • session
  • session_reused
  • read()
  • write()
  • getpeercert()
  • selected_alpn_protocol()
  • selected_npn_protocol()
  • cipher()
  • shared_ciphers()
  • compression()
  • pending()
  • do_handshake()
  • verify_client_post_handshake()
  • unwrap()
  • get_channel_binding()
  • version()

SSLSocket 相比,此对象缺少下列特性:

  • 任何形式的网络 IO; recv()send() 仅对下层的 MemoryBIO 缓冲区执行读取和写入。
  • 不存在 do_handshake_on_connect 机制。 你必须总是手动调用 do_handshake() 来开始握手操作。
  • 不存在对 suppress_ragged_eofs 的处理。 所有违反协议的文件结束条件将通过 SSLEOFError 异常来报告。
  • 方法 unwrap() 的调用不返回任何东西,不会如 SSL 套接字那样返回下层的套接字。
  • server_name_callback 回调被传给 SSLContext.set_servername_callback() 时将获得一个 SSLObject 实例而非 SSLSocket 实例作为其第一个形参。

有关 SSLObject 用法的一些说明:

  • SSLObject 上的所有 IO 都是 非阻塞的。 这意味着例如 read() 在其需要比 incoming BIO 可用的更多数据时将会引发 SSLWantReadError
  • 不存在模块层级的 wrap_bio() 调用,就像 wrap_socket() 那样。 SSLObject 总是通过 SSLContext 来创建。

在 3.7 版更改: SSLObject 的实例必须使用 wrap_bio() 来创建。 在较早的版本中,直接创建实例是可能的。 但这从未被记入文档或是被正式支持。

SSLObject 会使用内存缓冲区与外部世界通信。 MemoryBIO 类提供了可被用于此目的的内存缓冲区。 它包装了一个 OpenSSL 内存 BIO (Basic IO) 对象:

class ssl.MemoryBIO

一个可被用来在 Python 和 SSL 协议实例之间传递数据的内存缓冲区。

  • pending

    返回当前存在于内存缓冲区的字节数。

  • eof

    一个表明内存 BIO 目前是否位于文件末尾的布尔值。

  • read(n=- 1)

    从内存缓冲区读取至多 n 个字节。 如果 n 未指定或为负值,则返回全部字节数据。

  • write(buf)

    将字节数据从 buf 写入到内存 BIO。 buf 参数必须为支持缓冲区协议的对象。

    返回值为写入的字节数,它总是与 buf 的长度相等。

  • write_eof()

    将一个 EOF 标记写入到内存 BIO。 在此方法被调用以后,再调用 write() 将是非法的。 属性 eof will 在缓冲区当前的所有数据都被读取之后将变为真值。

SSL 会话

3.6 新版功能.

class ssl.SSLSession

session 所使用的会话对象。

  • id
  • time
  • timeout
  • ticket_lifetime_hint
  • has_ticket

安全考量

最佳默认值

针对 客户端使用,如果你对于安全策略没有任何特殊要求,则强烈推荐你使用 create_default_context() 函数来创建你的 SSL 上下文。 它将加载系统的受信任 CA 证书,启用证书验证和主机名检查,并尝试合理地选择安全的协议和密码设置。

例如,以下演示了你应当如何使用 smtplib.SMTP 类来创建指向一个 SMTP 服务器的受信任且安全的连接:

>>> import ssl, smtplib
>>> smtp = smtplib.SMTP("mail.python.org", port=587)
>>> context = ssl.create_default_context()
>>> smtp.starttls(context=context)
(220, b'2.0.0 Ready to start TLS')

如果连接需要客户端证书,可使用 SSLContext.load_cert_chain() 来添加。

作为对比,如果你通过自行调用 SSLContext 构造器来创建 SSL 上下文,它默认将不会启用证书验证和主机名检查。 如果你这样做,请阅读下面的段落以达到良好的安全级别。

手动设置

验证证书

当直接调用 SSLContext 构造器时,默认会使用 CERT_NONE。 由于它不会验证对等方的身份真实性,因此是不安全的,特别是在客户端模式下,大多数时候你都希望能保证你所连接的服务器的身份真实性。 因此,当处于客户端模式时,强烈推荐使用 CERT_REQUIRED。 但是,光这样还不够;你还必须检查服务器证书,这可以通过调用 SSLSocket.getpeercert() 来获取并匹配目标服务。 对于许多协议和应用来说,服务可通过主机名来标识;在此情况下,可以使用 match_hostname() 函数。 这种通用检测会在 SSLContext.check_hostname 被启用时自动执行。

在 3.7 版更改: 主机名匹配现在是由 OpenSSL 来执行的。 Python 不会再使用 match_hostname()

在服务器模式下,如果你想要使用 SSL 层来验证客户端(而不是使用更高层级的验证机制),你也必须要指定 CERT_REQUIRED 并以类似方式检查客户端证书。

协议版本

SSL 版本 2 和 3 被认为是不安全的因而使用它们会有风险。 如果你想要客户端和服务器之间有最大的兼容性,推荐使用 PROTOCOL_TLS_CLIENTPROTOCOL_TLS_SERVER 作为协议版本。 SSLv2 和 SSLv3 默认会被禁用。

>>> client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
>>> client_context.minimum_version = ssl.TLSVersion.TLSv1_3
>>> client_context.maximum_version = ssl.TLSVersion.TLSv1_3

前面创建的 SSL 上下文将只允许 TLSv1.2 及更新版本(如果你的系统支持)的服务器连接。 PROTOCOL_TLS_CLIENT 默认会使用证书验证和主机名检查。 你必须将证书加载到上下文中。

密码选择

如果你有更高级的安全要求,也可以通过 SSLContext.set_ciphers() 方法在协商 SSL 会话时对所启用的加密进行微调。 从 Python 3.2.3 开始,ssl 默认会禁用某些较弱的加密,但你还可能希望进一步限制加密选项。 请确保仔细阅读 OpenSSL 文档中有关 加密列表格式 的部分。 如果你想要检查给定的加密列表启用了哪些加密,可以使用 SSLContext.get_ciphers() 或所在系统的 openssl ciphers 命令。

多进程

如果使用此模块作为多进程应用的一部分(例如使用 multiprocessingconcurrent.futures 模块),请注意 OpenSSL 的内部随机数字生成器并不能正确处理分支进程。 应用程序必须修改父进程的 PRNG 状态,如果它们要使用任何包含 os.fork() 的 SSL 特性的话。 任何对 RAND_add(), RAND_bytes()RAND_pseudo_bytes() 都可以 做到这一点。

TLS 1.3

3.7 新版功能.

TLS 1.3 协议的行为与低版本的 TLS/SSL 略有不同。某些 TLS 1.3 新特性还不可用。

  • TLS 1.3 使用一组不同的加密套件集。 默认情况下所有 AES-GCM 和 ChaCha20 加密套件都会被启用。 SSLContext.set_ciphers() 方法还不能启用或禁用任何 TLS 1.3 加密,但 SSLContext.get_ciphers() 会返回它们。
  • 会话凭据不再会作为初始握手的组成部分被发送而是以不同的方式来处理。 SSLSocket.sessionSSLSession 与 TLS 1.3 不兼容。
  • 客户端证书在初始握手期间也不会再被验证。 服务器可以在任何时候请求证书。 客户端会在它们从服务器发送或接收应用数据时处理证书请求。
  • 早期数据、延迟的 TLS 客户端证书请求、签名算法配置和密钥重生成等 TLS 1.3 特性尚未被支持。

参见

Class socket.socket

下层 socket 类的文档

SSL/TLS 高强度加密:概述

Apache HTTP Server文档介绍

RFC 1422: 因特网电子邮件的隐私加强:第二部分:基于证书的密钥管理

Steve Kent

RFC 4086: 确保安全的随机性要求

Donald E., Jeffrey I. Schiller

RFC 5280: 互联网 X.509 公钥基础架构证书和证书吊销列表 (CRL) 配置文件

D. Cooper

RFC 5246: 传输层安全性 (TLS) 协议版本 1.2

T. Dierks et. al.

RFC 6066: 传输层安全性 (TLS) 的扩展

D. Eastlake

IANA TLS: 传输层安全性 (TLS) 的参数

IANA

RFC 7525: 传输层安全性 (TLS) 和数据报传输层安全性 (DTLS) 的安全使用建议

IETF

Mozilla 的服务器端 TLS 建议

Mozilla

select —- 等待 I/O 完成

该模块提供了对 select()poll() 函数的访问,这些函数在大多数操作系统中是可用的。在 Solaris 及其衍生版本上可用 devpoll(),在 Linux 2.5+ 上可用 epoll(),在大多数 BSD 上可用 kqueue()。注意,在 Windows 上,本模块仅适用于套接字;在其他操作系统上,本模块也适用于其他文件类型(特别地,在 Unix 上也适用于管道)。本模块不能用于常规文件,不能检测出(自上次读取文件后)文件是否有新数据写入。

注解

selectors 模块是在 select 模块原型的基础上进行高级且高效的 I/O 复用。推荐用户改用 selectors 模块,除非用户希望对 OS 级的函数原型进行精确控制。

该模块定义以下内容:

exception select.error

一个被弃用的 OSError 的别名。

在 3.3 版更改: 根据 PEP 3151,这个类是 OSError 的别名。

select.devpoll()

(仅支持 Solaris 及其衍生版本)返回一个 /dev/poll 轮询对象,

devpoll() 对象与实例化时允许的文件描述符数量有关,如果在程序中降低了此数值,devpoll() 调用将失败。如果程序提高了此数值,devpoll() 可能会返回一个不完整的活动文件描述符列表。

新的文件描述符是 不可继承的。

3.3 新版功能.

在 3.4 版更改: 新的文件描述符现在是不可继承的。

select.epoll(sizehint=- 1, flags=0)

(仅支持 Linux 2.5.44 或更高版本)返回一个 edge poll 对象,该对象可作为 I/O 事件的边缘触发或水平触发接口。

sizehint 指示 epoll 预计需要注册的事件数。它必须为正数,或为 -1 以使用默认值。它仅在 epoll_create1() 不可用的旧系统上会被用到,其他情况下它没有任何作用(尽管仍会检查其值)。

flags 已经弃用且完全被忽略。但是,如果提供该值,则它必须是 0select.EPOLL_CLOEXEC,否则会抛出 OSError 异常。

epoll 对象支持上下文管理器:当在 with 语句中使用时,新建的文件描述符会在运行至语句块结束时自动关闭。

新的文件描述符是 不可继承的。

在 3.3 版更改: 增加了 flags 参数。

在 3.4 版更改: 增加了对 with 语句的支持。新的文件描述符现在是不可继承的。

3.4 版后已移除: flags 参数。现在默认采用 select.EPOLL_CLOEXEC 标志。使用 os.set_inheritable() 来让文件描述符可继承。

select.poll()

(部分操作系统不支持)返回一个 poll 对象,该对象支持注册和注销文件描述符,支持对描述符进行轮询以获取 I/O 事件。请参阅下方 Poll 对象 获取 poll 对象所支持的方法。

select.kqueue()

(仅支持 BSD)返回一个内核队列对象,请参阅下方 Kqueue 对象 获取 kqueue 对象所支持的方法。

新的文件描述符是 不可继承的。

在 3.4 版更改: 新的文件描述符现在是不可继承的。

select.kevent(ident, filter=KQ_FILTER_READ, flags=KQ_EV_ADD, fflags=0, data=0, udata=0)

(仅支持 BSD)返回一个内核事件对象,请参阅下方 Kevent 对象 获取 kevent 对象所支持的方法。

select.select(rlist, wlist, xlist[, timeout])

这是一个明白直观的 Unix select() 系统调用接口。 前三个参数是由‘可等待对象’组成的序列:可以是代表文件描述符的整数,或是带有名为 fileno() 的返回这样的整数的无形参方法的对象:

  • rlist:等待,直到可以开始读取
  • wlist:等待,直到可以开始写入
  • xlist:等待“异常情况”(请参阅当前系统的手册,以获取哪些情况称为异常情况)

允许空的可迭代对象,但是否接受三个空的可迭代对象则取决于具体平台。 (已知在 Unix 上可行但在 Windows 上不可行。) 可选的 timeout 参数以一个浮点数表示超时秒数。 当省略 timeout 参数时该函数将阻塞直到至少有一个文件描述符准备就绪。 超时值为零表示执行轮询且永不阻塞。

返回值是三个列表,包含已就绪对象,返回的三个列表是前三个参数的子集。当超时时间已到且没有文件描述符就绪时,返回三个空列表。

可迭代对象中可接受的对象类型有 Python 文件对象 (例如 sys.stdin 以及 open()os.popen() 所返回的对象),由 socket.socket() 返回的套接字对象等。 你也可以自定义一个 wrapper 类,只要它具有适当的 fileno() 方法(该方法要确实返回一个文件描述符,而不能只是一个随机整数)。

注解

Windows 上不接受文件对象,但接受套接字。在 Windows 上,底层的 select() 函数由 WinSock 库提供,且不处理不是源自 WinSock 的文件描述符。

在 3.5 版更改: 现在,当本函数被信号中断时,重试超时将从头开始计时,不会抛出 InterruptedError 异常。除非信号处理程序抛出异常(相关原理请参阅 PEP 475)。

select.PIPE_BUF

当一个管道已经被 select()poll() 或本模块中的某个接口报告为可写入时,可以在不阻塞该管道的情况下写入的最小字节数。它不适用于套接字等其他类型的文件类对象。

POSIX 上须保证该值不小于 512。

可用性: Unix

3.2 新版功能.

/dev/poll 轮询对象

Solaris 及其衍生版本具备 /dev/pollselect() 复杂度为 O(最高文件描述符),poll() 为 O(文件描述符数量),而 /dev/poll 为 O(活动的文件描述符)。

/dev/poll 的行为与标准 poll() 对象十分类似。

devpoll.close()

关闭轮询对象的文件描述符。

3.4 新版功能.

devpoll.closed

如果轮询对象已关闭,则返回 True

3.4 新版功能.

devpoll.fileno()

返回轮询对象的文件描述符对应的数字。

3.4 新版功能.

devpoll.register(fd[, eventmask])

在轮询对象中注册文件描述符。这样,将来调用 poll() 方法时将检查文件描述符是否有未处理的 I/O 事件。fd 可以是整数,也可以是带有 fileno() 方法的对象(该方法返回一个整数)。文件对象已经实现了 fileno(),因此它们也可以用作参数。

eventmask 是可选的位掩码,用于指定要检查的事件类型。这些常量与 poll() 对象所用的相同。本参数的默认值是常量 POLLINPOLLPRIPOLLOUT 的组合。

警告

注册已注册过的文件描述符不会报错,但是结果是不确定的。正确的操作是先注销或直接修改它。与 poll() 相比,这是一个重要的区别。

devpoll.modify(fd[, eventmask])

此方法先执行 unregister() 后执行 register()。直接执行此操作效率(稍微)高一些。

devpoll.unregister(fd)

删除轮询对象正在跟踪的某个文件描述符。与 register() 方法类似,fd 可以是整数,也可以是带有 fileno() 方法的对象(该方法返回一个整数)。

尝试删除从未注册过的文件描述符将被安全地忽略。

devpoll.poll([timeout])

轮询已注册的文件描述符的集合,并返回一个列表,列表可能为空,也可能有多个 (fd, event) 二元组,其中包含了要报告事件或错误的描述符。fd 是文件描述符,event 是一个位掩码,表示该描述符所报告的事件 —- POLLIN 表示可以读取,POLLOUT 表示该描述符可以写入,依此类推。空列表表示调用超时,没有任何文件描述符报告事件。如果指定了 timeout*,它将指定系统等待事件时,等待多长时间后返回(以毫秒为单位)。如果 *timeout 为空,-1 或 None,则本调用将阻塞,直到轮询对象发生事件为止。

在 3.5 版更改: 现在,当本函数被信号中断时,重试超时将从头开始计时,不会抛出 InterruptedError 异常。除非信号处理程序抛出异常(相关原理请参阅 PEP 475)。

边缘触发和水平触发的轮询 (epoll) 对象

https://linux.die.net/man/4/epoll

eventmask

常量 含意
EPOLLIN 可读
EPOLLOUT 可写
EPOLLPRI 紧急数据读取
EPOLLERR 在关联的文件描述符上有错误情况发生
EPOLLHUP 关联的文件描述符已挂起
EPOLLET 设置触发方式为边缘触发,默认为水平触发
EPOLLONESHOT 设置 one-shot 模式。触发一次事件后,该描述符会在轮询对象内部被禁用。
EPOLLEXCLUSIVE 当已关联的描述符发生事件时,仅唤醒一个 epoll 对象。默认(如果未设置此标志)是唤醒所有轮询该描述符的 epoll 对象。
EPOLLRDHUP 流套接字的对侧关闭了连接或关闭了写入到一半的连接。
EPOLLRDNORM 等同于 EPOLLIN
EPOLLRDBAND 可以读取优先数据带。
EPOLLWRNORM 等同于 EPOLLOUT
EPOLLWRBAND 可以写入优先级数据。
EPOLLMSG 忽略

3.6 新版功能: 增加了 EPOLLEXCLUSIVE。仅支持 Linux Kernel 4.5 或更高版本。

epoll.close()

关闭用于控制 epoll 对象的文件描述符。

epoll.closed

如果 epoll 对象已关闭,则返回 True

epoll.fileno()

返回文件描述符对应的数字,该描述符用于控制 epoll 对象。

epoll.fromfd(fd)

根据给定的文件描述符创建 epoll 对象。

epoll.register(fd[, eventmask])

在 epoll 对象中注册一个文件描述符。

epoll.modify(fd, eventmask)

修改一个已注册的文件描述符。

epoll.unregister(fd)

从 epoll 对象中删除一个已注册的文件描述符。

在 3.9 版更改: 此方法不会再忽略 EBADF 错误。

epoll.poll(timeout=None, maxevents=- 1)

等待事件发生,timeout 是浮点数,单位为秒。

在 3.5 版更改: 现在,当本函数被信号中断时,重试超时将从头开始计时,不会抛出 InterruptedError 异常。除非信号处理程序抛出异常(相关原理请参阅 PEP 475)。

Poll 对象

大多数 Unix 系统支持 poll() 系统调用,为服务器提供了更好的可伸缩性,使服务器可以同时服务于大量客户端。poll() 的伸缩性更好,因为该调用内部仅列出所关注的文件描述符,而 select() 会构造一个 bitmap,在其中将所关注的描述符所对应的 bit 打开,然后重新遍历整个 bitmap。因此 select() 复杂度是 O(最高文件描述符),而 poll() 是 O(文件描述符数量)。

poll.register(fd[, eventmask])

在轮询对象中注册文件描述符。这样,将来调用 poll() 方法时将检查文件描述符是否有未处理的 I/O 事件。fd 可以是整数,也可以是带有 fileno() 方法的对象(该方法返回一个整数)。文件对象已经实现了 fileno(),因此它们也可以用作参数。

eventmask 是可选的位掩码,用于指定要检查的事件类型,它可以是常量 POLLINPOLLPRIPOLLOUT 的组合,如下表所述。如果未指定本参数,默认将会检查所有 3 种类型的事件。

常量 含意
POLLIN 有要读取的数据
POLLPRI 有紧急数据需要读取
POLLOUT 准备输出:写不会阻塞
POLLERR 某种错误条件
POLLHUP 挂起
POLLRDHUP 流套接字的对侧关闭了连接,或关闭了写入到一半的连接
POLLNVAL 无效的请求:描述符未打开

注册已注册过的文件描述符不会报错,且等同于只注册一次该描述符。

poll.modify(fd, eventmask)

修改一个已注册的文件描述符,等同于 register(fd, eventmask)。尝试修改未注册的文件描述符会抛出 OSError 异常,错误码为 ENOENT

poll.unregister(fd)

删除轮询对象正在跟踪的某个文件描述符。与 register() 方法类似,fd 可以是整数,也可以是带有 fileno() 方法的对象(该方法返回一个整数)。

尝试删除从未注册过的文件描述符会抛出 KeyError 异常。

poll.poll([timeout])

轮询已注册的文件描述符的集合,并返回一个列表,列表可能为空,也可能有多个 (fd, event) 二元组,其中包含了要报告事件或错误的描述符。fd 是文件描述符,event 是一个位掩码,表示该描述符所报告的事件 —- POLLIN 表示可以读取,POLLOUT 表示该描述符可以写入,依此类推。空列表表示调用超时,没有任何文件描述符报告事件。如果指定了 timeout*,它将指定系统等待事件时,等待多长时间后返回(以毫秒为单位)。如果 *timeout 为空、负数 或 None,则本调用将阻塞,直到轮询对象发生事件为止。

在 3.5 版更改: 现在,当本函数被信号中断时,重试超时将从头开始计时,不会抛出 InterruptedError 异常。除非信号处理程序抛出异常(相关原理请参阅 PEP 475)。

Kqueue 对象

kqueue.close()

关闭用于控制 kqueue 对象的文件描述符。

kqueue.closed

如果 kqueue 对象已关闭,则返回 True

kqueue.fileno()

返回文件描述符对应的数字,该描述符用于控制 epoll 对象。

kqueue.fromfd(fd)

根据给定的文件描述符创建 kqueue 对象。

kqueue.control(changelist, max_events[, timeout]) → eventlist

Kevent 的低级接口

  • changelist 必须是一个可迭代对象,迭代出 kevent 对象,否则置为 None
  • max_events 必须是 0 或一个正整数。
  • timeout 单位为秒(一般为浮点数),默认为 None,即永不超时。

在 3.5 版更改: 现在,当本函数被信号中断时,重试超时将从头开始计时,不会抛出 InterruptedError 异常。除非信号处理程序抛出异常(相关原理请参阅 PEP 475)。

Kevent 对象

https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2

kevent.ident

用于区分事件的标识值。其解释取决于筛选器,但该值通常是文件描述符。在构造函数中,该标识值可以是整数或带有 fileno() 方法的对象。kevent 在内部存储整数。

kevent.filter

内核筛选器的名称。

常量 含意
KQ_FILTER_READ 获取描述符,并在有数据可读时返回
KQ_FILTER_WRITE 获取描述符,并在有数据可写时返回
KQ_FILTER_AIO AIO 请求
KQ_FILTER_VNODE 当在 fflag 中监视的一个或多个请求事件发生时返回
KQ_FILTER_PROC 监视进程ID上的事件
KQ_FILTER_NETDEV Watch for events on a network device [not available on macOS]
KQ_FILTER_SIGNAL 每当监视的信号传递到进程时返回
KQ_FILTER_TIMER 建立一个任意的计时器
kevent.flags

筛选器操作。

常量 含意
KQ_EV_ADD 添加或修改事件
KQ_EV_DELETE 从队列中删除事件
KQ_EV_ENABLE Permitscontrol() 返回事件
KQ_EV_DISABLE 禁用事件
KQ_EV_ONESHOT 在第一次发生后删除事件
KQ_EV_CLEAR 检索事件后重置状态
KQ_EV_SYSFLAGS 内部事件
KQ_EV_FLAG1 内部事件
KQ_EV_EOF 筛选特定EOF条件
KQ_EV_ERROR 请参阅返回值
kevent.fflags

筛选特定标志。

KQ_FILTER_READKQ_FILTER_WRITE 筛选标志:

常量 含意
KQ_NOTE_LOWAT 套接字缓冲区的低水线

KQ_FILTER_VNODE 筛选标志:

常量 含意
KQ_NOTE_DELETE 已调用 unlink()
KQ_NOTE_WRITE 发生写入
KQ_NOTE_EXTEND 文件已扩展
KQ_NOTE_ATTRIB 属性已更改
KQ_NOTE_LINK 链接计数已更改
KQ_NOTE_RENAME 文件已重命名
KQ_NOTE_REVOKE 对文件的访问权限已被撤销

KQ_FILTER_PROC filter flags:

常量 含意
KQ_NOTE_EXIT 进程已退出
KQ_NOTE_FORK 该进程调用了 fork()
KQ_NOTE_EXEC 进程已执行新进程
KQ_NOTE_PCTRLMASK 内部筛选器标志
KQ_NOTE_PDATAMASK 内部筛选器标志
KQ_NOTE_TRACK fork() 执行进程
KQ_NOTE_CHILD NOTE_TRACK 的子进程上返回
KQ_NOTE_TRACKERR 无法附加到子对象

KQ_FILTER_NETDEV filter flags (not available on macOS):

常量 含意
KQ_NOTE_LINKUP 链接已建立
KQ_NOTE_LINKDOWN 链接已断开
KQ_NOTE_LINKINV 链接状态无效
kevent.data

筛选特定数据。

kevent.udata

用户自定义值。

selectors —- 高级 I/O 复用库

3.4 新版功能.

源码: Lib/selectors.py


概述

此模块允许高层级且高效率的 I/O 复用,它建立在 select 模块原型的基础之上。 推荐用户改用此模块,除非他们希望对所使用的 OS 层级原型进行精确控制。

它定义了一个 BaseSelector 抽象基类,以及多个实际的实现 (KqueueSelector, EpollSelector…),它们可被用于在多个文件对象上等待 I/O 就绪通知。 在下文中,”文件对象” 是指任何具有 fileno() 方法的对象,或是一个原始文件描述器。

DefaultSelector 是一个指向当前平台上可用的最高效实现的别名:这应为大多数用户的默认选择。

注解

受支持的文件对象类型取决于具体平台:在 Windows 上,支持套接字但不支持管道,而在 Unix 上两者均受支持(某些其他类型也可能受支持,例如 fifo 或特殊文件设备等)。

类的层次结构:

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

下文中,events 一个位掩码,指明哪些 I/O 事件要在给定的文件对象上执行等待。 它可以是以下模块级常量的组合:

常量 含意
EVENT_READ 可读
EVENT_WRITE 可写

class selectors.SelectorKey

SelectorKey 是一个 namedtuple,用来将文件对象关联到其下层的文件描述器、选定事件掩码和附加数据等。 它会被某些 BaseSelector 方法返回。

  • fileobj

    已注册的文件对象。

  • fd

    下层的文件描述器。

  • events

    必须在此文件对象上被等待的事件。

  • data

    可选的关联到此文件对象的不透明数据:例如,这可被用来存储各个客户端的会话 ID。

class selectors.BaseSelector

一个 BaseSelector,用来在多个文件对象上等待 I/O 事件就绪。 它支持文件流注册、注销,以及在这些流上等待 I/O 事件的方法。 它是一个抽象基类,因此不能被实例化。 请改用 DefaultSelector,或者 SelectSelector, KqueueSelector 等。 如果你想要指明使用某个实现,并且你的平台支持它的话。 BaseSelector 及其具体实现支持 context manager 协议。

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

    注册一个用于选择的文件对象,在其上监视 I/O 事件。

    fileobj 是要监视的文件对象。 它可以是整数形式的文件描述符或者具有 fileno() 方法的对象。 events 是要监视的事件的位掩码。 data 是一个不透明对象。

    这将返回一个新的 SelectorKey 实例,或在出现无效事件掩码或文件描述符时引发 ValueError,或在文件对象已被注册时引发 KeyError

  • abstractmethod unregister(fileobj)

    注销对一个文件对象的选择,移除对它的监视。 在文件对象被关闭之前应当先将其注销。

    fileobj 必须是之前已注册的文件对象。

    这将返回已关联的 SelectorKey 实例,或者如果 fileobj 未注册则会引发 KeyError。 It will raise ValueError 如果 fileobj 无效(例如它没有 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,调用将不会阻塞,并将报告当前就绪的文件对象。 如果 timeoutNone,调用将阻塞直到某个被监视的文件对象就绪。

    这将返回由 (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() 对象所使用的文件描述符。

例子

下面是一个简单的回显服务器实现:

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)

asyncore —- 异步套接字处理器

源码: Lib/asyncore.py

3.6 版后已移除: 请使用 asyncio 替代。


注解

该模块仅为提供向后兼容。我们推荐在新代码中使用 asyncio

该模块提供用于编写异步套接字服务客户端与服务端的基础构件。

只有两种方法让单个处理器上的程序“同一时间完成不止一件事”。 多线程编程是最简单和最流行的方法,但是还有另一种非常不同的技术,它可以让你拥有多线程的几乎所有优点,而无需实际使用多线程。 它仅仅在你的程序主要受 I/O 限制时有用,那么。 如果你的程序受处理器限制,那么先发制人的预定线程可能就是你真正需要的。 但是,网络服务器很少受处理器限制。

如果你的操作系统在其 I/O 库中支持 select() 系统调用(几乎所有操作系统),那么你可以使用它来同时处理多个通信通道;在 I/O 正在“后台”时进行其他工作。 虽然这种策略看起来很奇怪和复杂,特别是起初,它在很多方面比多线程编程更容易理解和控制。 asyncore 模块为您解决了许多难题,使得构建复杂的高性能网络服务器和客户端的任务变得轻而易举。 对于“会话”应用程序和协议,伴侣 asynchat 模块是非常宝贵的。

这两个模块背后的基本思想是创建一个或多个网络 通道 ,类的实例 asyncore.dispatcherasynchat.async_chat 。 创建通道会将它们添加到全局映射中,如果你不为它提供自己的 映射 ,则由 loop() 函数使用。

一旦创建了初始通道,调用 loop() 函数将激活通道服务,该服务将一直持续到最后一个通道(包括在异步服务期间已添加到映射中的任何通道)关闭。

asyncore.loop([timeout[, use_poll[, map[, count]]]])

进入一个轮询循环,其在循环计数超出或所有打开的通道关闭后终止。 所有参数都是可选的。 count 形参默认为 None ,导致循环仅在所有通道关闭时终止。 timeout 形参为适当的 select()poll() 调用设置超时参数,以秒为单位; 默认值为30秒。 use_poll 形参,如果为 True ,则表示 poll() 应优先使用 select() (默认为False)。

map 形参是一个条目为所监视通道的字典。 当通道关闭时它们会被从映射中删除。 如果省略 map,则会使用一个全局映射。 通道 (asyncore.dispatcher, asynchat.async_chat 及其子类的实例) 可以在映射中任意混合。

class asyncore.dispatcher

dispatcher 类是对低层级套接字对象的轻量包装器。 要让它更有用处,可以从异步循环调用一些事件处理方法。 在其他方面,它可以被当作是普通的非阻塞型套接字对象。

在特定时间或特定连接状态下触发的低层级事件可通知异步循环发生了特定的高层级事件。 例如,如果我们请求了一个套接字以连接到另一台主机,我们会在套接字首次变得可写时得知连接已建立(在此刻你将知道可以向其写入并预期能够成功)。 包含的高层级事件有:

事件 描述
handle_connect() 由首个读取或写入事件引起
handle_close() 由不带可用数据的读取事件引起
handle_accepted() 由在监听套接字上的读取事件引起

在异步处理过程中,每个已映射通道的 readable()writable() 方法会被用来确定是否要将通道的套接字添加到已执行 select()poll() 用于读取和写入事件的通道列表中。

因此,通道事件的集合要大于基本套接字事件。 可以在你的子类中被重载的全部方法集合如下:

  • handle_read()

    当异步循环检测到通道的套接字上的 read() 调用将要成功时会被调用。

  • handle_write()

    当异步循环检测到一个可写套接字可以被写入时会被调用。 通常此方法将实现必要的缓冲机制以保证运行效率。 例如:

    def handle_write(self):
        sent = self.send(self.buffer)
        self.buffer = self.buffer[sent:]
  • handle_expt()

    当一个套接字连接存在带外(OOB)数据时会被调用。 这几乎从来不会发生,因为 OOB 虽然受支持但很少被使用。

  • handle_connect()

    当活动打开方的套接字实际建立连接时会被调用。 可能会发送一条“欢迎”消息,或者向远程端点发起协议协商等。

  • handle_close()

    当套接字关闭时会被调用。

  • handle_error()

    当一个异常被引发并且未获得其他处理时会被调用。 默认版本将打印精简的回溯信息。

  • handle_accept()

    当可以与发起对本地端点的 connect() 调用的新远程端点建立连接时会在侦听通道(被动打开方)上被调用。 在 3.2 版中已被弃用;请改用 handle_accepted()

    3.2 版后已移除.

  • handle_accepted(sock, addr)

    当与发起对本地端点的 connect() 调用的新远程端点已建立连接时会在侦听通道(被动打开方)上被调用。 sock 是可被用于在连接上发送和接收数据的 新建 套接字对象,而 addr 是绑定到连接另一端的套接字的地址。

    3.2 新版功能.

  • readable()

    每次在异步循环之外被调用以确定是否应当将一个通道的套接字添加到可能在其上发生读取事件的列表中。 默认方法会简单地返回 True,表示在默认情况下,所有通道都希望能读取事件。

  • writable()

    每次在异步循环之外被调用以确定是否应当将一个通道的套接字添加到可能在其上发生写入事件的列表中。 默认方法会简单地返回 True,表示在默认情况下,所有通道都希望能写入事件。

此外,每个通道都委托或扩展了许多套接字方法。 它们大部分都与其套接字的对应方法几乎一样。

  • create_socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

    这与普通套接字的创建相同,并会使用同样的创建选项。

    在 3.3 版更改: familytype 参数可以被省略。

  • connect(address)

    与普通套接字对象一样,address 是一个元组,它的第一个元素是要连接的主机,第二个元素是端口号。

  • send(data)

    data 发送到套接字的远程端点。

  • recv(buffer_size)

    从套接字的远程端点读取至多 buffer_size 个字节。 读到空字节串表明通道已从另一端被关闭。

    请注意 recv() 可能会引发 BlockingIOError,即使 select.select()select.poll() 报告套接字已准备好被读取。

  • listen(backlog)

    侦听与套接字的连接。 backlog 参数指明排入连接队列的最大数量且至少应为 1;最大值取决于具体系统(通常为 5)。

  • bind(address)

    将套接字绑定到 address。 套接字必须尚未被绑定。 要将套接字标记为可重用的 (设置 SO_REUSEADDR 选项),请调用 dispatcher 对象的 set_reuse_addr() 方法。

  • accept()

    接受一个连接。 此套接字必须绑定到一个地址上并且侦听连接。 返回值可以是 None 或一个 (conn, address) 对,其中 conn 是一个可用来在此连接上发送和接收数据的 新的 套接字对象,而 address 是绑定到连接另一端套接字的地址。 当返回 None 时意味着连接没有建立,在此情况下服务器应当忽略此事件并继续侦听后续的入站连接。

  • close()

    关闭套接字。 在此套接字对象上的后续操作都将失败。 远程端点将不再接收任何数据(在排入队列的数据被清空之后)。 当套接字被垃圾回收时会自动关闭。

class asyncore.dispatcher_with_send

dispatcher 的一个添加了简单缓冲输出功能的子类,适用于简单客户端。 对于更复杂的用法请使用 asynchat.async_chat

class asyncore.file_dispatcher

file_dispatcher 接受一个文件描述符或 file object 以及一个可选的 map 参数,并对其进行包装以配合 poll()loop() 函数使用。 如果提供一个文件对象或任何具有 fileno() 方法的对象,其方法将被调用并传递给 file_wrapper 构造器。

可用性: Unix。

class asyncore.file_wrapper

file_wrapper 接受一个整数形式的文件描述符并调用 os.dup() 来复制其句柄,以便原始句柄可以独立于 file_wrapper 被关闭。 这个类实现了足够的方法来模拟套接字以供 file_dispatcher 类使用。

可用性: Unix。

asyncore 示例基本 HTTP 客户端

下面是一个非常基本的 HTTP 客户端,它使用了 dispatcher 类来实现套接字处理:

import asyncore
class HTTPClient(asyncore.dispatcher):
    def __init__(self, host, path):
        asyncore.dispatcher.__init__(self)
        self.create_socket()
        self.connect( (host, 80) )
        self.buffer = bytes('GET %s HTTP/1.0\r\nHost: %s\r\n\r\n' %
                            (path, host), 'ascii')
    def handle_connect(self):
        pass
    def handle_close(self):
        self.close()
    def handle_read(self):
        print(self.recv(8192))
    def writable(self):
        return (len(self.buffer) > 0)
    def handle_write(self):
        sent = self.send(self.buffer)
        self.buffer = self.buffer[sent:]
client = HTTPClient('www.python.org', '/')
asyncore.loop()

asyncore 示例基本回显服务器

下面是一个基本的回显服务器,它使用了 dispatcher 类来接受连接并将入站连接发送给处理程序:

import asyncore
class EchoHandler(asyncore.dispatcher_with_send):
    def handle_read(self):
        data = self.recv(8192)
        if data:
            self.send(data)
class EchoServer(asyncore.dispatcher):
    def __init__(self, host, port):
        asyncore.dispatcher.__init__(self)
        self.create_socket()
        self.set_reuse_addr()
        self.bind((host, port))
        self.listen(5)
    def handle_accepted(self, sock, addr):
        print('Incoming connection from %s' % repr(addr))
        handler = EchoHandler(sock)
server = EchoServer('localhost', 8080)
asyncore.loop()

asynchat —- 异步套接字指令/响应处理程序

源代码: Lib/asynchat.py

3.6 版后已移除: 请使用 asyncio 替代。


注解

该模块仅为提供向后兼容。我们推荐在新代码中使用 asyncio

此模块在 asyncore 框架之上构建,简化了异步客户端和服务器并使得处理元素为以任意字符串结束或者为可变长度的协议更加容易。 asynchat 定义了一个可以由你来子类化的抽象类 async_chat,提供了 collect_incoming_data()found_terminator() 等方法的实现。 它使用与 asyncore 相同的异步循环,并且可以在通道映射中自由地混合 asyncore.dispatcherasynchat.async_chat 这两种类型的通道。 一般来说 asyncore.dispatcher 服务器通道在接收到传入的连接请求时会生成新的 asynchat.async_chat 通道对象。

class asynchat.async_chat

这个类是 asyncore.dispatcher 的抽象子类。 对于实际使用的代码你必须子类化 async_chat,提供有意义的 collect_incoming_data()found_terminator() 方法。 asyncore.dispatcher 的方法也可以被使用,但它们在消息/响应上下文中并不是全都有意义。

asyncore.dispatcher 类似,async_chat 也定义了一组通过对 select() 调用之后的套接字条件进行分析所生成的事件。 一旦启动轮询循环 async_chat 对象的方法就会被事件处理框架调用而无须程序员方面做任何操作。

两个可被修改的类属性,用以提升性能,甚至也可能会节省内存。

  • ac_in_buffer_size

    异步输入缓冲区大小 (默认为 4096)。

  • ac_out_buffer_size

    异步输出缓冲区大小 (默认为 4096)。

asyncore.dispatcher 不同,async_chat 允许你定义一个 FIFO 队列 producers。 其中的生产者只需要一个方法 more(),该方法应当返回要在通道上传输的数据。 生产者通过让其 more() 方法返回空字节串对象来表明其处于耗尽状态 (意即 它已不再包含数据)。 此时 async_chat 对象会将该生产者从队列中移除并开始使用下一个生产者,如果有下一个的话。 当生产者队列为空时 handle_write() 方法将不执行任何操作。 你要使用通道对象的 set_terminator() 方法来描述如何识别来自远程端点的入站传输的结束或是重要的中断点。

要构建一个可用的 async_chat 子类,你的输入方法 collect_incoming_data()found_terminator() 必须要处理通道异步接收的数据。 这些参数的描述见下文。

async_chat.close_when_done()

None 推入生产者队列。 当此生产者被弹出队列时它将导致通道被关闭。

async_chat.collect_incoming_data(data)

调用时附带 data,其中包含任意数量的已接收数据。 必须被重载的默认方法将引发一个 NotImplementedError 异常。

async_chat.discard_buffers()

在紧急情况下此方法将丢弃输入和/或输出缓冲区以及生产者队列中的任何数据。

async_chat.found_terminator()

当输入数据流能匹配 set_terminator() 所设定的终结条件时会被调用。 必须被重载的默认方法将引发一个 NotImplementedError 异常。 被缓冲的输入数据应当可以通过实例属性来获取。

async_chat.get_terminator()

返回通道的当前终结器。

async_chat.push(data)

将数据推入通道的队列以确保其被传输。 要让通道将数据写到网络中你只需要这样做就足够了,虽然以更复杂的方式使用你自己的生产者也是有可能的,例如为了实现加密和分块。

async_chat.push_with_producer(producer)

获取一个生产者对象并将其加入到与通道相关联的生产者队列中。 当所有当前已推入的生产者都已被耗尽时通道将通过调用其 more() 方法来耗用此生产者的数据并将数据发送至远程端点。

async_chat.set_terminator(term)

设置可在通道上被识别的终结条件。 term 可以是三种类型值中的任意一种 ,对应于处理入站协议数据的三种不同方式。

term 描述
string 当在输入流中发现该字符串时将会调用 found_terminator()
integer 当接收到指定数量的字符时将会调用 found_terminator()
None 通道会不断地持续收集数据

请注意终结器之后的任何数据将可在 found_terminator() 被调用后由通道来读取。

asynchat 示例

下面的例子片段显示了如何通过 async_chat 来读取 HTTP 请求。 Web 服务器可以为每个入站的客户端连接创建 http_request_handler 对象。 请注意在初始时通道终结器会被设置为匹配 HTTP 标头末尾的空行,并且会用一个旗标来指明标头正在被读取。

一旦完成了标头的读取,如果请求类型为 POST (表明输入流中存在更多的数据) 则会使用 Content-Length: 标头来设置一个数值终结器以从通道读取适当数量的数据。

一旦完成了对所有相关输入的处理,将会在设置通道终结器为 None 以确保忽略掉 Web 客户端所发送的任何无关数据之后调用 handle_request() 方法。

import asynchat
class http_request_handler(asynchat.async_chat):
    def __init__(self, sock, addr, sessions, log):
        asynchat.async_chat.__init__(self, sock=sock)
        self.addr = addr
        self.sessions = sessions
        self.ibuffer = []
        self.obuffer = b""
        self.set_terminator(b"\r\n\r\n")
        self.reading_headers = True
        self.handling = False
        self.cgi_data = None
        self.log = log
    def collect_incoming_data(self, data):
        """Buffer the data"""
        self.ibuffer.append(data)
    def found_terminator(self):
        if self.reading_headers:
            self.reading_headers = False
            self.parse_headers(b"".join(self.ibuffer))
            self.ibuffer = []
            if self.op.upper() == b"POST":
                clen = self.headers.getheader("content-length")
                self.set_terminator(int(clen))
            else:
                self.handling = True
                self.set_terminator(None)
                self.handle_request()
        elif not self.handling:
            self.set_terminator(None)  # browsers sometimes over-send
            self.cgi_data = parse(self.headers, b"".join(self.ibuffer))
            self.handling = True
            self.ibuffer = []
            self.handle_request()

signal —- 设置异步事件处理程序

该模块提供了在 Python 中使用信号处理程序的机制。

一般规则

signal.signal() 函数允许定义在接收到信号时执行的自定义处理程序。少量的默认处理程序已经设置: SIGPIPE 被忽略(因此管道和套接字上的写入错误可以报告为普通的 Python 异常)以及如果父进程没有更改 SIGINT ,则其会被翻译成 KeyboardInterrupt 异常。

一旦设置,特定信号的处理程序将保持安装,直到它被显式重置( Python 模拟 BSD 样式接口而不管底层实现),但 SIGCHLD 的处理程序除外,它遵循底层实现。

执行 Python 信号处理程序

Python 信号处理程序不会在低级( C )信号处理程序中执行。相反,低级信号处理程序设置一个标志,告诉 virtual machine 稍后执行相应的 Python 信号处理程序(例如在下一个 bytecode 指令)。这会导致:

  • 捕获同步错误是没有意义的,例如 SIGFPESIGSEGV ,它们是由 C 代码中的无效操作引起的。Python 将从信号处理程序返回到 C 代码,这可能会再次引发相同的信号,导致 Python 显然的挂起。 从Python 3.3开始,你可以使用 faulthandler 模块来报告同步错误。
  • 纯 C 中实现的长时间运行的计算(例如在大量文本上的正则表达式匹配)可以在任意时间内不间断地运行,而不管接收到任何信号。计算完成后将调用 Python 信号处理程序。

信号与线程

Python 信号处理程序总是会在主 Python 主解释器的主线程中执行,即使信号是在另一个线程中接收的。 这意味着信号不能被用作线程间通信的手段。 你可以改用 threading 模块中的同步原语。

此外,只有主解释器的主线程才被允许设置新的信号处理程序。

模块内容

在 3.5 版更改: 信号( SIG* ),处理程序( SIG_DFLSIG_IGN)和 sigmask( SIG_BLOCKSIG_UNBLOCKSIG_SETMASK )下面列出的相关常量变成了 enumsgetsignal()pthread_sigmask()sigpending()sigwait() 函数返回人类可读的 enums

signal 模块中定义的变量是:

signal.SIG_DFL

这是两种标准信号处理选项之一;它只会执行信号的默认函数。 例如,在大多数系统上,对于 SIGQUIT 的默认操作是转储核心并退出,而对于 SIGCHLD 的默认操作是简单地忽略它。

signal.SIG_IGN

这是另一个标准信号处理程序,它将简单地忽略给定的信号。

signal.SIGABRT

来自 abort(3) 的中止信号。

signal.SIGALRM

来自 alarm(2) 的计时器信号。

可用性: Unix。

signal.SIGBREAK

来自键盘的中断 (CTRL + BREAK)。

可用性: Windows。

signal.SIGBUS

总线错误 (非法的内存访问)。

可用性: Unix。

signal.SIGCHLD

子进程被停止或终结。

可用性: Unix。

signal.SIGCLD

SIGCHLD 的别名。

signal.SIGCONT

如果进程当前已停止则继续执行它

可用性: Unix。

signal.SIGFPE

浮点异常。 例如除以零。

参见

当除法或求余运算的第二个参数为零时会引发 ZeroDivisionError

signal.SIGHUP

在控制终端上检测到挂起或控制进程的终止。

可用性: Unix。

signal.SIGILL

非法指令。

signal.SIGINT

来自键盘的中断 (CTRL + C)。

默认的动作是引发 KeyboardInterrupt

signal.SIGKILL

终止信号。

它不能被捕获、阻塞或忽略。

可用性: Unix。

signal.SIGPIPE

损坏的管道:写入到没有读取器的管道。

默认的动作是忽略此信号。

可用性: Unix。

signal.SIGSEGV

段错误:无效的内存引用。

signal.SIGTERM

终结信号。

signal.SIGUSR1

用户自定义信号 1。

可用性: Unix。

signal.SIGUSR2

用户自定义信号 2。

可用性: Unix。

signal.SIGWINCH

窗口调整大小信号。

可用性: Unix。

SIG*

所有信号编号都是符号化定义的。 例如,挂起信号被定义为 signal.SIGHUP;变量的名称与 C 程序中使用的名称相同,具体见 <signal.h>。 ‘signal()‘ 的 Unix 手册页面列出了现有的信号 (在某些系统上这是 signal(2)*,在其他系统中此列表则是在 *signal(7) 中)。 请注意并非所有系统都会定义相同的信号名称集;只有系统所定义的名称才会由此模块来定义。

signal.CTRL_C_EVENT

对应于 Ctrl+C 击键事件的信号。此信号只能用于 os.kill()

可用性: Windows。

3.2 新版功能.

signal.CTRL_BREAK_EVENT

对应于 Ctrl+Break 击键事件的信号。此信号只能用于 os.kill()

可用性: Windows。

3.2 新版功能.

signal.NSIG

比最高信号数多一。

signal.ITIMER_REAL

实时递减间隔计时器,并在到期时发送 SIGALRM

signal.ITIMER_VIRTUAL

仅在进程执行时递减间隔计时器,并在到期时发送 SIGVTALRM 。

signal.ITIMER_PROF

当进程执行时以及当系统替进程执行时都会减小间隔计时器。 这个计时器与 ITIMER_VIRTUAL 相配结,通常被用于分析应用程序在用户和内核空间中花费的时间。 SIGPROF 会在超期时被发送。

signal.SIG_BLOCK

pthread_sigmask()how 形参的一个可能的值,表明信号将会被阻塞。

3.3 新版功能.

signal.SIG_UNBLOCK

pthread_sigmask()how 形参的是个可能的值,表明信号将被解除阻塞。

3.3 新版功能.

signal.SIG_SETMASK

pthread_sigmask()how 形参的一个可能的值,表明信号掩码将要被替换。

3.3 新版功能.

signal 模块定义了一个异常:

exception signal.ItimerError

作为来自下层 setitimer()getitimer() 实现错误的信号被引发。 如果将无效的定时器或负的时间值传给 setitimer() 就导致这个错误。 此错误是 OSError 的子类型。

3.3 新版功能: 此错误是 IOError 的子类型,现在则是 OSError 的别名。

signal 模块定义了以下函数:

signal.alarm(time)

如果 time 值非零,则此函数将要求将一个 SIGALRM 信号在 time 秒内发往进程。 任何在之前排入计划的警报都会被取消(在任何时刻都只能有一个警报被排入计划)。 后续的返回值将是任何之前设置的警报被传入之前的秒数。 如果 time 值为零,则不会将任何警报排入计划,并且任何已排入计划的警报都会被取消。 如果返回值为零,则目前没有任何警报被排入计划。

可用性: Unix。 更多信息请参见手册页面 *alarm(2)*。

signal.getsignal(signalnum)

返回当前用于信号 signalnum 的信号处理程序。 返回值可以是一个 Python 可调用对象,或是特殊值 signal.SIG_IGN, signal.SIG_DFLNone 之一。 在这里,signal.SIG_IGN 表示信号在之前被忽略,signal.SIG_DFL 表示之前在使用默认的信号处理方式,而 None 表示之前的信号处理程序未由 Python 安装。

signal.strsignal(signalnum)

返回信号 signalnum 的系统描述,例如 “Interrupt”, “Segmentation fault” 等等。 如果信号无法被识别则返回 None

3.8 新版功能.

signal.valid_signals()

返回本平台上的有效信号编号集。 这可能会少于 range(1, NSIG),如果某些信号被系统保留作为内部使用的话。

3.8 新版功能.

signal.pause()

使进程休眠直至接收到一个信号;然后将会调用适当的处理程序。 返回空值。

可用性: Unix。 更多信息请参见手册页面 *signal(2)*。

signal.raise_signal(signum)

向调用方进程发送一个信号。 返回空值。

3.8 新版功能.

signal.pidfd_send_signal(pidfd, sig, siginfo=None, flags=0)

发送信号 sig 到文件描述符 pidfd 所指向的进程。 Python 目前不支持 siginfo 形参;它必须为 None。 提供 flags 参数是为了将来扩展;当前未定义旗标值。

更多信息请参阅 pidfd_send_signal(2) 手册页面。

可用性: Linux 5.1+

3.9 新版功能.

signal.pthread_kill(thread_id, signalnum)

将信号 signalnum 发送至与调用者在同一进程中另一线程 thread_id。 目标线程可被用于执行任何代码(Python或其它)。 但是,如果目标线程是在执行 Python 解释器,则 Python 信号处理程序将 由主解释器的主线程来执行。 因此,将信号发送给特定 Python 线程的唯一作用在于强制让一个正在运行的系统调用失败并抛出 InterruptedError

使用 threading.get_ident()threading.Thread 对象的 ident 属性为 thread_id 获取合适的值。

如果 signalnum 为 0,则不会发送信号,但仍然会执行错误检测;这可被用来检测目标线程是否仍在运行。

引发一个 审计事件 signal.pthread_kill,附带参数 thread_id, signalnum

可用性: Unix。 更多信息请参见手册页面 *pthread_kill(3)*。

3.3 新版功能.

signal.pthread_sigmask(how, mask)

获取和/或修改调用方线程的信号掩码。 信号掩码是一组传送过程目前为调用者而阻塞的信号集。 返回旧的信号掩码作为一组信号。

该调用的行为取决于 how 的值,具体见下。

  • SIG_BLOCK: 被阻塞信号集是当前集与 mask 参数的并集。
  • SIG_UNBLOCK: mask 中的信号会从当前已阻塞信号集中被移除。 允许尝试取消对一个非阻塞信号的阻塞。
  • SIG_SETMASK: 已阻塞信号集会被设为 mask 参数的值。

mask 是一个信号编号集合 (例如 {signal.SIGINT, signal.SIGTERM})。 请使用 valid_signals() 表示包含所有信号的完全掩码。

例如,signal.pthread_sigmask(signal.SIG_BLOCK, []) 会读取调用方线程的信号掩码。

SIGKILLSIGSTOP 不能被阻塞。

可用性: Unix。 更多信息请参见手册页面 sigprocmask(2) 和 *pthread_sigmask(3)*。

3.3 新版功能.

signal.setitimer(which, seconds, interval=0.0)

设置由 which 指明的给定间隔计时器 (signal.ITIMER_REAL, signal.ITIMER_VIRTUALsignal.ITIMER_PROF 之一) 在 seconds 秒 (接受浮点数值,为与 alarm() 之差) 之后开始并在每 interval 秒间隔时 (如果 interval 不为零) 启动。 由 which 指明的间隔计时器可通过将 seconds 设为零来清空。

当一个间隔计时器启动时,会有信号发送至进程。 所发送的具体信号取决于所使用的计时器;signal.ITIMER_REAL 将发送 SIGALRM, signal.ITIMER_VIRTUAL 将发送 SIGVTALRM, 而 signal.ITIMER_PROF 将发送 SIGPROF.

原有的值会以元组: (delay, interval) 的形式被返回。

尝试传入无效的计时器将导致 ItimerError

可用性: Unix。

signal.getitimer(which)

返回由 which 指明的给定间隔计时器当前的值。

可用性: Unix。

signal.set_wakeup_fd(fd, **, warn_on_full_buffer=True*)

将唤醒文件描述符设为 fd。 当接收到信号时,会将信号编号以单个字节的形式写入 fd。 这可被其它库用来唤醒一次 poll 或 select 调用,以允许该信号被完全地处理。

原有的唤醒 fd 会被返回(或者如果未启用文件描述符唤醒则返回 -1)。 如果 fd 为 -1,文件描述符唤醒会被禁用。 如果不为 -1,则 fd 必须为非阻塞型。 需要由库来负责在重新调用 poll 或 select 之前从 fd 移除任何字节数据。

当启用线程用时,此函数只能从 主解释器的主线程 被调用;尝试从另一线程调用它将导致 ValueError 异常被引发。

使用此函数有两种通常的方式。 在两种方式下,当有信号到达时你都是用 fd 来唤醒,但之后它们在确定达到的一个或多个信号 which 时存在差异。

在第一种方式下,我们从 fd 的缓冲区读取数据,这些字节值会给你信号编号。 这种方式很简单,但在少数情况下会发生问题:通常 fd 将有缓冲区空间大小限制,如果信号到达得太多且太快,缓冲区可能会爆满,有些信号可能丢失。 如果你使用此方式,则你应当设置 warn_on_full_buffer=True,当信号丢失时这至少能将警告消息打印到 stderr。

在第二种方式下,我们 只会 将唤醒 fd 用于唤醒,而忽略实际的字节值。 在此情况下,我们所关心的只有 fd 的缓冲区为空还是不为空;爆满的缓冲区完全不会导致问题。 如果你使用此方式,则你应当设置 warn_on_full_buffer=False,这样你的用户就不会被虚假的警告消息所迷惑。

在 3.5 版更改: 在 Windows 上,此函数现在也支持套接字处理。

在 3.7 版更改: 添加了 warn_on_full_buffer 形参。

signal.siginterrupt(signalnum, flag)

更改系统调用重启行为:如果 flagFalse,系统调用将在被信号 signalnum 中断时重启,否则系统调用将被中断。 返回空值。

可用性: Unix。 更多信息请参见手册页面 *siginterrupt(3)*。

请注意用 signal() 安装信号处理程序将重启行为重置为可通过显式调用 siginterrupt() 并为给定信号的 flag 设置真值来实现中断。

signal.signal(signalnum, handler)

将信号 signalnum 的处理程序设为函数 handler*。 *handler 可以为接受两个参数(见下)的 Python 可调用对象,或者为特殊值 signal.SIG_IGNsignal.SIG_DFL 之一。 之前的信号处理程序将被返回。 (更多信息请参阅 Unix 手册页面 *signal(2)*。)

当启用线程用时,此函数只能从 主解释器的主线程 被调用;尝试从另一线程调用它将导致 ValueError 异常被引发。

handler 将附带两个参数调用:信号编号和当前堆栈帧 (None 或一个帧对象;有关帧对象的描述请参阅 类型层级结构描述 或者参阅 inspect 模块中的属性描述)。

在 Windows 上,signal() 调用只能附带 SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERMSIGBREAK。 任何其他值都将引发 ValueError。 请注意不是所有系统都定义了同样的信号名称集合;如果一个信号名称未被定义为 SIG* 模块层级常量则将引发 AttributeError

signal.sigpending()

检查正在等待传送给调用方线程的信号集合(即在阻塞期间被引发的信号)。 返回正在等待的信号集合。

可用性: Unix。 更多信息请参见手册页面 *sigpending(2)*。

3.3 新版功能.

signal.sigwait(sigset)

挂起调用方线程的执行直到信号集合 sigset 中指定的信号之一被传送。 此函数会接受该信号(将其从等待信号列表中移除),并返回信号编号。

可用性: Unix。 更多信息请参见手册页面 *sigwait(3)*。

3.3 新版功能.

signal.sigwaitinfo(sigset)

挂起调用方线程的执行直到信号集合 sigset 中指定的信号之一被传送。 此函数会接受该信号并将其从等待信号列表中移除。 如果 sigset 中的信号之一已经在等待调用方线程,此函数将立即返回并附带有关该信号的信息。 被传送信号的信号处理程序不会被调用。 如果该函数被某个不在 sigset 中的信号中断则会引发 InterruptedError

返回值是一个代表 siginfo_t 结构体所包含数据的对象,具体为: si_signo, si_code, si_errno, si_pid, si_uid, si_status, si_band

可用性: Unix。 更多信息请参见手册页面 *sigwaitinfo(2)*。

3.3 新版功能.

在 3.5 版更改: 当被某个 不在 sigset 中的信号中断时本函数将进行重试并且信号处理程序不会引发异常(请参阅 PEP 475 了解其理由)。

signal.sigtimedwait(sigset, timeout)

类似于 sigwaitinfo(),但会接受一个额外的 timeout 参数来指定超时限制。 如果将 timeout 指定为 0,则会执行轮询。 如果发生超时则返回 None

可用性: Unix。 更多信息请参见手册页面 *sigtimedwait(2)*。

3.3 新版功能.

在 3.5 版更改: 现在当此函数被某个不在 sigset 中的信号中断时将以计算出的 timeout 进行重试并且信号处理程序不会引发异常(请参阅 PEP 475 了解其理由)。

示例

这是一个最小示例程序。 它使用 alarm() 函数来限制等待打开一个文件所花费的时间;这在文件为无法开启的串行设备时会很有用处,此情况通常会导致 os.open() 无限期地挂起。 解决办法是在打开文件之前设置 5 秒钟的 alarm;如果操作耗时过长,将会发送 alarm 信号,并且处理程序会引发一个异常。

import signal, os
def handler(signum, frame):
    print('Signal handler called with signal', signum)
    raise OSError("Couldn't open device!")
# Set the signal handler and a 5-second alarm
signal.signal(signal.SIGALRM, handler)
signal.alarm(5)
# This open() may hang indefinitely
fd = os.open('/dev/ttyS0', os.O_RDWR)
signal.alarm(0)          # Disable the alarm

对于 SIGPIPE 的说明

将你的程序用管道输出到工具例如 head(1) 将会导致 SIGPIPE 信号在其标准输出的接收方提前关闭时被发送到你的进程。 这将引发一个异常例如 BrokenPipeError: [Errno 32] Broken pipe。 要处理这种情况,请对你的入口点进行包装以捕获此异常,如下所示:

import os
import sys
def main():
    try:
        # simulate large output (your code replaces this loop)
        for x in range(10000):
            print("y")
        # flush output here to force SIGPIPE to be triggered
        # while inside this try block.
        sys.stdout.flush()
    except BrokenPipeError:
        # Python flushes standard streams on exit; redirect remaining output
        # to devnull to avoid another BrokenPipeError at shutdown
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())
        sys.exit(1)  # Python exits with error code 1 on EPIPE
if __name__ == '__main__':
    main()

不要将 SIGPIPE 的处置方式设为 SIG_DFL 以避免 BrokenPipeError。 这样做还会在你的程序所写入的任何套接字连接中断时导致你的程序异常退出。

mmap —- 内存映射文件支持

内存映射文件对象的行为既像 bytearray 又像 文件对象。 你可以在大部分接受 bytearray 的地方使用 mmap 对象;例如,你可以使用 re 模块来搜索一个内存映射文件。 你也可以通过执行 obj[index] = 97 来修改单个字节,或者通过对切片赋值来修改一个子序列: obj[i1:i2] = b'...'。 你还可以在文件的当前位置开始读取和写入数据,并使用 seek() 前往另一个位置。

内存映射文件是由 mmap 构造函数创建的,其在 Unix 和 Windows 上是不同的。 无论哪种情况,你都必须为一个打开的文件提供文件描述符以进行更新。 如果你希望映射一个已有的 Python 文件对象,请使用该对象的 fileno() 方法来获取 fileno 参数的正确值。 否则,你可以使用 os.open() 函数来打开这个文件,这会直接返回一个文件描述符(结束时仍然需要关闭该文件)。

注解

如果要为可写的缓冲文件创建内存映射,则应当首先 flush() 该文件。 这确保了对缓冲区的本地修改在内存映射中可用。

对于 Unix 和 Windows 版本的构造函数,可以将 access 指定为可选的关键字参数。 access 接受以下四个值之一: ACCESS_READACCESS_WRITEACCESS_COPY 分别指定只读,直写或写时复制内存,或 ACCESS_DEFAULT 推迟到 protaccess 可以在 Unix 和 Windows 上使用。如果未指定 access ,则 Windows mmap 返回直写映射。这三种访问类型的初始内存值均取自指定的文件。向 ACCESS_READ 内存映射赋值会引发 TypeError 异常。 向 ACCESS_WRITE 内存映射赋值会影响内存和底层的文件。 向 ACCESS_COPY 内存映射赋值会影响内存,但不会更新底层的文件。

在 3.7 版更改: 添加了 ACCESS_DEFAULT 常量。

要映射匿名内存,应将 -1 作为 fileno 和 length 一起传递。

class mmap.mmap(fileno, length, tagname=None, access=ACCESS_DEFAULT[, offset])

( Windows 版本) 映射被文件句柄 fileno 指定的文件的 length 个字节,并创建一个 mmap 对象。如果 length 大于当前文件大小,则文件将扩展为包含 length 个字节。如果 length0,则映射的最大长度为当前文件大小。如果文件为空, Windows 会引发异常(你无法在Windows上创建空映射)。

如果 tagname 被指定且不是 None ,则是为映射提供标签名称的字符串。 Windows 允许你对同一文件拥有许多不同的映射。如果指定现有标签的名称,则会打开该标签,否则将创建该名称的新标签。如果省略此参数或设置为 None ,则创建的映射不带名称。避免使用 tag 参数将有助于使代码在Unix和Windows之间可移植。

offset 可以被指定为非负整数偏移量。 mmap 引用将相对于从文件开头的偏移。 offset 默认为0。 offeset 必须是 ALLOCATIONGRANULARITY 的倍数。

引发一个 审计事件 mmap.__new__ 附带参数 fileno, length, access, offset

class mmap.mmap(fileno, length, flags=MAP_SHARED, prot=PROT_WRITE|PROT_READ, access=ACCESS_DEFAULT[, offset])

(Unix 版本) 映射文件描述符 fileno 指定的文件的 length 个字节,并返回一个 mmap 对象。如果 length0 ,则当调用 mmap 时,映射的最大长度将为文件的当前大小。

flags 指明映射的性质。 MAP_PRIVATE 会创建私有的写入时拷贝映射,因此对 mmap 对象内容的修改将为该进程所私有。 而 MAP_SHARED 会创建与其他映射同一文件区域的进程所共享的映射。 默认值为 MAP_SHARED。 某些系统还具有额外的可用旗标,完整列表会在 MAP_* 常量 中指明。

如果指明了 prot*,它将给出所需的内存保护方式;最有用的两个值是 PROT_READPROT_WRITE,分别指明页面为可读或可写。 *prot 默认为 PROT_READ | PROT_WRITE

可以指定 access 作为替代 flagsprot 的可选关键字形参。 同时指定 flags, protaccess 将导致错误。 请参阅上文中 access 的描述了解有关如何使用此形参的信息。

offset 可以被指定为非负整数偏移量。 mmap 引用将相对于从文件开头的偏移。 offset 默认为 0。 offset 必须是 ALLOCATIONGRANULARITY 的倍数,它在 Unix 系统上等价于 PAGESIZE

To ensure validity of the created memory mapping the file specified by the descriptor fileno is internally automatically synchronized with physical backing store on macOS and OpenVMS.

这个例子演示了使用 mmap 的简单方式:

import mmap
# write a simple example file
with open("hello.txt", "wb") as f:
    f.write(b"Hello Python!\n")
with open("hello.txt", "r+b") as f:
    # memory-map the file, size 0 means whole file
    mm = mmap.mmap(f.fileno(), 0)
    # read content via standard file methods
    print(mm.readline())  # prints b"Hello Python!\n"
    # read content via slice notation
    print(mm[:5])  # prints b"Hello"
    # update content using slice notation;
    # note that new content must have same size
    mm[6:] = b" world!\n"
    # ... and read again using standard file methods
    mm.seek(0)
    print(mm.readline())  # prints b"Hello  world!\n"
    # close the map
    mm.close()

mmap 也可以在 with 语句中被用作上下文管理器:

import mmap
with mmap.mmap(-1, 13) as mm:
    mm.write(b"Hello world!")

3.2 新版功能: 上下文管理器支持。

下面的例子演示了如何创建一个匿名映射并在父进程和子进程之间交换数据。:

import mmap
import os
mm = mmap.mmap(-1, 13)
mm.write(b"Hello world!")
pid = os.fork()
if pid == 0:  # In a child process
    mm.seek(0)
    print(mm.readline())
    mm.close()

引发一个 审计事件 mmap.__new__ 附带参数 fileno, length, access, offset

映射内存的文件对象支持以下方法:

  • close()

    关闭 mmap。 后续调用该对象的其他方法将导致引发 ValueError 异常。 此方法将不会关闭打开的文件。

  • closed

    如果文件已关闭则返回 True

    3.2 新版功能.

  • find(sub[, start[, end]])

    返回子序列 sub 在对象内被找到的最小索引号,使得 sub 被包含在 [start, end*] 范围中。 可选参数 *startend 会被解读为切片表示法。 如果未找到则返回 -1

    在 3.5 版更改: 现在接受可写的 字节类对象。

  • flush([offset[, size]])

    将对文件的内存副本的修改刷新至磁盘。 如果不使用此调用则无法保证在对象被销毁前将修改写回存储。 如果指定了 offsetsize*,则只将对指定范围内字节的修改刷新至磁盘;在其他情况下,映射的全部范围都会被刷新。 *offset 必须为 PAGESIZEALLOCATIONGRANULARITY 的倍数。

    返回 None 以表示成功。 当调用失败时将引发异常。

    在 3.8 版更改: 在之前版本中,成功时将返回非零值;在 Windows 下当发生错误时将返回零。 在 Unix 下 成功时将返回零值;当发生错误时将引发异常。

  • madvise(option[, start[, length]])

    将有关内存区域的建议 option 发送至内核,从 start 开始扩展 length 个字节。 option 必须为系统中可用的 MADV_* 常量 之一。 如果省略 startlength,则会包含整个映射。 在某些系统中(包括 Linux),start 必须为 PAGESIZE 的倍数。

    可用性: 具有 madvise() 系统调用的系统。

    3.8 新版功能.

  • move(dest, src, count)

    将从偏移量 src 开始的 count 个字节拷贝到目标索引号 dest。 如果 mmap 创建时设置了 ACCESS_READ,则调用 move 将引发 TypeError 异常。

  • read([n])

    返回一个 bytes,其中包含从当前文件位置开始的至多 n 个字节。 如果参数省略,为 None 或负数,则返回从当前文件位置开始直至映射结尾的所有字节。 文件位置会被更新为返回字节数据之后的位置。

    在 3.3 版更改: 参数可被省略或为 None

  • read_byte()

    将当前文件位置上的一个字节以整数形式返回,并让文件位置前进 1。

  • readline()

    返回一个单独的行,从当前文件位置开始直到下一个换行符。 文件位置会被更新为返回字节数据之后的位置。

  • resize(newsize)

    改变映射以及下层文件的大小,如果存在的话。 如果 mmap 创建时设置了 ACCESS_READACCESS_COPY,则改变映射大小将引发 TypeError 异常。

  • rfind(sub[, start[, end]])

    返回子序列 sub 在对象内被找到的最大索引号,使得 sub 被包含在 [start, end*] 范围中。 可选参数 *startend 会被解读为切片表示法。 如果未找到则返回 -1

    在 3.5 版更改: 现在接受可写的 字节类对象。

  • seek(pos[, whence])

    设置文件的当前位置。 whence 参数为可选项并且默认为 os.SEEK_SET0 (绝对文件定位);其他值还有 os.SEEK_CUR1 (相对当前位置查找) 和 os.SEEK_END2 (相对文件末尾查找)。

  • size()

    返回文件的长度,该数值可以大于内存映射区域的大小。

  • tell()

    返回文件指针的当前位置。

  • write(bytes)

    bytes 中的字节数据写入文件指针当前位置的内存并返回写入的字节总数 (一定不小于 len(bytes),因为如果写入失败,将会引发 ValueError)。 在字节数据被写入后文件位置将会更新。 如果 mmap 创建时设置了 ACCESS_READ,则向其写入将引发 TypeError 异常。

    在 3.5 版更改: 现在接受可写的 字节类对象。

    在 3.6 版更改: 现在会返回写入的字节总数。

  • write_byte(byte)

    将整数值 byte 写入文件指针当前位置的内存;文件位置前进 1。 如果 mmap 创建时设置了 ACCESS_READ,则向其写入将引发 TypeError 异常。

MADV_* 常量

mmap.MADV_NORMAL
mmap.MADV_RANDOM
mmap.MADV_SEQUENTIAL
mmap.MADV_WILLNEED
mmap.MADV_DONTNEED
mmap.MADV_REMOVE
mmap.MADV_DONTFORK
mmap.MADV_DOFORK
mmap.MADV_HWPOISON
mmap.MADV_MERGEABLE
mmap.MADV_UNMERGEABLE
mmap.MADV_SOFT_OFFLINE
mmap.MADV_HUGEPAGE
mmap.MADV_NOHUGEPAGE
mmap.MADV_DONTDUMP
mmap.MADV_DODUMP
mmap.MADV_FREE
mmap.MADV_NOSYNC
mmap.MADV_AUTOSYNC
mmap.MADV_NOCORE
mmap.MADV_CORE
mmap.MADV_PROTECT
mmap.MADV_FREE_REUSABLE
mmap.MADV_FREE_REUSE

这些选项可被传给 mmap.madvise()。 不是每个选项都存在于每个系统中。

可用性: 具有 madvise() 系统调用的系统。

3.8 新版功能.

MAP_* 常量

mmap.MAP_SHARED
mmap.MAP_PRIVATE
mmap.MAP_DENYWRITE
mmap.MAP_EXECUTABLE
mmap.MAP_ANON
mmap.MAP_ANONYMOUS
mmap.MAP_POPULATE

这些是可被传给 mmap.mmap() 的各种旗标。 请注意某些选项在某些系统上可能不存在。

在 3.10 版更改: 增加了 MAP_POPULATE 常量。

互联网数据处理

本章介绍了一些支持处理因特网上常用数据格式的模块。

  • email —- 电子邮件与 MIME 处理包
    • email.message: 表示一封电子邮件信息
    • email.parser: 解析电子邮件信息
      • FeedParser API
      • Parser API
      • 附加说明
    • email.generator: 生成 MIME 文档
    • email.policy: Policy 对象
    • email.errors: 异常和缺陷类
    • email.headerregistry: 自定义标头对象
    • email.contentmanager: 管理 MIME 内容
      • 内容管理器实例
    • email: 示例
    • email.message.Message: 使用 compat32 API 来表示电子邮件消息
    • email.mime: 从头创建电子邮件和 MIME 对象
    • email.header: 国际化标头
    • email.charset: 表示字符集
    • email.encoders: 编码器
    • email.utils: 其他工具
    • email.iterators: 迭代器
  • json —- JSON 编码和解码器
    • 基本使用
    • 编码器和解码器
    • 异常
    • 标准符合性和互操作性
      • 字符编码
      • Infinite 和 NaN 数值
      • 对象中的重复名称
      • 顶级非对象,非数组值
      • 实现限制
    • 命令行界面
      • 命令行选项
  • mailcap —- Mailcap 文件处理
  • mailbox —- 操作多种格式的邮箱
    • Mailbox 对象
      • Maildir
      • mbox
      • MH
      • Babyl
      • MMDF
    • Message 对象
      • MaildirMessage
      • mboxMessage
      • MHMessage
      • BabylMessage
      • MMDFMessage
    • 异常
    • 例子
  • mimetypes —- 映射文件名到 MIME 类型
    • MimeTypes 对象
  • base64 —- Base16, Base32, Base64, Base85 数据编码
    • 安全考量
  • binhex —- 对binhex4文件进行编码和解码
    • 备注
  • binascii —- 二进制和 ASCII 码互转
  • quopri —- 编码与解码经过 MIME 转码的可打印数据
  • uu —- 对 uuencode 文件进行编码与解码

email —- 电子邮件与 MIME 处理包

源代码: Lib/email/init.py


email 包是一个用于管理电子邮件消息的库。 它 并非 被设计为执行向 SMTP (RFC 2821), NNTP 或其他服务器发送电子邮件消息的操作;这些是 smtplibnntplib 等模块的功能。 email 包试图尽可能地遵循 RFC,支持 RFC 5322RFC 6532,以及与 MIME 相关的各个 RFC 例如 RFC 2045, RFC 2046, RFC 2047, RFC 2183RFC 2231

email 包的总体结构可以分为三个主要组件,另外还有第四个组件用于控制其他组件的行为。

这个包的中心组件是代表电子邮件消息的“对象模型”。 应用程序主要通过在 message 子模块中定义的对象模型接口与这个包进行交互。 应用程序可以使用此 API 来询问有关现有电子邮件的问题、构造新的电子邮件,或者添加或移除自身也使用相同对象模型接口的电子邮件子组件。 也就是说,遵循电子邮件消息及其 MIME 子组件的性质,电子邮件对象模型是所有提供 EmailMessage API 的对象所构成的树状结构。

这个包的另外两个主要组件是 parsergenerator。 parser 接受电子邮件消息的序列化版本(字节流)并将其转换为 EmailMessage 对象树。 generator 接受 EmailMessage 并将其转回序列化的字节流。 (parser 和 generator 还能处理文本字符流,但不建议这种用法,因为这很容易导致某种形式的无效消息。

控制组件是 policy 模块。 每一个 EmailMessage、每一个 generator 和每一个 parser 都有一个相关联的 policy 对象来控制其行为。 通常应用程序只有在 EmailMessage 被创建时才需要指明控制策略,或者通过直接实例代 EmailMessage 来新建电子邮件,或者通过使用 parser 来解析输入流。 但是策略也可以在使用 generator 序列化消息时被更改。 例如,这允许从磁盘解析通用电子邮件消息,而在将消息发送到电子邮件服务器时使用标准 SMTP 设置对其进行序列化。

email 包会尽量地对应用程序隐藏各种控制类 RFC 的细节。 从概念上讲应用程序应当能够将电子邮件消息视为 Unicode 文本和二进制附件的结构化树,而不必担心在序列化时要如何表示它们。 但在实际中,经常有必要至少了解一部分控制类 MIME 消息及其结构的规划,特别是 MIME “内容类型” 的名称和性质以及它们是如何标识多部分文档的。 在大多数情况下这些知识应当仅对于更复杂的应用程序来说才是必需的,并且即便在那时它也应当仅是特定的高层级结构,而不是如何表示这些结构的细节信息。 由于 MIME 内容类型被广泛应用于现代因特网软件(而非只是电子邮件),因此这对许多程序员来说将是很熟悉的概念。

以下小节描述了 email 包的具体功能。 我们会从 message 对象模型开始,它是应用程序将要使用的主要接口,之后是 parsergenerator 组件。 然后我们会介绍 policy 控制组件,它将完成对这个库的主要组件的处理。

接下来的三个小节会介绍这个包可能引发的异常以及 parser 可能检测到的缺陷(即与 RFC 不相符)。 然后我们会介绍 headerregistrycontentmanager 子组件,它们分别提供了用于更精细地操纵标题和载荷的工具。 这两个组件除了包含使用与生成非简单消息的相关特性,还记录了它们的可扩展性 API,这将是高级应用程序所感兴趣的内容。

在此之后是一组使用之前小节所介绍的 API 的基本部分的示例。

前面的内容是 email 包的现代(对 Unicode 支持良好)API。 从 Message 类开始的其余小节则介绍了旧式 compat32 API,它会更直接地处理如何表示电子邮件消息的细节。 compat32 API 不会 向应用程序隐藏 RFC 的相关细节,但对于需要进行此种层级操作的应用程序来说将是很有用的工具。 此文档对于因向下兼容理由而仍然使用 compat32 API 的应用程序也是很适合的。

在 3.6 版更改: 文档经过重新组织和撰写以鼓励使用新的 EmailMessage/EmailPolicy API。

email 包文档的内容:

旧式 API:

json —- JSON 编码和解码器

源代码: Lib/json/init.py


JSON (JavaScript Object Notation),由 RFC 7159 (which obsoletes RFC 4627) 和 ECMA-404 指定,是一个受 JavaScript 的对象字面量语法启发的轻量级数据交换格式,尽管它不仅仅是一个严格意义上的 JavaScript 的字集 1

json 提供了与标准库 marshalpickle 相似的API接口。

对基本的 Python 对象层次结构进行编码:

>>> import json
>>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
'["foo", {"bar": ["baz", null, 1.0, 2]}]'
>>> print(json.dumps("\"foo\bar"))
"\"foo\bar"
>>> print(json.dumps('\u1234'))
"\u1234"
>>> print(json.dumps('\\'))
"\\"
>>> print(json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True))
{"a": 0, "b": 0, "c": 0}
>>> from io import StringIO
>>> io = StringIO()
>>> json.dump(['streaming API'], io)
>>> io.getvalue()
'["streaming API"]'

紧凑编码:

>>> import json
>>> json.dumps([1, 2, 3, {'4': 5, '6': 7}], separators=(',', ':'))
'[1,2,3,{"4":5,"6":7}]'

美化输出:

>>> import json
>>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4))
{
    "4": 5,
    "6": 7
}

JSON解码:

>>> import json
>>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]')
['foo', {'bar': ['baz', None, 1.0, 2]}]
>>> json.loads('"\\"foo\\bar"')
'"foo\x08ar'
>>> from io import StringIO
>>> io = StringIO('["streaming API"]')
>>> json.load(io)
['streaming API']

特殊 JSON 对象解码:

>>> import json
>>> def as_complex(dct):
...     if '__complex__' in dct:
...         return complex(dct['real'], dct['imag'])
...     return dct
...
>>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
...     object_hook=as_complex)
(1+2j)
>>> import decimal
>>> json.loads('1.1', parse_float=decimal.Decimal)
Decimal('1.1')

扩展 JSONEncoder

>>> import json
>>> class ComplexEncoder(json.JSONEncoder):
...     def default(self, obj):
...         if isinstance(obj, complex):
...             return [obj.real, obj.imag]
...         # Let the base class default method raise the TypeError
...         return json.JSONEncoder.default(self, obj)
...
>>> json.dumps(2 + 1j, cls=ComplexEncoder)
'[2.0, 1.0]'
>>> ComplexEncoder().encode(2 + 1j)
'[2.0, 1.0]'
>>> list(ComplexEncoder().iterencode(2 + 1j))
['[2.0', ', 1.0', ']']

从命令行使用 json.tool 来验证并美化输出:

$ echo '{"json":"obj"}' | python -m json.tool
{
    "json": "obj"
}
$ echo '{1.2:3.4}' | python -m json.tool
Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

注解

JSON 是 YAML 1.2 的一个子集。由该模块的默认设置生成的 JSON (尤其是默认的 “分隔符” 设置值)也是 YAML 1.0 and 1.1 的一个子集。因此该模块也能够用于序列化为 YAML。

注解

这个模块的编码器和解码器默认保护输入和输出的顺序。仅当底层的容器未排序时才会失去顺序。

在 Python 3.7 之前,dict 并不保证有序,因此输入和输出通常都是乱序的,除非是明确地请求 collections.OrderedDict。 从 Python 3.7 开始,普通的 dict 会保留顺序,因此不必再为 JSON 的生成和解析指定使用 collections.OrderedDict

基本使用

json.dump(obj, fp, **, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, *kw)

使用这个 转换表 将 obj 序列化为 JSON 格式化流形式的 fp (支持 .write() 的 file-like object)。

如果 skipkeys 是 true (默认为 False),那么那些不是基本对象的字典的键会被跳过;否则引发一个 TypeError

json 模块始终产生 str 对象而非 bytes 对象。因此,fp.write() 必须支持 str 输入。

如果 ensure_ascii 是 true (即默认值),输出保证将所有输入的非 ASCII 字符转义。如果 ensure_ascii 是 false,这些字符会原样输出。

如果 check_circular 是为假值 (默认为 True),那么容器类型的循环引用检验会被跳过并且循环引用会引发一个 OverflowError (或者更糟的情况)。

如果 allow_nan 是 false(默认为 True),那么在对严格 JSON 规格范围外的 float 类型值(naninf-inf)进行序列化时会引发一个 ValueError。如果 allow_nan 是 true,则使用它们的 JavaScript 等价形式(NaNInfinity-Infinity)。

如果 indent 是一个非负整数或者字符串,那么 JSON 数组元素和对象成员会被美化输出为该值指定的缩进等级。 如果缩进等级为零、负数或者 "",则只会添加换行符。 None (默认值) 选择最紧凑的表达。 使用一个正整数会让每一层缩进同样数量的空格。 如果 indent 是一个字符串 (比如 "\t"),那个字符串会被用于缩进每一层。

在 3.2 版更改: 现允许使用字符串作为 indent 而不再仅仅是整数。

当被指定时,separators 应当是一个 (item_separator, key_separator) 元组。当 indentNone 时,默认值取 (', ', ': '),否则取 (',', ': ')。为了得到最紧凑的 JSON 表达式,你应该指定其为 (',', ':') 以消除空白字符。

在 3.4 版更改: 现当 indent 不是 None 时,采用 (',', ': ') 作为默认值。

default 被指定时,其应该是一个函数,每当某个对象无法被序列化时它会被调用。它应该返回该对象的一个可以被 JSON 编码的版本或者引发一个 TypeError。如果没有被指定,则会直接引发 TypeError

如果 sort_keys 是 true(默认为 False),那么字典的输出会以键的顺序排序。

为了使用一个自定义的 JSONEncoder 子类(比如:覆盖了 default() 方法来序列化额外的类型), 通过 cls 关键字参数来指定;否则将使用 JSONEncoder

在 3.6 版更改: 所有可选形参现在都是 仅限关键字参数。

注解

picklemarshal 不同,JSON 不是一个具有框架的协议,所以尝试多次使用同一个 fp 调用 dump() 来序列化多个对象会产生一个不合规的 JSON 文件。

json.dumps(obj, **, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, *kw)

使用这个 转换表 将 obj 序列化为 JSON 格式的 str。 其参数的含义与 dump() 中的相同。

注解

JSON 中的键-值对中的键永远是 str 类型的。当一个对象被转化为 JSON 时,字典中所有的键都会被强制转换为字符串。这所造成的结果是字典被转换为 JSON 然后转换回字典时可能和原来的不相等。换句话说,如果 x 具有非字符串的键,则有 loads(dumps(x)) != x

json.load(fp, **, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, *kw)

使用这个 转换表 将 fp (一个支持 .read() 并包含一个 JSON 文档的 text file 或者 binary file) 反序列化为一个 Python 对象。

object_hook 是一个可选的函数,它会被调用于每一个解码出的对象字面量(即一个 dict)。object_hook 的返回值会取代原本的 dict。这一特性能够被用于实现自定义解码器(如 JSON-RPC 的类型提示)。

object_pairs_hook 是一个可选的函数,它会被调用于每一个有序列表对解码出的对象字面量。 object_pairs_hook 的返回值将会取代原本的 dict 。这一特性能够被用于实现自定义解码器。如果 object_hook 也被定义, object_pairs_hook 优先。

在 3.1 版更改: 添加了对 object_pairs_hook 的支持。

parse_float ,如果指定,将与每个要解码 JSON 浮点数的字符串一同调用。默认状态下,相当于 float(num_str) 。可以用于对 JSON 浮点数使用其它数据类型和语法分析程序 (比如 decimal.Decimal )。

parse_int ,如果指定,将与每个要解码 JSON 整数的字符串一同调用。默认状态下,相当于 int(num_str) 。可以用于对 JSON 整数使用其它数据类型和语法分析程序 (比如 float )。

parse_constant ,如果指定,将要与以下字符串中的一个一同调用: '-Infinity''Infinity''NaN' 。如果遇到无效的 JSON 数字则可以使用它引发异常。

在 3.1 版更改: parse_constant 不再调用 ‘null’ , ‘true’ , ‘false’ 。

要使用自定义的 JSONDecoder 子类,用 cls 指定他;否则使用 JSONDecoder 。额外的关键词参数会通过类的构造函数传递。

如果反序列化的数据不是有效 JSON 文档,引发 JSONDecodeError 错误。

在 3.6 版更改: 所有可选形参现在都是 仅限关键字参数。

在 3.6 版更改: fp 现在可以是 binary file 。输入编码应当是 UTF-8 , UTF-16 或者 UTF-32 。

json.loads(s, **, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, *kw)

使用这个 转换表 将 s (一个包含 JSON 文档的 str, bytesbytearray 实例) 反序列化为 Python 对象。

其他参数的含义与 load() 中的相同。

如果反序列化的数据不是有效 JSON 文档,引发 JSONDecodeError 错误。

在 3.6 版更改: s 现在可以为 bytesbytearray 类型。 输入编码应为 UTF-8, UTF-16 或 UTF-32。

在 3.9 版更改: 关键字参数 encoding 已被移除。

编码器和解码器

class json.JSONDecoder(**, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, strict=True, object_pairs_hook=None*)

简单的JSON解码器。

默认情况下,解码执行以下翻译:

JSON Python
object — 对象 dict
array list — 列表
string str
number (int) int
number (real) float
true True
false False
null None

它还将“NaN”、“Infinity”和“-Infinity”理解为它们对应的“float”值,这超出了JSON规范。

如果指定了 object_hook,它将被调用并传入每个已解码 JSON 对象的结果,并且其返回值将被用来替代给定的 dict。 它可被用于提供自定义的反序列化操作(例如支持 JSON-RPC 类提示)。

如果指定了 object_pairs_hook 则它将被调用并传入以对照值有序列表进行解码的每个 JSON 对象的结果。 object_pairs_hook 的结果值将被用来替代 dict。 这一特性可被用于实现自定义解码器。 如果还定义了 object_hook*,则 *object_pairs_hook 的优先级更高。

在 3.1 版更改: 添加了对 object_pairs_hook 的支持。

parse_float ,如果指定,将与每个要解码 JSON 浮点数的字符串一同调用。默认状态下,相当于 float(num_str) 。可以用于对 JSON 浮点数使用其它数据类型和语法分析程序 (比如 decimal.Decimal )。

parse_int ,如果指定,将与每个要解码 JSON 整数的字符串一同调用。默认状态下,相当于 int(num_str) 。可以用于对 JSON 整数使用其它数据类型和语法分析程序 (比如 float )。

parse_constant ,如果指定,将要与以下字符串中的一个一同调用: '-Infinity''Infinity''NaN' 。如果遇到无效的 JSON 数字则可以使用它引发异常。

如果 strict 为 false (默认为 True ),那么控制字符将被允许在字符串内。在此上下文中的控制字符编码在范围 0—31 内的字符,包括 '\t' (制表符), '\n''\r''\0'

如果反序列化的数据不是有效 JSON 文档,引发 JSONDecodeError 错误。

在 3.6 版更改: 所有形参现在都是 仅限关键字参数。

  • decode(s)

    返回 s 的 Python 表示形式(包含一个 JSON 文档的 str 实例)。

    如果给定的 JSON 文档无效则将引发 JSONDecodeError

  • raw_decode(s)

    s 中解码出 JSON 文档(以 JSON 文档开头的一个 str 对象)并返回一个 Python 表示形式为 2 元组以及指明该文档在 s 中结束位置的序号。

    这可以用于从一个字符串解码JSON文档,该字符串的末尾可能有无关的数据。

class json.JSONEncoder(**, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None*)

用于Python数据结构的可扩展JSON编码器。

默认支持以下对象和类型:

Python JSON
dict object — 对象
list, tuple array
str string
int, float, int 和 float 派生的枚举 number
True true
False false
None null

在 3.4 版更改: 添加了对 int 和 float 派生的枚举类的支持

为了将其拓展至识别其他对象,需要子类化并实现 default() 方法于另一种返回 o 的可序列化对象的方法如果可行,否则它应该调用超类实现(来引发 TypeError )。

如果 skipkeys 为假值(默认),则当尝试对非 str, int, floatNone 的键进行编码时将会引发 TypeError。 如果 skipkeys 为真值,这些条目将被直接跳过。

如果 ensure_ascii 是 true (即默认值),输出保证将所有输入的非 ASCII 字符转义。如果 ensure_ascii 是 false,这些字符会原样输出。

如果 check_circular 为 true (默认),那么列表,字典,和自定义编码的对象在编码期间会被检查重复循环引用防止无限递归(无限递归将导致 OverflowError )。否则,这样进行检查。

如果 allow_nan 为 true (默认),那么 NaNInfinity ,和 -Infinity 进行编码。此行为不符合 JSON 规范,但与大多数的基于 Javascript 的编码器和解码器一致。否则,它将是一个 ValueError 来编码这些浮点数。

如果 sort_keys 为 true (默认为: False ),那么字典的输出是按照键排序;这对回归测试很有用,以确保可以每天比较 JSON 序列化。

如果 indent 是一个非负整数或者字符串,那么 JSON 数组元素和对象成员会被美化输出为该值指定的缩进等级。 如果缩进等级为零、负数或者 "",则只会添加换行符。 None (默认值) 选择最紧凑的表达。 使用一个正整数会让每一层缩进同样数量的空格。 如果 indent 是一个字符串 (比如 "\t"),那个字符串会被用于缩进每一层。

在 3.2 版更改: 现允许使用字符串作为 indent 而不再仅仅是整数。

当被指定时,separators 应当是一个 (item_separator, key_separator) 元组。当 indentNone 时,默认值取 (', ', ': '),否则取 (',', ': ')。为了得到最紧凑的 JSON 表达式,你应该指定其为 (',', ':') 以消除空白字符。

在 3.4 版更改: 现当 indent 不是 None 时,采用 (',', ': ') 作为默认值。

default 被指定时,其应该是一个函数,每当某个对象无法被序列化时它会被调用。它应该返回该对象的一个可以被 JSON 编码的版本或者引发一个 TypeError。如果没有被指定,则会直接引发 TypeError

在 3.6 版更改: 所有形参现在都是 仅限关键字参数。

  • default(o)

    在子类中实现这种方法使其返回 o 的可序列化对象,或者调用基础实现(引发 TypeError )。

    例如,为了支持任意的迭代器,你可以这样来实现 default():

    def default(self, o):
       try:
           iterable = iter(o)
       except TypeError:
           pass
       else:
           return list(iterable)
       # Let the base class default method raise the TypeError
       return json.JSONEncoder.default(self, o)
  • encode(o)

    返回 Python o 数据结构的 JSON 字符串表达方式。例如:

    >>> json.JSONEncoder().encode({"foo": ["bar", "baz"]})
    '{"foo": ["bar", "baz"]}'
  • iterencode(o)

    编码给定对象 o ,并且让每个可用的字符串表达方式。例如:

    for chunk in json.JSONEncoder().iterencode(bigobject):
        mysocket.write(chunk)

异常

exception json.JSONDecodeError(msg, doc, pos)

拥有以下附加属性的 ValueError 的子类:

  • msg

    未格式化的错误消息。

  • doc

    正在解析的 JSON 文档。

  • pos

    The start index of doc where parsing failed.

  • lineno

    The line corresponding to pos.

  • colno

    The column corresponding to pos.

3.5 新版功能.

标准符合性和互操作性

JSON 格式由 RFC 7159ECMA-404 指定。此段落详细讲了这个模块符合 RFC 的级别。简单来说, JSONEncoderJSONDecoder 子类,和明确提到的参数以外的参数,不作考虑。

此模块不严格遵循于 RFC ,它实现了一些扩展是有效的 Javascript 但不是有效的 JSON。尤其是:

  • 无限和 NaN 数值是被接受并输出;
  • 对象内的重复名称是接受的,并且仅使用最后一对属性-值对的值。

自从 RFC 允许符合 RFC 的语法分析程序接收 不符合 RFC 的输入文本以来,这个模块的解串器在默认状态下默认符合 RFC 。

字符编码

RFC 要求使用 UTF-8 , UTF-16 ,或 UTF-32 之一来表示 JSON ,为了最大互通性推荐使用 UTF-8 。

RFC允许,尽管不是必须的,这个模块的序列化默认设置为 ensure_ascii=True ,这样消除输出以便结果字符串至容纳 ASCII 字符。

ensure_ascii 参数以外,此模块是严格的按照在 Python 对象和 Unicode strings 间的转换定义的,并且因此不能直接解决字符编码的问题。

RFC 禁止添加字符顺序标记( BOM )在 JSON 文本的开头,这个模块的序列化器不添加 BOM 标记在它的输出上。 RFC,准许 JSON 反序列化器忽略它们输入中的初始 BOM 标记,但不要求。此模块的反序列化器引发 ValueError 当存在初始 BOM 标记。

RFC 不会明确禁止包含字节序列的 JSON 字符串这不对应有效的 Unicode 字符(比如 不成对的 UTF-16 的替代物),但是它确实指出它们可能会导致互操作性问题。默认下,模块对这样的序列接受和输出(当在原始 str 存在时)代码点。

Infinite 和 NaN 数值

RFC 不允许 infinite 或者 NaN 数值的表达方式。尽管这样,默认情况下,此模块接受并且输出 Infinity-Infinity,和 NaN 好像它们是有效的JSON数字字面值

>>> # Neither of these calls raises an exception, but the results are not valid JSON
>>> json.dumps(float('-inf'))
'-Infinity'
>>> json.dumps(float('nan'))
'NaN'
>>> # Same when deserializing
>>> json.loads('-Infinity')
-inf
>>> json.loads('NaN')
nan

序列化器中, allow_nan 参数可用于替代这个行为。反序列化器中, parse_constant 参数,可用于替代这个行为。

对象中的重复名称

RFC 具体说明了 在 JSON对象里的名字应该是唯一的,但没有规定如何处理JSON对象中的重复名称。默认下,此模块不引发异常;作为替代,对于给定名它将忽略除姓-值对之外的所有对:

>>> weird_json = '{"x": 1, "x": 2, "x": 3}'
>>> json.loads(weird_json)
{'x': 3}

The object_pairs_hook parameter can be used to alter this behavior.

顶级非对象,非数组值

过时的 RFC 4627 指定的旧版本 JSON 要求 JSON 文本顶级值必须是 JSON 对象或数组( Python dictlist ),并且不能是 JSON null 值,布尔值,数值或者字符串值。 RFC 7159 移除这个限制,此模块没有并且从未在序列化器和反序列化器中实现这个限制。

无论如何,为了最大化地获取互操作性,你可能希望自己遵守该原则。

实现限制

一些 JSON 反序列化器的实现应该在以下方面做出限制:

  • 可接受的 JSON 文本大小
  • 嵌套 JSON 对象和数组的最高水平
  • JSON 数字的范围和精度
  • JSON 字符串的内容和最大长度

此模块不强制执行任何上述限制,除了相关的 Python 数据类型本身或者 Python 解释器本身的限制以外。

当序列化为 JSON ,在应用中当心此类限制这可能破坏你的 JSON 。特别是,通常将 JSON 数字反序列化为 IEEE 754 双精度数字,从而受到该表示方式的范围和精度限制。这是特别相关的,当序列化非常大的 Python int 值时,或者当序列化 “exotic” 数值类型的实例时比如 decimal.Decimal

命令行界面

源代码: Lib/json/tool.py


The json.tool module provides a simple command line interface to validate and pretty-print JSON objects.

如果未指定可选的 infileoutfile 参数,则将分别使用 sys.stdinsys.stdout:

$ echo '{"json": "obj"}' | python -m json.tool
{
    "json": "obj"
}
$ echo '{1.2:3.4}' | python -m json.tool
Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

在 3.5 版更改: 输出现在将与输入顺序保持一致。 请使用 --sort-keys 选项来将输出按照键的字母顺序排序。

命令行选项

infile

要被验证或美化打印的 JSON 文件:

$ python -m json.tool mp_films.json
[
    {
        "title": "And Now for Something Completely Different",
        "year": 1971
    },
    {
        "title": "Monty Python and the Holy Grail",
        "year": 1975
    }
]

如果 infile 未指定,则从 sys.stdin 读取。

outfile

infile 输出写入到给定的 outfile。 在其他情况下写入到 sys.stdout

--sort-keys

将字典输出按照键的字母顺序排序。

3.5 新版功能.

--no-ensure-ascii

禁用非 ASCII 字符的转义,详情参见 json.dumps()

3.9 新版功能.

--json-lines

将每个输入行解析为单独的 JSON 对象。

3.8 新版功能.

--indent``,` `--tab``,` `--no-indent``,` `--compact

用于空白符控制的互斥选项。

3.9 新版功能.

-h``,` `--help

显示帮助消息。

mailcap —- Mailcap 文件处理

源代码: Lib/mailcap.py


Mailcap 文件可用来配置支持 MIME 的应用程序例如邮件阅读器和 Web 浏览器如何响应具有不同 MIME 类型的文件。 (“mailcap” 这个名称源自短语 “mail capability”。) 例如,一个 mailcap 文件可能包含 video/mpeg; xmpeg %s 这样的行。 然后,如果用户遇到 MIME 类型为 video/mpeg 的邮件消息或 Web 文档时,%s 将被替换为一个文件名 (通常属于临时文件) 并且会自动启动 xmpeg 程序来查看该文件。

mailcap 格式的说明文档见 RFC 1524, “A User Agent Configuration Mechanism For Multimedia Mail Format Information”,但它并不是一个互联网标准。 不过,mailcap 文件在大多数 Unix 系统上都受到支持。

mailcap.findmatch(caps, MIMEtype, key=’view’, filename=’/dev/null’, plist=[])

返回一个 2 元组;其中第一个元素是包含所要执行命令的字符串 (它可被传递给 os.system()),第二个元素是对应于给定 MIME 类型的 mailcap 条目。 如果找不到匹配的 MIME 类型,则将返回 (None, None)

key 是所需字段的名称,它代表要执行的活动类型;默认值是 ‘view’,因为在最通常的情况下你只是想要查看 MIME 类型数据的正文。 其他可能的值还有 ‘compose’ 和 ‘edit’,分别用于想要创建给定 MIME 类型正文或修改现有正文数据的情况。 请参阅 RFC 1524 获取这些字段的完整列表。

filename 是在命令行中用来替换 %s 的文件名;默认值 '/dev/null' 几乎肯定不是你想要的,因此通常你要通过指定一个文件名来重载它。

plist 可以是一个包含命名形参的列表;默认值只是一个空列表。 列表中的每个条目必须为包含形参名称的字符串、等于号 ('=') 以及形参的值。 Mailcap 条目可以包含形如 %{foo} 的命名形参,它将由名为 ‘foo’ 的形参的值所替换。 例如,如果命令行 showpartial %{id} %{number} %{total} 是在一个 mailcap 文件中,并且 plist 被设为 ['id=1', 'number=2', 'total=3'],则结果命令行将为 'showpartial 1 2 3'

在 mailcap 文件中,可以指定可选的 “test” 字段来检测某些外部条件(例如所使用的机器架构或窗口系统)来确定是否要应用 mailcap 行。 findmatch() 将自动检查此类条件并在检查未通过时跳过条目。

mailcap.getcaps()

返回一个将 MIME 类型映射到 mailcap 文件条目列表的字典。 此字典必须被传给 findmatch() 函数。 条目会被存储为字典列表,但并不需要了解此表示形式的细节。

此信息来自在系统中找到的所有 mailcap 文件。 用户的 mailcap 文件 $HOME/.mailcap 中的设置将覆盖系统 mailcap 文件 /etc/mailcap, /usr/etc/mailcap/usr/local/etc/mailcap 中的设置。

一个用法示例:

>>> import mailcap
>>> d = mailcap.getcaps()
>>> mailcap.findmatch(d, 'video/mpeg', filename='tmp1223')
('xmpeg tmp1223', {'view': 'xmpeg %s'})

mailbox —- 操作多种格式的邮箱

源代码: Lib/mailbox.py


本模块定义了两个类,MailboxMessage,用于访问和操作磁盘中的邮箱及其所包含的电子邮件。 Mailbox 提供了类似字典的从键到消息的映射。 Messageemail.message 模块的 Message 类增加了特定格式专属的状态和行为。 支持的邮箱格式有 Maildir, mbox, MH, Babyl 以及 MMDF。

Mailbox 对象

class mailbox.Mailbox

一个邮箱,它可以被检视和修改。

Mailbox 类定义了一个接口并且它不应被实例化。 而是应该让格式专属的子类继承 Mailbox 并且你的代码应当实例化一个特定的子类。

Mailbox 接口类似于字典,其中每个小键都有对应的消息。 键是由 Mailbox 实例发出的,它们将由实例来使用并且只对该 Mailbox 实例有意义。 键会持续标识一条消息,即使对应的消息已被修改,例如被另一条消息所替代。

可以使用 set 型方法 add() 将消息添加到 Mailbox 并可以使用 del 语句或 set 型方法 remove()discard() 将其移除。

Mailbox 接口语义在某些值得注意的方面与字典语义有所不同。 每次请求消息时,都会基于邮箱的当前状态生成一个新的表示形式(通常为 Message 实例)。 类似地,当向 Mailbox 实例添加消息时,所提供的消息表示形式的内容将被复制。 无论在哪种情况下 Mailbox 实例都不会保留对消息表示形式的引用。

默认的 Mailbox 迭代器会迭代消息表示形式,而不像默认的字典迭代器那样迭代键。 此外,在迭代期间修改邮箱是安全且有明确定义的。 在创建迭代器之后被添加到邮箱的消息将对该迭代不可见。 在迭代器产出消息之前被从邮箱移除的消息将被静默地跳过,但是使用来自迭代器的键也有可能导致 KeyError 异常,如果对应的消息后来被移除的话。

警告

在修改可能同时被其他某个进程修改的邮箱时要非常小心。 用于此种任务的最安全邮箱格式是 Maildir;请尽量避免使用 mbox 之类的单文件格式进行并发写入。 如果你正在修改一个邮箱,你 必须 在读取文件中的任何消息或者执行添加或删除消息等修改操作 之前 通过调用 lock() 以及 unlock() 方法来锁定它。 如果未锁定邮箱则将导致丢失消息或损坏整个邮箱的风险。

Mailbox 实例具有下列方法:

  • add(message)

    message 添加到邮箱并返回分配给它的键。

    形参 message 可以是 Message 实例、email.message.Message 实例、字符串、字节串或文件类对象(应当以二进制模式打开)。 如果 message 是适当的格式专属 Message 子类的实例(举例来说,如果它是一个 mboxMessage 实例而这是一个 mbox 实例),将使用其格式专属的信息。 在其他情况下,则会使用合理的默认值作为格式专属的信息。

    在 3.2 版更改: 增加了对二进制输入的支持。

  • remove(key)

    __delitem__(key)

    discard(key)

    从邮箱中删除对应于 key 的消息。

    当消息不存在时,如果此方法是作为 remove()__delitem__() 调用则会引发 KeyError 异常,而如果此方法是作为 discard() 调用则不会引发异常。 如果下层邮箱格式支持来自其他进程的并发修改则 discard() 的行为可能是更为适合的。

  • __setitem__(key, message)

    key 所对应的消息替换为 message*。 如果没有与 *key 所对应的消息则会引发 KeyError 异常。

    add() 一样,形参 message 可以是 Message 实例、email.message.Message 实例、字符串、字节串或文件类对象(应当以二进制模式打开)。 如果 message 是适当的格式专属 Message 子类的实例(举例来说,如果它是一个 mboxMessage 实例而这是一个 mbox 实例),将使用其格式专属的信息。 在其他情况下,当前与 key 所对应的消息的格式专属信息则会保持不变。

  • iterkeys()

    keys()

    如果通过 iterkeys() 调用则返回一个迭代所有键的迭代器,或者如果通过 keys() 调用则返回一个键列表。

  • itervalues()

    __iter__()

    values()

    如果通过 itervalues()__iter__() 调用则返回一个迭代所有消息的表示形式的迭代器,或者如果通过 values() 调用则返回一个由这些表示形式组成的列表。 消息会被表示为适当的格式专属 Message 子类的实例,除非当 Mailbox 实例被初始化时指定了自定义的消息工厂函数。

    注解

    __iter__() 的行为与字典不同,后者是对键进行迭代。

  • iteritems()

    items()

    如果通过 iteritems() 调用则返回一个迭代 (key, message) 对的迭代器,其中 key 为键而 message 为消息的表示形式,或者如果通过 items() 调用则返回一个由这种键值对组成的列表。 消息会被表示为适当的格式专属 Message 子类的实例,除非当 Mailbox 实例被初始化时指定了自定义的消息工厂函数。

  • get(key, default=None)

    __getitem__(key)

    返回对应于 key 的消息的表示形式。 当对应的消息不存在时,如果通过 get() 调用则返回 default 而如果通过 __getitem__() 调用此方法则会引发 KeyError 异常。 消息会被表示为适当的格式专属 Message 子类的实例,除非当 Mailbox 实例被初始化时指定了自定义的消息工厂函数。

  • get_message(key)

    将对应于 key 的消息的表示形式作为适当的格式专属 Message 子类的实例返回,或者如果对应的消息不存在则会引发 KeyError 异常。

  • get_bytes(key)

    返回对应于 key 的消息的字节表示形式,或者如果对应的消息不存在则会引发 KeyError 异常。

    3.2 新版功能.

  • get_string(key)

    返回对应于 key 的消息的字符串表示形式,或者如果对应的消息不存在则会引发 KeyError 异常。 消息是通过 email.message.Message 处理来将其转换为纯 7bit 表示形式的。

  • get_file(key)

    返回对应于 key 的消息的文件类表示形式,或者如果对应的消息不存在则会引发 KeyError 异常。 文件类对象的行为相当于以二进制模式打开。 当不再需要此文件时应当将其关闭。

    在 3.2 版更改: 此文件对象实际上是二进制文件;之前它被不正确地以文本模式返回。 并且,此文件类对象现在还支持上下文管理协议:你可以使用 with 语句来自动关闭它。

    注解

    不同于其他消息表示形式,文件类表示形式并不一定独立于创建它们的 Mailbox 实例或下层的邮箱。 每个子类都会提供更具体的文档。

  • __contains__(key)

    如果 key 有对应的消息则返回 True,否则返回 False

  • __len__()

    返回邮箱中消息的数量。

  • clear()

    从邮箱中删除所有消息。

  • pop(key, default=None)

    返回对应于 key 的消息的表示形式并删除该消息。 如果对应的消息不存在则返回 default。 消息会被表示为适当的格式专属 Message 子类的实例,除非当 Mailbox 实例被初始化时指定了自定义的消息工厂函数。

  • popitem()

    返回一个任意的 (key, message) 对,其中 key 为键而 message 为消息的表示形式,并删除对应的消息。 如果邮箱为空,则会引发 KeyError 异常。 消息会被表示为适当的格式专属 Message 子类的实例,除非当 Mailbox 实例被初始化时指定了自定义的消息工厂函数。

  • update(arg)

    形参 arg 应当是 keymessage 的映射或 (key, message) 对的可迭代对象。 用来更新邮箱以使得对于每个给定的 keymessage*,与 *key 相对应的消息会被设为 message*,就像通过使用 __setitem__() 一样。 类似于 __setitem__(),每个 *key 都必须在邮箱中有一个对应的消息否则将会引发 KeyError 异常,因此在通常情况下将 arg 设为 Mailbox 实例是不正确的。

    注解

    与字典不同,关键字参数是不受支持的。

  • flush()

    将所有待定的更改写入到文件系统。 对于某些 Mailbox 子类来说,更改总是被立即写入因而 flush() 并不会做任何事,但您仍然应当养成调用此方法的习惯。

  • lock()

    在邮箱上获取一个独占式咨询锁以使其他进程知道不能修改它。 如果锁无法被获取则会引发 ExternalClashError。 所使用的具体锁机制取决于邮箱的格式。 在对邮箱内容进行任何修改之前你应当 总是 锁定它。

  • unlock()

    释放邮箱上的锁,如果存在的话。

  • close()

    刷新邮箱,如果必要则将其解锁。 并关闭所有打开