模块与标准库
Python 自带一大批现成可用的工具:随机数、数学运算、日期、文件路径,等等。这些工具存放在**模块(module)**里,你用 import 把它们引入自己的代码。在 文件与异常 一章里,你已经用过 import json。这一章会把 import 讲透,并介绍标准库中最有用的一些部分。
导入模块
最简单的导入方式是引入整个模块,然后用点号语法使用它里面的内容。你也可以从模块中导入特定的名字,这样使用时就不需要加前缀了。别名可以把长名字缩短。
import math
math.sqrt(16) # 4.0
math.pi # 3.141592653589793
math.floor(3.9) # 3
math.ceil(3.1) # 4从模块中导入特定名字,这样就可以直接使用它们:
from math import sqrt, pi
sqrt(16) # 4.0(不需要 "math." 前缀)
pi # 3.141592653589793给模块或名字取个别名来缩短它:
import math as m
m.sqrt(16) # 4.0
from math import sqrt as square_root
square_root(25) # 5.0别名在常用第三方库中很常见(比如 import numpy as np、import pandas as pd)。对于标准库模块,建议使用完整名字,这样代码更易读。
import math 会引入整个模块,之后你用点号语法进去用:math.sqrt(16)。from math import sqrt 只取出一个名字,这样就不用加前缀了。as 关键字可以给一个长的导入改名,但对标准库的东西,用完整名字反而读起来更清楚。 random
random 模块用来生成随机数、做随机选择。游戏、模拟、随机抽样,以及其他任何需要不可预测性的场景都可以用它。设置一个**种子(seed)**可以让结果可复现:同一个种子每次都会产生相同的序列。
import random
random.random() # 0 到 1 之间的浮点数(不含 1)
random.randint(1, 10) # 1 到 10 之间的整数(两端都包含)
random.uniform(1.0, 10.0) # 1.0 到 10.0 之间的浮点数
colours = ["red", "green", "blue"]
random.choice(colours) # 挑一项
random.choices(colours, k=3) # 挑 k 项(有放回)
random.sample(colours, k=2) # 挑 k 项(无放回)
numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers) # 原地打乱,返回 None想要结果可复现(在测试和数据科学中很有用),生成前先设置一个种子:
random.seed(42)
random.randint(1, 100) # 种子为 42 时,结果永远一样同一个种子在任何机器上每次都会产生相同的序列。
random.choice() 从列表里挑一项,random.randint(1, 10) 在一个区间内给你一个整数,两端都包含在内。想每次运行结果都一样,比如写测试?先调用一下 random.seed(),序列就固定下来了。 math
math 模块在你于 数字 一章中学到的基本运算符之外,提供了更高级的数学运算。平方根、幂运算、对数、三角函数,以及像 pi 和无穷大这样的特殊值,都在这里。
import math
math.sqrt(25) # 5.0
math.pow(2, 10) # 1024.0(和 2 ** 10 结果一样,但永远返回 float)
math.log(100, 10) # 2.0(以 10 为底的对数)
math.log(math.e) # 1.0(自然对数)
math.sin(math.pi / 2) # 1.0
math.cos(0) # 1.0
math.ceil(3.2) # 4
math.floor(3.9) # 3
math.trunc(3.9) # 3(对正数来说和 int() 一样)
math.inf # 无穷大
math.isnan(float("nan")) # True
math.isinf(math.inf) # Truemath 涵盖了 + - * / 之外的一切:平方根、幂运算、对数、三角函数,还有像 math.pi 这样的常量。一旦某个计算需要用到基本运算符之外的东西,就该想到它,这样你就不用自己去写公式了。 datetime
datetime 模块用来处理日期和时间。datetime.now() 给你当前的日期和时间。strftime() 把它格式化成字符串。strptime() 把字符串解析成 datetime。timedelta 表示一段可以加减的时长。
from datetime import datetime, date, timedelta
now = datetime.now() # 当前日期和时间
today = date.today() # 只有当前日期
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)
# 格式化
print(now.strftime("%Y-%m-%d")) # "2024-01-15"
print(now.strftime("%d %B %Y, %H:%M")) # "15 January 2024, 09:42"
# 解析
deadline = datetime.strptime("2024-12-31", "%Y-%m-%d")
# 算术运算
tomorrow = today + timedelta(days=1)
next_week = today + timedelta(weeks=1)
diff = deadline - now
print(f"{diff.days} days until deadline")常用的 strftime 代码:
| 代码 | 含义 | 示例 |
|---|---|---|
%Y | 4 位数年份 | 2024 |
%m | 月份(补零) | 01 |
%d | 日(补零) | 15 |
%H | 小时(24 小时制) | 09 |
%M | 分钟 | 42 |
%B | 完整月份名称 | January |
datetime.now() 给你当前的日期和时间。strftime() 用类似 %Y-%m-%d 这样的代码把它变成字符串,strptime() 则反过来,把字符串解析回去。timedelta 是一段可以加减的时长,所以 today + timedelta(days=1) 就是明天。 os 与 pathlib
pathlib 是处理文件路径的现代方式。Path 对象让你可以用 / 运算符来构建、检查和遍历路径。os 提供了对环境变量以及更底层操作系统操作的访问。新代码建议优先使用 pathlib。
from pathlib import Path
p = Path("data/reports")
p.exists() # 路径存在则为 True
p.is_dir() # 是目录则为 True
p.is_file() # 是文件则为 True
p.mkdir(parents=True, exist_ok=True) # 创建目录
for f in p.glob("*.csv"): # 目录中所有的 CSV 文件
print(f.name) # 文件名
report = p / "report_jan.csv" # / 运算符拼接路径
report.stem # "report_jan"(不含扩展名的文件名)
report.suffix # ".csv"
report.parent # Path("data/reports")
content = report.read_text() # 直接读取文件内容
report.write_text("new content\n") # 直接写入再来看 os 模块:
import os
os.getcwd() # 当前工作目录
os.listdir(".") # 列出目录内容
os.path.exists("data.txt") # 路径存在则为 True
os.path.join("data", "file.txt") # "data/file.txt"(跨平台)
os.environ.get("HOME") # 读取一个环境变量新代码建议优先使用 pathlib。需要用到环境变量,或是要对接期望字符串的老 API 时,再用 os。
pathlib.Path 把文件路径当成一个可以摆弄的对象:.exists()、.read_text()、.write_text()、.glob()。/ 运算符用来拼接路径片段,所以 p / "report.csv" 读起来就像一条真正的路径。新代码里,用 pathlib 代替老旧的 os.path。 timeit
timeit 用来测量一段代码运行所需的时间。当你想比较两种写法、挑出更快的那个时,它很有用。要多次运行代码,才能得到稳定的测量结果。
import timeit
# 给单条语句计时
timeit.timeit("sum(range(1000))", number=10000)
# 给更复杂的一段代码计时
setup = "data = list(range(1000))"
code = "[x * 2 for x in data]"
time = timeit.timeit(code, setup=setup, number=10000)
print(f"{time:.4f} seconds for 10,000 runs")number 表示要重复运行的次数。重复次数越多,测量结果就越稳定。
timeit.timeit() 通过一遍又一遍地运行一段代码来计时,然后你用总时间除以运行次数,得到一个稳定的单次耗时。它是用来确定"这两种写法哪个更快",而不是靠猜。运行一次太不稳定,让它多跑几遍才靠得住。 string
string 模块提供了预先构建好的字符串常量,涵盖字母、数字和标点符号。当你需要检查字符,或者从特定字符集中生成随机字符串时,这个模块很有用。
import string
string.ascii_lowercase # "abcdefghijklmnopqrstuvwxyz"
string.ascii_uppercase # "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
string.ascii_letters # 两者的组合
string.digits # "0123456789"
string.punctuation # 所有标点符号字符当你需要检查字符,或者生成随机字符串时会很有用:
import string, random
chars = string.ascii_letters + string.digits
password = "".join(random.choices(chars, k=12))string 模块给你现成的字符集:string.ascii_letters、string.digits、string.punctuation。它们都是普通字符串,所以可以遍历,也可以从中抽样。每当你需要一个已知的字符集来构建或做校验时,它就很方便。 创建自己的模块
任何一个 Python 文件都是一个模块。要在另一个文件中使用它,按文件名(不带 .py)导入即可。你可以导入整个模块,用点号语法使用它里面的内容,也可以直接导入特定的名字。
# utils.py
def clamp(value, lo, hi):
return max(lo, min(value, hi))
PI = 3.14159# main.py
import utils
utils.clamp(150, 0, 100) # 100
utils.PI # 3.14159
from utils import clamp
clamp(50, 0, 100) # 50Python 会在导入方文件所在的目录(以及其他几个位置)里查找模块。对于较大的项目,模块会被组织成包:也就是带有 __init__.py 文件的目录。
.py 文件本身就已经是一个模块了:按它的文件名(去掉 .py)导入它,里面的函数和变量就都归你用了。import utils 然后 utils.clamp(...),或者用 from utils import clamp 只拿一个名字。这就是把一个越长越大的程序拆分到多个文件里的办法。 __name__ == "__main__"
当 Python 直接运行一个文件时,__name__ 会被设为 "__main__"。当同一个文件被作为模块导入时,__name__ 则是这个模块的名字。这个用法让你可以写这样的代码:直接执行这个文件时它才运行,被别的模块导入时则会被跳过。
# utils.py
def clamp(value, lo, hi):
return max(lo, min(value, hi))
if __name__ == "__main__":
# 只有在这样运行时才会执行:python utils.py
# 而在这样导入时不会执行:import utils
print(clamp(150, 0, 100)) # 100对于任何一个同时也想作为独立脚本使用的模块,这都是一种标准写法。
if __name__ == "__main__": 让一个文件既能作为可导入的模块,也能作为可运行的脚本。守卫下面的代码在你执行 python utils.py 时会运行,而在别的文件 import utils 时会被跳过。很适合把一段快速的演示或测试塞在模块末尾。 标准库精选
再介绍几个值得了解的模块。每一个都解决了一个常见问题,而这些问题如果自己动手实现,都得花不少功夫。
collections:一些专用的容器类型:
from collections import Counter, defaultdict, deque
Counter(["a", "b", "a", "c", "a"]) # Counter({'a': 3, 'b': 1, 'c': 1})
defaultdict(list) # 缺失键会自动创建的字典
deque([1, 2, 3], maxlen=5) # 两端都能高效追加/弹出itertools:处理可迭代对象的工具:
import itertools
list(itertools.chain([1, 2], [3, 4])) # [1, 2, 3, 4]
list(itertools.islice(range(100), 5)) # [0, 1, 2, 3, 4]
list(itertools.combinations([1, 2, 3], 2)) # [(1, 2), (1, 3), (2, 3)]
list(itertools.product([0, 1], repeat=2)) # [(0,0), (0,1), (1,0), (1,1)]sys:访问 Python 解释器本身:
import sys
sys.argv # 命令行参数列表
sys.exit(1) # 以某个状态码退出
sys.version # Python 版本字符串第三方包:除了标准库,pip 还能安装社区提供的包:
pip install requests # HTTP 库
pip install pandas # 数据处理
pip install numpy # 数值计算第三方包超出了本指南的范围,但套路始终一样:先 pip install,再 import。
实战演练
结合 random、string 和 datetime,生成带时间戳的唯一游戏 ID:
import random
import string
from datetime import datetime
def generate_game_id(length: int = 8) -> str:
chars = string.ascii_uppercase + string.digits
return "".join(random.choices(chars, k=length))
def timestamp() -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
game_id = generate_game_id()
print(f"[{timestamp()}] Starting game {game_id}")
scores = [random.randint(50, 100) for _ in range(5)]
print(f"Round scores: {scores}")
print(f"Best: {max(scores)}")
