本文将带你完整梳理 Python 工程化最核心的四大板块:模块与包、文件操作与 JSON、路径管理,以及异常处理。


第一部分:模块与包

在 Python 中,每一个 .py 文件就是一个单独的模块,而包含 __init__.py 文件的文件夹就是一个包

1. 导入 (Import) 的四种方法

假设有一个 tools.py 文件,里面有一个 calculate() 函数。

  • import tools:最推荐的写法。调用时使用 tools.calculate(),来源清晰明确。
  • import tools as tl:起别名。适合模块名过长的情况(如 import matplotlib.pyplot as plt)。
  • from tools import calculate:直接导入特定函数,调用时直接写 calculate()。注意:如果当前文件也有同名函数,会引发命名冲突。
  • from tools import *最不推荐的初学者陷阱。它会把目标模块里的所有内容倒进当前空间,极易导致代码污染和难以排查的 Bug。

2. 模块的“通行证”:if __name__ == "__main__":

几乎所有专业的 Python 脚本底部都有这一行。它的作用就是区分是否是当前这个模块在被运行,如果是的话才会执行这个语句下的代码,一般可以写点测试代码在这里面或者是控制执行一个main()方法中的流程。

每个模块都有一个内置属性 __name__

  • 当脚本被直接运行时,__name__ 的值为 "__main__"
  • 当脚本被导入 (import) 时,__name__ 的值是模块名(即文件名)。

3. __init__.py:包的“总服务台”

在pycharm中,可以在一个文件夹下直接创建一个python软件包,_init_.py 就会出现在这个包文件加下。
在一个包文件夹中,__init__.py 扮演着接口控制器的角色:

  • 白名单机制 (__all__):在 __init__.py 中定义

      比如下面这个,在__init__中写下第一行代码,外部使用from package import *的时候
      就只能导入func_a,func_b这两个模块。
    
    __all__ = ["func_a", "func_b"]`
    
  • 命名空间提升:通过在 __init__.py 中提前执行 from .sub_folder.tool import func,用户可以直接通过 import package 调用深层功能,无需关心复杂的内部层级。

    如下所示

    #不写的时候
    import package.sub_folder.tool
    package.sub_folder.tool.func()
    #包的内部层级暴露给了使用者,如果以后重构改变 sub_folder 或 tool 的位置,所有使用的地方都要改
    #而如果写了,用户可以直接使用最顶层的包名来使用func函数
    import package
    package.func()    
    #或者
    from package import func
    func()
    

4. 绝对导入和相对导入

  • 绝对导入:以项目根目录为起点的完整路径(如 from my_app.models.user import User)。就像完整的 GPS 地址,永远准确。强烈建议全部使用绝对导入。
  • 相对导入:以当前文件为起点的路径(如 from .utils import func)。就像邻里指路,极易引发 ImportError: attempted relative import with no known parent package(特别是当你直接运行子文件时),因为有时别人运行这个模块的时候文件的路径变了。

第二部分:文件操作与 JSON

1. with 上下文管理器

open(‘文件路径’,‘权限’,encoding = ‘编码格式’)可以打开一个文件夹,之后就用他来读取文件中的内容
而使用 with open() 可以确保无论程序是否报错,文件最终都会被安全关闭释放。

with open("data.txt", "r", encoding="utf-8") as f:
    content = f.read()

注意:只要处理包含中文的文本文件,务必加上 encoding="utf-8" 防止乱码。

2. 读写模式 (Mode) 与组合写法

  • r (只读):默认模式,文件必须存在。
  • w (覆盖写):文件存在则清空重写,不存在则创建。
  • a (追加写):在文件末尾追加内容。
  • 功能增强组合 (+ / b)
    • r+:可读可写,不会清空文件。适合修改中间内容,但需要配合 f.seek(位置的数字) 拨动文件指针。如果没用f.seek的话会从最开始修改,覆盖后面的内容(比如原来的111222被覆盖后变成aaa222)
    • w+:可读可写。注意陷阱:一打开就会立刻清空旧内容
    • a+:可读可写。写入操作永远在文件最末尾发生。感觉这个最常用……
    • rb / wb:用于处理图片、音频等非文本(二进制)文件。

举几个例子

# 写模式 (w+):清空后写入新内容
with open("output.txt", "w+", encoding="utf-8") as f:
    f.write("直接清空之前的内容\n")

# 追加模式 (a+):在文件末尾添加内容
with open("output.txt", "a", encoding="utf-8") as f:
    f.write("这行会被追加到文件末尾,不会动之前的\n")

# 读写模式 (r+):先读后写,需要移动指针
with open("output.txt", "r+", encoding="utf-8") as f:
    old = f.read()          # 读取全部内容
    f.seek(0)               # 将指针移回开头
    f.write("【新增开头】\n" + old)  # 在开头插入文字
    #感觉挺鸡肋

3. 数据交换通用语言:JSON (JavaScript Object Notation)

JSON 是目前互联网上最流行的数据交换格式。它之所以在 Python 中如此受欢迎,是因为它的结构与 Python 的字典 (dict)列表 (list) 几乎一模一样。

为了彻底掌握 JSON,我们首先要记住一个口诀:“S” 代表 String (字符串)

(1) 四大核心方法:带 S 与不带 S 的区别

json 模块中,操作分为“面向文件”和“面向内存字符串”两类:

类别方法作用场景
处理文件 (不带 S)json.dump(data, f)将数据写入文件保存游戏存档、配置文件
json.load(f)从文件读取数据程序启动时加载用户设置
处理字符串 (带 S)json.dumps(data)将数据转成 JSON 字符串向网页发送数据、打印调试
json.loads(str)JSON 字符串转回字典解析从 API 获取的文本
(2) 详细参数解析

在调用 dumpdumps 时,为了让生成的 JSON 文件“既好看又好读”,有两个参数是必加的:

  • indent=4:让数据自动缩进 4 个空格。如果不加,所有数据会挤成一行,人类极难阅读。
  • ensure_ascii=False处理中文必加! 默认情况下,JSON 会将中文转义为 \u6211 这样的字符编码。加上这一句,中文才能正常显示。

代码示例:

import json

data = {"name": "张三", "role": "初学者", "score": 100}

# 存为文件
with open("user.json", "w", encoding="utf-8") as f:
    json.dump(data, f, indent=4, ensure_ascii=False)

# 转为字符串
json_str = json.dumps(data, ensure_ascii=False)
print(json_str) # 输出: {"name": "张三", "role": "初学者", "score": 100}

(3) 对象存储陷阱 (The Object Trap)

这是最让初学者抓狂的地方:JSON 并不认识你自定义的类。

如果你定义了一个 User 类并试图直接 dump 它的实例,Python 会报错:TypeError: Object of type User is not JSON serializable

  • 原因:JSON 是一种标准协议,它只认识 dict, list, str, int, float, bool, None。它不知道如何把你的 User 对象拆解成文本。
  • 解决方案:使用 obj.__dict__。这个内置属性会自动将对象的所有属性提取成一个字典。

代码示例:

class User:
    def __init__(self, name, id):
        self.name = name
        self.id = id

u1 = User("Bob", 1)

# 错误做法:json.dump(u1, f) -> 报错
# 正确做法:
with open("user_data.json", "w") as f:
    json.dump(u1.__dict__, f) # 存入的是 {'name': 'Bob', 'id': 1}

(4) 存储多个对象:为什么不能用 a+

如果你想存 100 个用户,千万不要用 a+ 模式循环执行 json.dump

  • 错误现象:使用 a+ 连续写入两个对象,文件内容会变成:{"id": 1}{"id": 2}
  • 致命后果:这不是合法的 JSON 格式(缺少逗号,也没有外层中括号)。当你用 json.load() 读取时,程序会直接崩溃

正确的“多对象存储”逻辑:
你应该把所有对象放入一个列表中,然后整体写入。

users = [User("Alice", 1), User("Bob", 2), User("Cindy", 3)]

# 1. 序列化:将对象列表转为字典列表
data_list = [u.__dict__ for u in users]

# 2. 写入:一次性写入一个大列表
with open("all_users.json", "w") as f:
    json.dump(data_list, f, indent=4)

# 3. 读取并“复活”:读回来的只是字典,需要手动转回对象
with open("all_users.json", "r") as f:
    raw_data = json.load(f) # 此时 raw_data 是列表,里面装的是字典
    # 实例化还原
    reconstructed_users = [User(d['name'], d['id']) for d in raw_data]

核心总结
  1. 进出文件用 dump/load处理字符串用 dumps/loads
  2. 存对象必用 __dict__,把对象“降级”为字典。
  3. 多对象必用 [] 列表包裹,千万别用 a+ 强行拼接。
  4. 读取回来的数据是**“死的” (字典)**,如果需要调用类方法,必须重新执行 User(...) 实例化

第三部分:路径处理

在管理系统文件时,我们经历了从“字符串拼接”到“对象化管理”的跨越。处理路径最核心的挑战在于:Windows 使用反斜杠 \,而 Mac 和 Linux 使用正斜杠 /。如果你的代码里写死了路径字符串,换个电脑可能就崩了。


1. 传统的 os 模块

在 Python 3.4 之前,os 及其子模块 os.path 是处理文件的唯一标准。它将路径视为字符串,通过各种函数对字符串进行切割、拼接和判断。

(1) 基础管理命令:你的“终端指令” Python 版

这些方法直接对应我们在命令行(CMD/Terminal)中常用的指令。

import os

# 1. 获取当前工作目录 (Equivalent to 'pwd')
cwd = os.getcwd() 
print(f"当前 Python 运行的位置是: {cwd}")

# 2. 列出目录下的文件和文件夹 (Equivalent to 'ls' or 'dir')
# "." 代表当前目录,也可以传入绝对路径
items = os.listdir(".")
print(f"当前目录下有: {items}")

# 3. 递归创建文件夹
# mkdir 只能建一层;makedirs 可以直接建出 'a/b/c' 这种深层结构
if not os.path.exists("backup/2026/march"):
    os.makedirs("backup/2026/march")
    print("多级目录创建成功!")

# 4. 重命名与删除
# os.rename("old_name.txt", "new_name.txt")
# os.remove("temp.tmp") # 删除文件
# os.rmdir("empty_folder") # 只能删除空文件夹

(2) 路径拼接

绝对不要手动用加号拼接路径:path = "data/" + "user.json"
因为 Windows 用反斜杠 \,而 Linux/Mac 用正斜杠 /

必须使用 os.path.join():它会根据运行代码的系统,自动塞进正确的斜杠。

# 稳健的写法
dir_name = "configs"
file_name = "database.yaml"
full_path = os.path.join(dir_name, file_name)

print(full_path) 
# Windows 输出: configs\database.yaml
# Mac/Linux 输出: configs/database.yaml

(3) 核心重难点:os.walk() 深度遍历

如果你要在一个巨大的文件夹(比如你的“我的文档”)里寻找所有的 .mp4 文件,os.walk() 是效率最高的工具。它会像剥洋葱一样,一层一层深入所有的子文件夹。

它的运行机制:
os.walk(path) 会返回一个生成器,每次循环它都会给你一个包含 3 个变量 的元组:

  1. root (当前目录路径):当前正在扫描的这一层文件夹的路径。
  2. dirs (子文件夹列表):当前路径下所有的文件夹名字(列表)。
  3. files (文件列表):当前路径下所有的文件名字(列表)。

实战案例:全盘搜索特定文件并计算大小
假设我们要找当前目录下所有的 .txt 文件:

import os

search_dir = "." # 从当前目录开始找

for root, dirs, files in os.walk(search_dir):
    # root: 字符串,当前文件夹路径
    # dirs: 列表,当前文件夹下的子目录
    # files: 列表,当前文件夹下的所有文件
    
    print(f" 正在进入目录: {root}")
    
    for filename in files:
        if filename.endswith(".txt"):
            # 必须把 root 和 filename 拼接起来,才是文件的完整路径
            full_path = os.path.join(root, filename)
            file_size = os.path.getsize(full_path) # 获取文件字节大小
            print(f"   发现目标: {filename} ({file_size} bytes)")
  • 全自动:它会自动进入子文件夹的子文件夹,直到穷尽所有角落。
  • 分工明确:它通过三个变量帮你分好了类。如果你只想对文件夹操作,就遍历 dirs;如果只想处理文件,就遍历 files

(4) os.path 常用小工具
方法作用例子
os.path.abspath(path)获取绝对路径test.txt 变成 C:\Users\Bob\test.txt
os.path.split(path)拆分目录和文件名/a/b.txt 拆成 ('/a', 'b.txt')
os.path.exists(path)判断路径是否存在返回 TrueFalse
os.path.isfile/isdir判断是文件还是文件夹过滤掉文件夹,只处理文件时必用

2. pathlib 模块

pathlib 将路径从“死字符串”变成了“活对象”。它不仅更好看,而且功能更聚合。

(1) 魔法拼接符号 /

pathlib 重载了除法运算符,让拼接路径像在资源管理器里点鼠标一样直观:

from pathlib import Path

# 极其优雅的拼接方式
base_path = Path("home")
user_path = base_path / "bob" / "scripts" / "run.py"

print(user_path) # 输出会自动适配系统斜杠
(2) 路径属性拆解

假设我们有一个路径对象 p = Path("C:/Downloads/study.tar.gz"),你可以像剥洋葱一样获取它的信息:

属性结果说明
p.name'study.tar.gz'完整的文件名
p.stem'study.tar'剥掉最后一层后缀后的名字
p.suffix'.gz'最后一个后缀
p.parentPath('C:/Downloads')父级目录
p.exists()True/False路径是否存在(方法)
(3) 一气呵成的文件操作

pathlib 集成了简单的读写功能,小文件处理甚至不需要 open()

p = Path("hello.txt")

# 一行写入文本 (自动处理 open 和 close)
p.write_text("Hello Pathlib!", encoding="utf-8")

# 一行读取文本
content = p.read_text(encoding="utf-8")

# 快速改名并移动
# p.rename(p.parent / "new_name.txt")

3. ospathlib 对比

我们要实现同一个功能:检查 data 文件夹是否存在,不存在就创建,然后拼接出内部 result.json 的绝对路径。

老派写法 (os):

import os
folder = "data"
if not os.path.exists(folder):
    os.mkdir(folder)
file_path = os.path.abspath(os.path.join(folder, "result.json"))

现代写法 (pathlib):

from pathlib import Path
p = Path("data")
p.mkdir(exist_ok=True) # exist_ok=True 代表存在也不报错,一行顶三行
file_path = (p / "result.json").resolve() # resolve() 获取绝对路径

路径处理

  1. Windows 的“转义”:在 Windows 字符串路径里,\ 会被误认为转义符(比如 \n 是换行)。
    • 对策:使用 r"C:\Users\Name"(原始字符串)或者直接用 pathlib
  2. 绝对路径 vs 相对路径
    • ./data 是相对路径(相对于你运行代码时的位置)。
    • C:/data 是绝对路径。
    • 建议:在大型项目中,尽量先用 .resolve()os.path.abspath() 将路径转为绝对路径,避免因为运行位置不同而找不到文件。
  3. 删库跑路警告os.remove()os.rmdir()永久删除,不会经过回收站。在执行删除代码前,务必先 print 一下路径确认没搞错!

第四部分:异常处理

在进行文件读写和系统交互时,报错(如文件丢失、权限不足)是家常便饭。一套完整的异常处理逻辑包含 5 个关键动作:

def process_config_data(file_path):
    try:
        # 1. try :存放高风险操作
        print(f"正在尝试打开文件: {file_path}")
        f = open(file_path, 'r')
        data = f.read().strip()
        result = 100 / int(data) 
        
    except FileNotFoundError:
        # 2. except :处理文件不存在的情况
        print("错误:找不到指定的配置文件!")
    except ZeroDivisionError:
        # 针对不同类型的错误,可以有多个
        print("错误:文件中的数字不能为零!")
    except Exception as e:
        # 捕获其他未知错误
        print(f"发生了意外错误: {e}")
        
    else:
        # 3. else :只有 try 成功了才会运行
        print(f"数据处理成功!计算结果为: {result}")
        
    finally:
        # 4. finally :无论成败,都要关门(释放资源)
        if 'f' in locals() and not f.closed:
            f.close()
            print("文件句柄已安全关闭。")

# 5. raise :在业务逻辑处手动触发
def set_age(age):
    if age < 0:
        raise ValueError("年龄不能为负数!") # 哪怕语法没错,业务错了也要报警
    return age
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐