跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python

Python 开发中建议弃用的旧库与替代方案

对比了 Python 标准库更新后推荐的替代方案,包括使用 pathlib 替代 os.path 进行路径操作,secrets 模块替代 os.urandom 生成安全令牌,Zoneinfo 替代 pytz 处理时区,dataclasses 替代 namedtuple 定义数据结构,以及使用 logging 模块替代 print 语句进行日志记录。此外还介绍了 f-strings 字符串格式化、tomllib 解析 TOML 配置、setuptools 替代 distutils 打包等最佳实践,帮助开发者保持代码现代化。

雪落无声发布于 2025/2/7更新于 2026/8/2149 浏览
Python 开发中建议弃用的旧库与替代方案

随着每个 Python 版本的发布,都会添加新模块,并引入新的更好的做事方式。虽然我们都习惯了使用好的旧 Python 库和某些做事方式,但现在也是时候升级并利用新的和改进的模块及其特性了。

pathlib 而不是 os.path

pathlib 绝对是 Python 标准库中最近添加的重要部分之一,自 Python 3.4 以来,它一直是标准库的一部分,但很多人仍然使用 os 模块进行文件系统操作。

然而,pathlib 与旧的 os.path 相比具有许多优点:虽然 os 模块以原始字符串格式表示路径,但 pathlib 使用面向对象的样式,这使得它更具可读性和编写自然。

from pathlib import Path
import os.path

# 老方式
two_dirs_up = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# 新方式,可读性强
two_dirs_up = Path(__file__).resolve().parent.parent

路径被视为对象而不是字符串这一事实也使得可以创建一次对象,然后查找其属性或对其进行操作:

readme = Path("README.md").resolve()
print(f"Absolute path: {readme.absolute()}")
# Absolute path: /home/martin/some/path/README.md
print(f"File name: {readme.name}")
# File name: README.md
print(f"Path root: {readme.root}")
# Path root: /
print(f"Parent directory: {readme.parent}")
# Parent directory: /home/martin/some/path
print(f"File extension: {readme.suffix}")
# File extension: .md
print(f"Is it absolute: {readme.is_absolute()}")
# Is it absolute: True

我最喜欢 pathlib 的一个特性是可以使用 /(除法)运算符来连接路径:

# Operators:
etc = Path('/etc')
joined = etc / "cron.d" / "anacron"
print(f"Exists? - {joined.exists()}")
# Exists? - True

重要的是要注意 pathlib 只是替代 os.path 而不是整个 os 模块,它还包括 glob 模块的功能,因此如果你习惯于将 os.path 与 glob.glob 结合使用,那么你可以完全用 pathlib 替代它们。

在上面的片段中,我们展示了一些方便的路径操作和对象属性,但 pathlib 还包括你习惯于 os.path 的所有方法,例如:

print(f"Working directory: {Path.cwd()}")  # same as os.getcwd()
# Working directory: /home/martin/some/path
Path.mkdir(Path.cwd() / "new_dir", exist_ok=True)  # same as os.makedirs()
print(Path("README.md").resolve())  # same as os.path.abspath()
# /home/martin/some/path/README.md
print(Path.home())  # same as os.path.expanduser()
# /home/martin

有关 os.path 函数到 pathlib 中新函数的完整映射,请参阅官方文档。

secrets 而不是 os.urandom

说到 os 模块,你应该停止使用的另一部分是 os.urandom。相反,你应该使用自 Python 3.6 以来可用的新 secrets 模块。

# 老方式:
import os
length = 64
value = os.urandom(length)
print(f"Bytes: {value}")
# Bytes: b'\xfa\xf3...\xf2\x1b\xf5\xb6'
print(f"Hex: {value.hex()}")
# Hex: faf3cc656370e31a938e7...33d9b023c3c24f1bf5

# 新方式:
import secrets
value = secrets.token_bytes(length)
print(f"Bytes: {value}")
# Bytes: b'U\xe9n\x87...\x85>\x04j:\xb0'
value = secrets.token_hex(length)
print(f"Hex: {value}")
# Hex: fb5dd85e7d73f7a08b8e3...4fd9f95beb08d77391

使用 os.urandom 实际上并不是这里的问题,引入 secrets 模块的原因是因为人们使用 random 模块来生成密码等,即使随机模块不产生密码安全令牌。

根据文档,random 模块不应用于安全目的,你应该使用 secrets 或 os.urandom,但 secrets 模块绝对更可取,因为它比较新,并且包含一些用于十六进制令牌的实用程序/便利方法以及 URL 安全令牌。

zoneinfo 而不是 pytz

在 Python 3.9 之前,没有用于时区操作的内置库,所以每个人都在使用 pytz,但现在我们在标准库中有 zoneinfo,所以是时候切换了。

from datetime import datetime
import pytz  # pip install pytz

dt = datetime(2022, 6, 4)
nyc = pytz.timezone("America/New_York")
localized = nyc.localize(dt)
print(f"Datetime: {localized}, Timezone: {localized.tzname()}, TZ Info: {localized.tzinfo}")

# 新方式:
from zoneinfo import ZoneInfo

nyc = ZoneInfo("America/New_York")
localized = datetime(2022, 6, 4, tzinfo=nyc)
print(f"Datetime: {localized}, Timezone: {localized.tzname()}, TZ Info: {localized.tzinfo}")
# Datetime: 2022-06-04 00:00:00-04:00, Timezone: EDT, TZ Info: America/New_York

datetime 模块将所有时区操作委托给抽象基类 datetime.tzinfo,这个抽象基类需要一个具体的实现——在引入这个很可能来自 pytz 的模块之前。现在我们在标准库中有 zoneinfo,我们可以使用它。

然而,使用 zoneinfo 有一个警告——它假定系统上有可用的时区数据,UNIX 系统就是这种情况,如果你的系统没有时区数据,那么你应该使用 tzdata 包,它是由 CPython 核心开发人员维护的第一方库,其中包含 IANA 时区数据库。

dataclasses 而不是 namedtuple

Python 3.7 的一个重要补充是 dataclasses 包,它是 namedtuple 的替代品。

你可能想知道为什么需要替换 namedtuple?以下是你应该考虑切换到数据类的一些原因:

  1. 它可以是可变的
  2. 默认提供 repr、eq、init、hash 魔术方法
  3. 允许指定默认值
  4. 支持继承。此外,数据类还支持 frozen 和 slots(从 3.10 开始)属性以提供与命名元组的特征奇偶校验

切换真的不应该太难,因为你只需要更改定义:

# 老方式:
# from collections import namedtuple
from typing import NamedTuple
import sys

User = NamedTuple("User", [("name", str), ("surname", str), ("password", bytes)])
u = User("John", "Doe", b'tfeL+uD...\xd2')
print(f"Size: {sys.getsizeof(u)}")
# Size: 64

# 新方式:
from dataclasses import dataclass

@dataclass()
class User:
    name: str
    surname: str
    password: bytes

u = User("John", "Doe", b'tfeL+uD...\xd2')
print(u)
# User(name='John', surname='Doe', password=b'tfeL+uD...\xd2')

print(f"Size: {sys.getsizeof(u)}, {sys.getsizeof(u) + sys.getsizeof(vars(u))}")
# Size: 48, 152

在上面的代码中,我们还包含了大小比较,因为这是 namedtuple 和数据类之间的较大差异之一,如上所见,命名元组的大小要小得多,这是由于数据类使用 dict 来表示属性。

至于速度比较,除非你计划创建数百万个实例,否则属性的访问时间应该基本相同,或者不够重要:

import timeit

setup = '''
from typing import NamedTuple
User = NamedTuple("User", [("name", str), ("surname", str), ("password", bytes)])
u = User("John", "Doe", b'')
'''

print(f"Access speed: {min(timeit.repeat('u.name', setup=setup, number=10000000))}")
# Access speed: 0.16838401100540068

setup = '''
from dataclasses import dataclass

@dataclass(slots=True)
class User:
    name: str
    surname: str
    password: bytes

u = User("John", "Doe", b'')
'''

print(f"Access speed: {min(timeit.repeat('u.name', setup=setup, number=10000000))}")
# Access speed: 0.17728697300481144

如果以上内容说服了你打算切换到数据类,请尽快尝试吧。

相反,如果你不想切换并且出于某种原因真的想使用命名元组,那么你至少应该使用 typing 模块中的 NamedTuple 而不是 collections 中的 namedtuple:

# 不好方式的:
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])

# 更好的方式:
from typing import NamedTuple
class Point(NamedTuple):
    x: float
    y: float

最后,如果你既不使用 namedtuple 也不使用数据类,你可能需要考虑直接使用 Pydantic。

proper logging 而不是 print

这不是标准库的最新添加,但值得使用——你应该使用正确的日志记录而不是打印语句。如果你在本地调试问题,则可以使用 print,但对于任何无需用户干预即可运行的生产就绪程序,正确的日志记录是必须的。

特别是考虑到设置 Python 日志记录非常简单:

import logging
logging.basicConfig(
    filename='application.log',
    level=logging.WARNING,
    format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',
    datefmt='%H:%M:%S'
)

logging.error("Some serious error occurred.")
# [12:52:35] {<stdin>:1} ERROR - Some serious error occurred.
logging.warning('Some warning.')
# [12:52:35] {<stdin>:1} WARNING - Some warning.

与打印语句相比,上面的简单配置将为你提供卓越的调试体验。最重要的是,你可以进一步自定义日志库以记录到不同的位置、更改日志级别、自动轮换日志等。例如,可以使用 RotatingFileHandler 来限制日志文件大小,或使用 StreamHandler 输出到控制台。

f-strings 而不是 format

Python 包含很多格式化字符串的方法,包括 C 样式格式化、f 字符串、模板字符串或 .format 函数。不过,其中之一——f-strings(格式化的字符串文字),它们写起来更自然,可读性更强,并且是前面提到的选项中最快的。

因此,我认为没有必要争论或解释为什么要使用它们,然而,在某些情况下不能使用 f 字符串:

使用 % 格式的唯一原因是用于记录:

import logging

things = "something happened..."

logger = logging.getLogger(__name__)
logger.error("Message: %s", things)  # 评估内部记录器方法
logger.error(f"Message: {things}")  # 立即评估

在上面的示例中,如果你使用 f 字符串,则表达式将立即计算,而使用 C 样式格式,替换将被推迟到实际需要时,这对于消息分组很重要,其中具有相同模板的所有消息都可以记录为一个,这不适用于 f 字符串,因为模板在传递给记录器之前填充了数据。

此外,有些事情是 f-strings 根本无法做到的,例如在运行时填充模板——即动态格式——这就是 f-strings 被称为文字字符串格式的原因:

# 动态设置模板及其参数
def func(tpl: str, param1: str, param2: str) -> str:
    return tpl.format(param=param1, param2=param2)

some_template = "First template: {param1}, {param2}"
another_template = "Other template: {param1} and {param2}"
print(func(some_template, "Hello", "World"))
print(func(another_template, "Hello", "Python"))

# 动态重用具有不同参数的相同模板.
inputs = ["Hello", "World", "!"]
template = "Here's some dynamic value: {value}"

for value in inputs:
    print(template.format(value=value))

最重要的是,尽可能使用 f 字符串,因为它们更具可读性和更高性能,但请注意,在某些情况下仍然首选和/或需要其他格式样式。

tomllib 而不是 tomli

TOML 是一种广泛使用的配置格式,对于 Python 的工具和生态系统尤其重要,因为它用于 pyproject.toml 配置文件。到目前为止,你必须使用外部库来管理 TOML 文件,但是从 Python 3.11 开始,将有一个名为 tomllib 的内置库,它基于 toml 包。

所以,一旦你切换到 Python 3.11,你应该养成使用 import tomllib 而不是 import tomli 的习惯。少了一种需要担心的依赖!

# import tomli as tomllib
import tomllib

with open("pyproject.toml", "rb") as f:
    config = tomllib.load(f)
    print(config)
    # {'project': {'authors': [{'email': '[email protected]',
    #                          'name': 'Martin Heinz'}],
    #              'dependencies': ['flask', 'requests'],
    #              'description': 'Example Package',
    #              'name': 'some-app',
    #              'version': '0.1.0'}}

toml_string = """
[project]
name = "another-app"
description = "Example Package"
version = "0.1.1"
"""

config = tomllib.loads(toml_string)
print(config)
# {'project': {'name': 'another-app', 'description': 'Example Package', 'version': '0.1.1'}}

注意 tomllib 仅支持读取,如果需要写入,仍需使用第三方库如 tomlkit。

setuptools 而不是 distutils

最后一个更像是弃用通知:

由于 Distutils 已弃用,因此同样不鼓励使用任何来自 distutils 的函数或对象,Setuptools 旨在替换或弃用所有此类用途。

是时候告别 distutils 包并切换到 setuptools 了,setuptools 文档提供了有关如何替换 distutils 用法的指导,除此之外,PEP 632 还为 setuptools 未涵盖的部分 distutils 提供迁移建议。在现代 Python 项目中,推荐使用 pyproject.toml 配合 setuptools 作为构建后端,彻底移除 setup.py 中的 distutils 引用。

总结

每个新的 Python 版本都会带来新的特性,因此我建议你查看 Python 发行说明中的'新模块'、'不推荐使用的模块'和'已删除的模块'部分,这是了解 Python 标准重大变化的好方法。通过这种方式,你可以不断地将新功能和最佳实践整合到你的项目中,保持项目的可维护性和安全性。

目录

  1. pathlib 而不是 os.path
  2. 老方式
  3. 新方式,可读性强
  4. Absolute path: /home/martin/some/path/README.md
  5. File name: README.md
  6. Path root: /
  7. Parent directory: /home/martin/some/path
  8. File extension: .md
  9. Is it absolute: True
  10. Operators:
  11. Exists? - True
  12. Working directory: /home/martin/some/path
  13. /home/martin/some/path/README.md
  14. /home/martin
  15. secrets 而不是 os.urandom
  16. 老方式:
  17. Bytes: b'\xfa\xf3...\xf2\x1b\xf5\xb6'
  18. Hex: faf3cc656370e31a938e7...33d9b023c3c24f1bf5
  19. 新方式:
  20. Bytes: b'U\xe9n\x87...\x85>\x04j:\xb0'
  21. Hex: fb5dd85e7d73f7a08b8e3...4fd9f95beb08d77391
  22. zoneinfo 而不是 pytz
  23. 新方式:
  24. Datetime: 2022-06-04 00:00:00-04:00, Timezone: EDT, TZ Info: America/New_York
  25. dataclasses 而不是 namedtuple
  26. 老方式:
  27. from collections import namedtuple
  28. Size: 64
  29. 新方式:
  30. User(name='John', surname='Doe', password=b'tfeL+uD...\xd2')
  31. Size: 48, 152
  32. Access speed: 0.16838401100540068
  33. Access speed: 0.17728697300481144
  34. 不好方式的:
  35. 更好的方式:
  36. proper logging 而不是 print
  37. [12:52:35] {<stdin>:1} ERROR - Some serious error occurred.
  38. [12:52:35] {<stdin>:1} WARNING - Some warning.
  39. f-strings 而不是 format
  40. 动态设置模板及其参数
  41. 动态重用具有不同参数的相同模板.
  42. tomllib 而不是 tomli
  43. import tomli as tomllib
  44. {'project': {'name': 'another-app', 'description': 'Example Package', 'version': '0.1.1'}}
  45. setuptools 而不是 distutils
  46. 总结
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 数据中台血缘可视化实践:基于 Neo4j 的图数据库方案
  • Python 包管理实战:pip 常用命令速查
  • LLaMA 3.1 模型本地部署与 Streamlit 聊天机器人实战
  • Linux 命令行参数与环境变量详解
  • 大语言模型综述:预训练、微调、应用与评估详解
  • Stable Diffusion v2-1-base 模型使用指南与代码实践
  • Transformer 三种注意力机制详解与 PyTorch 实现
  • OpenSpec 规范驱动 AI 协作:深入解析开发流程变革
  • 主流 AI 编程助手对比:Claude、Cursor、Aider 与 Copilot 怎么选
  • OpenClaw 2026.3.7 更新:支持 GPT-5.4 与记忆热插拔架构
  • RAG 场景中复杂 PDF 解析工具对比:PyMuPDF、Nougat 与 TextIn
  • Python 3.14.0 安装与使用指南
  • GESP C++ 七级真题解析:矩阵移动
  • FLUX.1-dev 与 Stable Diffusion 对比评测:图像质量与生成速度
  • C++11右值引用与移动语义:从深拷贝到资源转移的实战理解
  • Python 网络爬虫实战:从基础请求到数据可视化
  • 前端权限管理实战:从设计到落地的完整方案
  • C++ 内核性能优化十大误区:如何避免常见陷阱
  • FAIR plus 机器人全产业链接会:聚焦具身智能与产业链协同
  • 前端开发中集成与应用 AI 技术详解

相关免费在线工具

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online