库和扩展常见问题解答 — Python 文档

来自菜鸟教程
Python/docs/3.9/faq/library
跳转至:导航、​搜索

库和扩展常见问题解答

一般图书馆问题

我如何找到一个模块或应用程序来执行任务 X?

检查 库参考 以查看是否有相关的标准库模块。 (最终,您将了解标准库中的内容,并且可以跳过此步骤。)

对于第三方包,搜索 Python 包索引 或尝试 Google 或其他网络搜索引擎。 搜索“Python”加上您感兴趣的主题的一两个关键字通常会找到有用的东西。


math.py(socket.py、regex.py 等)源文件在哪里?

如果找不到模块的源文件,它可能是用 C、C++ 或其他编译语言实现的内置或动态加载的模块。 在这种情况下,您可能没有源文件,或者它可能类似于 mathmodule.c,位于 C 源目录中的某处(不在 Python 路径上)。

Python中有(至少)三种模块:

  1. 用 Python (.py) 编写的模块;

  2. 用 C 编写并动态加载的模块(.dll、.pyd、.so、.sl 等);

  3. 用 C 语言编写并与解释器链接的模块; 要获取这些列表,请键入:

    import sys
    print(sys.builtin_module_names)


如何使 Python 脚本在 Unix 上可执行?

您需要做两件事:脚本文件的模式必须是可执行的,并且第一行必须以 #! 开头,后跟 Python 解释器的路径。

第一个是通过执行 chmod +x scriptfilechmod 755 scriptfile 来完成的。

第二种可以通过多种方式完成。 最直接的方法是写

#!/usr/local/bin/python

作为文件的第一行,使用 Python 解释器在您的平台上安装位置的路径名。

如果您希望脚本独立于 Python 解释器所在的位置,您可以使用 env 程序。 几乎所有 Unix 变体都支持以下内容,假设 Python 解释器位于用户的 PATH 目录中:

#!/usr/bin/env python

不要 为 CGI 脚本执行此操作。 CGI 脚本的 PATH 变量通常非常小,因此您需要使用解释器的实际绝对路径名。

偶尔,某个用户的环境太满导致/usr/bin/env程序失败; 或者根本没有 env 程序。 在这种情况下,您可以尝试以下 hack(由于 Alex Rezinsky):

#! /bin/sh
""":"
exec python $0 ${1+"$@"}
"""

次要缺点是它定义了脚本的 __doc__ 字符串。 但是,您可以通过添加来解决这个问题

__doc__ = """...Whatever..."""

是否有适用于 Python 的 curses/termcap 包?

对于 Unix 变体:标准的 Python 源代码分发在 :source:`Modules` 子目录中带有一个 curses 模块,尽管它不是默认编译的。 (请注意,这在 Windows 发行版中不可用 - Windows 没有 Curses 模块。)

curses 模块支持基本的 curses 功能以及来自 ncurses 和 SYSV curses 的许多附加功能,例如颜色、替代字符集支持、pads 和鼠标支持。 这意味着该模块与只有 BSD 诅咒的操作系统不兼容,但似乎没有任何当前维护的操作系统属于这一类。

对于 Windows:使用 控制台库模块


Python 中是否有等效于 C 的 onexit() ?

atexit模块提供了类似于C的onexit()的寄存器功能。


为什么我的信号处理程序不起作用?

最常见的问题是使用错误的参数列表声明了信号处理程序。 它被称为

handler(signum, frame)

所以它应该用两个参数声明:

def handler(signum, frame):
    ...

常见任务

如何测试 Python 程序或组件?

Python 带有两个测试框架。 doctest 模块在模块的文档字符串中查找示例并运行它们,将输出与文档字符串中给出的预期输出进行比较。

unittest 模块是一个更高级的测试框架,它以 Java 和 Smalltalk 测试框架为模型。

为了使测试更容易,您应该在程序中使用良好的模块化设计。 你的程序应该几乎所有的功能都封装在函数或类方法中——这有时会产生令人惊讶和愉快的效果,使程序运行得更快(因为局部变量访问比全局访问更快)。 此外,程序应该避免依赖于变异的全局变量,因为这使得测试变得更加困难。

你程序的“全局主逻辑”可能很简单

if __name__ == "__main__":
    main_logic()

在程序主模块的底部。

一旦你的程序被组织成一个易于处理的函数和类行为的集合,你应该编写测试函数来练习这些行为。 自动化一系列测试的测试套件可以与每个模块相关联。 这听起来像很多工作,但由于 Python 如此简洁和灵活,所以它非常容易。 通过与“生产代码”并行编写测试函数,您可以使编码更加愉快和有趣,因为这样可以更早地发现错误甚至设计缺陷。

不打算成为程序主要模块的“支持模块”可能包括模块的自检。

if __name__ == "__main__":
    self_test()

当外部接口不可用时,即使是与复杂外部接口交互的程序也可以通过使用在 Python 中实现的“假”接口进行测试。


如何从文档字符串创建文档?

pydoc 模块可以从 Python 源代码中的文档字符串创建 HTML。 纯粹从文档字符串创建 API 文档的另一种方法是 epydocSphinx 还可以包含文档字符串内容。


如何一次获得一个按键?

对于 Unix 变体,有几种解决方案。 使用curses 执行此操作很简单,但curses 是一个相当大的模块,需要学习。


线程

如何使用线程编程?

请务必使用 threading 模块而不是 _thread 模块。 threading 模块在 _thread 模块提供的低级原语之上构建了方便的抽象。

Aahz 在他的线程教程中有一组有用的幻灯片; 见 http://www.pythoncraft.com/OSCON2001/。


我的线程似乎都没有运行:为什么?

一旦主线程退出,所有线程都会被杀死。 您的主线程运行得太快,使线程没有时间做任何工作。

一个简单的解决方法是在程序末尾添加一个足够所有线程完成的睡眠:

import threading, time

def thread_task(name, n):
    for i in range(n):
        print(name, i)

for i in range(10):
    T = threading.Thread(target=thread_task, args=(str(i), i))
    T.start()

time.sleep(10)  # <---------------------------!

但是现在(在许多平台上)线程不是并行运行,而是顺序运行,一次一个! 原因是操作系统线程调度程序在前一个线程被阻塞之前不会启动新线程。

一个简单的解决方法是在 run 函数的开头添加一个小睡眠:

def thread_task(name, n):
    time.sleep(0.001)  # <--------------------!
    for i in range(n):
        print(name, i)

for i in range(10):
    T = threading.Thread(target=thread_task, args=(str(i), i))
    T.start()

time.sleep(10)

与其试图为 time.sleep() 猜测一个好的延迟值,不如使用某种信号量机制。 一种想法是使用 queue 模块创建一个队列对象,让每个线程在完成时将一个令牌附加到队列中,并让主线程从队列中读取与线程数量一样多的令牌。


如何在一堆工作线程之间分配工作?

最简单的方法是使用 concurrent.futures 模块,尤其是 ThreadPoolExecutor 类。

或者,如果您想对调度算法进行精细控制,您可以手动编写自己的逻辑。 使用 queue 模块创建一个包含作业列表的队列。 Queue 类维护一个对象列表,并有一个 .put(obj) 方法将项目添加到队列和一个 .get() 方法返回它们。 该类将负责必要的锁定,以确保每个作业只分发一次。

这是一个简单的例子:

import threading, queue, time

# The worker thread gets jobs off the queue.  When the queue is empty, it
# assumes there will be no more work and exits.
# (Realistically workers will run until terminated.)
def worker():
    print('Running worker')
    time.sleep(0.1)
    while True:
        try:
            arg = q.get(block=False)
        except queue.Empty:
            print('Worker', threading.currentThread(), end=' ')
            print('queue empty')
            break
        else:
            print('Worker', threading.currentThread(), end=' ')
            print('running with argument', arg)
            time.sleep(0.5)

# Create queue
q = queue.Queue()

# Start a pool of 5 workers
for i in range(5):
    t = threading.Thread(target=worker, name='worker %i' % (i+1))
    t.start()

# Begin adding work to the queue
for i in range(50):
    q.put(i)

# Give threads time to run
print('Main thread sleeping')
time.sleep(5)

运行时,这将产生以下输出:

Running worker
Running worker
Running worker
Running worker
Running worker
Main thread sleeping
Worker <Thread(worker 1, started 130283832797456)> running with argument 0
Worker <Thread(worker 2, started 130283824404752)> running with argument 1
Worker <Thread(worker 3, started 130283816012048)> running with argument 2
Worker <Thread(worker 4, started 130283807619344)> running with argument 3
Worker <Thread(worker 5, started 130283799226640)> running with argument 4
Worker <Thread(worker 1, started 130283832797456)> running with argument 5
...

有关更多详细信息,请参阅模块的文档; Queue 类提供了一个功能强大的接口。


什么样的全局值变异是线程安全的?

内部使用 全局解释器锁 (GIL) 以确保一次在 Python VM 中仅运行一个线程。 一般来说,Python 只在字节码指令之间提供线程之间的切换; 可以通过 sys.setswitchinterval() 设置切换频率。 因此,从 Python 程序的角度来看,每个字节码指令以及从每个指令到达的所有 C 实现代码都是原子的。

理论上,这意味着准确的记账需要对 PVM 字节码实现有准确的理解。 实际上,这意味着对“看起来原子”的内置数据类型(整数、列表、字典等)的共享变量的操作确实是。

例如,以下操作都是原子操作(L、L1、L2 是列表,D、D1、D2 是字典,x、y 是对象,i、j 是整数):

L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()

这些不是:

i = i+1
L.append(L[-1])
L[i] = L[j]
D[x] = D[x] + 1

当其他对象的引用计数达到零时,替换其他对象的操作可能会调用这些其他对象的 __del__() 方法,这可能会影响事物。 对于字典和列表的大量更新尤其如此。 如有疑问,请使用互斥锁!


我们不能摆脱全局解释器锁吗?

全局解释器锁 (GIL) 通常被视为 Python 在高端多处理器服务器机器上部署的障碍,因为多线程 Python 程序实际上只使用一个 CPU,由于坚持(几乎) 所有 Python 代码只能在持有 GIL 时运行。

回到 Python 1.5 的时代,Greg Stein 实际上实现了一个全面的补丁集(“自由线程”补丁),它删除了 GIL 并用细粒度锁取而代之。 Adam Olsen 最近在他的 python-safethread 项目中做了一个类似的实验。 不幸的是,这两个实验都表现出单线程性能的急剧下降(至少降低了 30% s),这是由于需要大量的细粒度锁定来补偿 GIL 的移除。

这并不意味着你不能在多 CPU 的机器上很好地使用 Python! 您只需要创造性地将工作划分到多个 进程 而不是多个 线程 。 新的 concurrent.futures 模块中的 ProcessPoolExecutor 类提供了一种简单的方法; multiprocessing 模块提供了一个较低级别的 API,以防您想要更多地控制任务的分派。

明智地使用 C 扩展也会有所帮助; 如果您使用 C 扩展来执行耗时的任务,则该扩展可以在执行线程在 C 代码中时释放 GIL,并允许其他线程完成一些工作。 一些标准库模块,例如 zlibhashlib 已经这样做了。

有人建议 GIL 应该是每个解释器状态的锁,而不是真正的全局锁; 然后解释器将无法共享对象。 不幸的是,这也不太可能发生。 这将是一项巨大的工作,因为目前许多对象实现都具有全局状态。 例如,缓存小整数和短字符串; 这些缓存必须移动到解释器状态。 其他对象类型有自己的空闲列表; 这些空闲列表必须移至口译状态。 等等。

而且我怀疑它甚至可以在有限的时间内完成,因为 3rd 方扩展也存在同样的问题。 很可能 3rd 方扩展的编写速度比您转换它们以将其所有全局状态存储在解释器状态中的速度要快。

最后,一旦你有多个解释器不共享任何状态,那么在单独的进程中运行每个解释器你有什么收获?


输入和输出

如何删除文件? (以及其他文件问题……)

使用 os.remove(filename)os.unlink(filename); 有关文档,请参阅 os 模块。 这两个功能是相同的; unlink() 只是此函数的 Unix 系统调用的名称。

要删除目录,请使用 os.rmdir(); 使用 os.mkdir() 创建一个。 os.makedirs(path) 将在 path 中创建任何不存在的中间目录。 os.removedirs(path) 将删除中间目录,只要它们是空的; 如果要删除整个目录树及其内容,请使用 shutil.rmtree()

要重命名文件,请使用 os.rename(old_path, new_path)

要截断文件,请使用 f = open(filename, "rb+") 打开它,然后使用 f.truncate(offset); offset 默认为当前搜索位置。 对于使用 os.open() 打开的文件,还有 os.ftruncate(fd, offset),其中 fd 是文件描述符(一个小整数)。

shutil 模块还包含许多处理文件的函数,包括 copyfile()copytree()rmtree() .


如何复制文件?

shutil 模块包含一个 copyfile() 函数。 请注意,在 MacOS 9 上,它不会复制资源分支和 Finder 信息。


如何读取(或写入)二进制数据?

要读取或写入复杂的二进制数据格式,最好使用 struct 模块。 它允许您获取包含二进制数据(通常是数字)的字符串并将其转换为 Python 对象; 反之亦然。

例如,以下代码从文件中读取两个 2 字节整数和一个大端格式的 4 字节整数:

import struct

with open(filename, "rb") as f:
    s = f.read(8)
    x, y, z = struct.unpack(">hhl", s)

格式字符串中的“>”强制大端数据; 字母“h”从字符串中读取一个“短整数”(2 个字节),而“l”从字符串中读取一个“长整数”(4 个字节)。

对于更规则的数据(例如 整数或浮点数的同类列表),您也可以使用 array 模块。

笔记

要读取和写入二进制数据,必须以二进制模式打开文件(此处将 "rb" 传递给 open())。 如果您使用 "r" 代替(默认),文件将以文本模式打开并且 f.read() 将返回 str 对象而不是 bytes 对象.


我似乎无法在用 os.popen() 创建的管道上使用 os.read(); 为什么?

os.read() 是一个低级函数,它接受一个文件描述符,一个代表打开文件的小整数。 os.popen() 创建一个高级文件对象,与内置 open() 函数返回的类型相同。 因此,要从使用 os.popen() 创建的管道 p 读取 n 字节,您需要使用 p.read(n)


如何访问串行 (RS232) 端口?

对于 Win32、POSIX(Linux、BSD 等)、Jython:

对于 Unix,请参阅 Mitch Chapman 的 Usenet 帖子:

为什么不关闭 sys.stdout (stdin, stderr) 真的关闭它?

Python file objects 是低级 C 文件描述符的高级抽象层。

对于您通过内置的 open() 函数在 Python 中创建的大多数文件对象,f.close() 从 Python 的角度将 Python 文件对象标记为已关闭,并安排关闭底层 C 文件描述符。 当 f 变成垃圾时,这也会在 f 的析构函数中自动发生。

但是 stdin、stdout 和 stderr 被 Python 特殊对待,因为 C 也赋予了它们特殊的地位。 运行 sys.stdout.close() 将 Python 级文件对象标记为已关闭,但 不会 关闭关联的 C 文件描述符。

要关闭这三个中的一个的底层 C 文件描述符,您应该首先确定这是您真正想要做的(例如,您可能会混淆尝试执行 I/O 的扩展模块)。 如果是,请使用 os.close()

os.close(stdin.fileno())
os.close(stdout.fileno())
os.close(stderr.fileno())

或者,您可以分别使用数字常量 0、1 和 2。


网络/互联网编程

有哪些适用于 Python 的 WWW 工具?

请参阅库参考手册中标题为 Internet 协议和支持Internet 数据处理 的章节。 Python 有许多模块可以帮助您构建服务器端和客户端 Web 系统。

可用框架的摘要由 Paul Boddie 在 https://wiki.python.org/moin/WebProgramming 维护。

Cameron Laird 在 http://phaseit.net/claird/comp.lang.python/web_python 维护了一组关于 Python Web 技术的有用页面。


如何模拟 CGI 表单提交 (METHOD=POST)?

我想检索作为 POST 表单结果的网页。 是否有现有的代码可以让我轻松地做到这一点?

是的。 这是一个使用 urllib.request 的简单示例:

#!/usr/local/bin/python

import urllib.request

# build the query string
qs = "First=Josephine&MI=Q&Last=Public"

# connect and send the server a path
req = urllib.request.urlopen('http://www.some-server.out-there'
                             '/cgi-bin/some-cgi-script', data=qs)
with req:
    msg, hdrs = req.read(), req.info()

请注意,通常对于百分比编码的 POST 操作,必须使用 urllib.parse.urlencode() 引用查询字符串。 例如,发送 name=Guy Steele, Jr.

>>> import urllib.parse
>>> urllib.parse.urlencode({'name': 'Guy Steele, Jr.'})
'name=Guy+Steele%2C+Jr.'

也可以看看

HOWTO 使用 urllib 包获取 Internet 资源 以获取大量示例。


我应该使用什么模块来帮助生成 HTML?

您可以在 Web 编程 wiki 页面 上找到一系列有用的链接。


如何从 Python 脚本发送邮件?

使用标准库模块 smtplib

这是一个使用它的非常简单的交互式邮件发件人。 此方法适用于任何支持 SMTP 侦听器的主机。

import sys, smtplib

fromaddr = input("From: ")
toaddrs  = input("To: ").split(',')
print("Enter message, end with ^D:")
msg = ''
while True:
    line = sys.stdin.readline()
    if not line:
        break
    msg += line

# The actual mail send
server = smtplib.SMTP('localhost')
server.sendmail(fromaddr, toaddrs, msg)
server.quit()

仅 Unix 的替代方案使用 sendmail。 sendmail 程序的位置因系统而异; 有时是 /usr/lib/sendmail,有时是 /usr/sbin/sendmail。 sendmail 手册页将帮助您。 这是一些示例代码:

import os

SENDMAIL = "/usr/sbin/sendmail"  # sendmail location
p = os.popen("%s -t -i" % SENDMAIL, "w")
p.write("To: receiver@example.com\n")
p.write("Subject: test\n")
p.write("\n")  # blank line separating headers from body
p.write("Some text\n")
p.write("some more text\n")
sts = p.close()
if sts != 0:
    print("Sendmail exit status", sts)

如何避免在套接字的 connect() 方法中阻塞?

select 模块通常用于帮助处理套接字上的异步 I/O。

为了防止 TCP 连接阻塞,您可以将套接字设置为非阻塞模式。 然后,当您执行 socket.connect() 时,您将立即(不太可能)连接或获得包含错误编号为 .errno 的异常。 errno.EINPROGRESS 表示连接正在进行中,但尚未完成。 不同的操作系统将返回不同的值,因此您将不得不检查系统上返回的内容。

您可以使用 socket.connect_ex() 方法来避免创建异常。 它只会返回 errno 值。 要轮询,您可以稍后再次调用 socket.connect_ex()0errno.EISCONN 表示您已连接 – 或者您可以将此套接字传递给 select.select() 检查它是否可写。

笔记

asyncio 模块提供了一个通用的单线程并发异步库,可用于编写非阻塞网络代码。 第三方 Twisted 库是一种流行且功能丰富的替代方案。


数据库

Python 中是否有数据库包的接口?

是的。

标准 Python 还包含基于磁盘的哈希接口,例如 DBMGDBM。 还有 sqlite3 模块,它提供了一个轻量级的基于磁盘的关系数据库。

支持大多数关系数据库。 有关详细信息,请参阅 DatabaseProgramming wiki 页面


你如何在 Python 中实现持久对象?

pickle 库模块以一种非常通用的方式解决了这个问题(尽管你仍然无法存储打开的文件、套接字或窗口之类的东西),而 shelf 库模块使用 pickle 和 ( g)dbm 创建包含任意 Python 对象的持久映射。


数学和数字

如何在 Python 中生成随机数?

标准模块 random 实现了一个随机数生成器。 用法很简单:

import random
random.random()

这将返回 [0, 1) 范围内的随机浮点数。

该模块中还有许多其他专门的生成器,例如:

  • randrange(a, b) 选择 [a, b) 范围内的整数。
  • uniform(a, b) 在 [a, b) 范围内选择一个浮点数。
  • normalvariate(mean, sdev) 对正态(高斯)分布进行采样。

一些更高级别的函数直接对序列进行操作,例如:

  • choice(S) 从给定的序列中选择一个随机元素。
  • shuffle(L) 原地打乱列表,即 随机排列它。

还有一个 Random 类,您可以实例化以创建独立的多个随机数生成器。