1. Python异常捕获:从崩溃边缘拯救你的代码
作为一名Python开发者,最尴尬的时刻莫过于用户向你展示那个鲜红的Traceback界面。上周我就遇到了这种情况——一个简单的除法运算因为用户输入了0而导致整个数据分析程序崩溃。这正是异常捕获机制存在的意义。
Python的异常处理基于try-except-else-finally结构,这就像给代码穿上了一套防护服。当你在try块中放入可能出错的代码时,except块就是你的紧急预案。我特别喜欢else块的设定,它只在不发生异常时执行,这比在try块末尾直接写正常逻辑要清晰得多。
python复制try:
result = 10 / int(input("请输入除数: "))
except ZeroDivisionError:
print("数学老师没教过你不能除以0吗?")
except ValueError:
print("请输入数字,别闹!")
else:
print(f"计算结果:{result}")
finally:
print("程序执行完毕,无论成功与否")
在实际项目中,我养成了几个异常处理的好习惯:
- 永远不要用裸except(即不指定异常类型),这会连KeyboardInterrupt(Ctrl+C)都捕获
- 将特定异常放在前面,通用Exception放在最后
- 使用as关键字获取异常对象,便于记录详细错误信息
- 在日志中记录完整的traceback而不仅仅是错误消息
经验之谈:处理文件操作时,一定要在finally块中关闭文件句柄,或者直接使用with语句。我见过太多因为未关闭文件导致资源泄漏的案例。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 文件操作:数据持久化的核心技能
上周需要处理一个2GB的日志文件时,我才真正理解了Python文件操作的精髓。与临时存储在内存中的数据不同,文件操作让我们能够永久保存数据——这是所有严肃程序的基础。
Python通过open()函数提供了多种文件模式:
- 'r':只读(默认)
- 'w':写入(会清空原有内容!)
- 'a':追加
- 'b':二进制模式
- '+':读写模式
我强烈推荐使用上下文管理器(with语句)来处理文件,这比手动调用close()要可靠得多:
python复制with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read()
# 文件会自动关闭,即使在处理过程中发生异常
对于大型文件,逐行读取是更高效的方式:
python复制with open('big_file.log', 'r') as f:
for line in f: # 内存友好型读取
process_line(line)
在最近的一个项目中,我需要同时处理多个文件,这时使用with的嵌套写法就非常优雅:
python复制with open('source.csv', 'r') as src, open('output.json', 'w') as dst:
data = parse_csv(src)
json.dump(data, dst)
文件路径处理方面,我强烈推荐使用pathlib模块,它比传统的os.path更现代、更直观:
python复制from pathlib import Path
config_file = Path('config') / 'settings.ini' # 跨平台路径拼接
if config_file.exists():
content = config_file.read_text()
3. 模块化编程:构建可维护的代码库
当我第一次看到超过1000行的Python脚本时,我意识到模块化的重要性。模块(.py文件)和包(包含__init__.py的目录)是Python组织代码的基本单元。
创建一个模块非常简单——只需将相关函数和类放入.py文件中。例如,创建一个math_tools.py:
python复制# math_tools.py
def factorial(n):
return 1 if n == 0 else n * factorial(n-1)
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
然后在其他文件中导入:
python复制from math_tools import factorial, Vector
print(factorial(5)) # 120
v = Vector(3, 4)
当你的代码库越来越大时,就该使用包了。一个典型的包结构如下:
code复制my_package/
│── __init__.py
│── utils.py
│── core/
│ ├── __init__.py
│ ├── calculations.py
│ └── models.py
└── tests/
├── __init__.py
└── test_utils.py
在__init__.py中,你可以控制包的导入行为。我常用的模式是:
python复制# my_package/__init__.py
from .utils import helper_function # 使helper_function可以直接从包导入
__all__ = ['helper_function'] # 控制from my_package import *的行为
Python的导入系统非常灵活,但也有一些陷阱需要注意:
- 相对导入(from . import module)只能在包内使用
- 循环导入会导致各种奇怪问题
- sys.path决定了Python查找模块的位置
实用技巧:在大型项目中,我习惯在根目录下创建一个requirements.txt文件,用pip install -r requirements.txt一键安装所有依赖。
4. 高级主题:自定义异常与文件处理模式
在开发供他人使用的库时,定义自己的异常类型能让错误更清晰。这是我的做法:
python复制class DataValidationError(Exception):
"""当数据不符合预期时抛出"""
def __init__(self, message, invalid_data):
super().__init__(message)
self.invalid_data = invalid_data
def validate_user(user):
if not user.get('name'):
raise DataValidationError("用户名不能为空", user)
对于文件操作,二进制模式在处理非文本文件时必不可少。这是我处理图片文件的例子:
python复制def copy_image(source_path, target_path):
with open(source_path, 'rb') as src, open(target_path, 'wb') as dst:
while True:
chunk = src.read(4096) # 分块读取,节省内存
if not chunk:
break
dst.write(chunk)
在模块设计方面,我推荐使用"鸭子类型"——关注对象的行为而非类型。这使得模块间的耦合度更低:
python复制# 接受任何实现了read()方法的对象
def process_file(file_obj):
data = file_obj.read()
# 处理数据...
最后,关于Python的导入系统,有一个鲜为人知的技巧:你可以通过importlib动态导入模块:
python复制import importlib
module_name = "json" # 可以从配置文件中读取
json = importlib.import_module(module_name)
data = json.loads('{"key": "value"}')
这些年来,我总结的Python工程最佳实践是:
- 每个模块应该只负责一件事
- 异常处理要具体且有针对性
- 文件操作一定要考虑资源清理
- 包结构应该反映功能划分
- 文档字符串(docstring)不是可选项
记住,好的代码不是能运行的代码,而是六个月后你(或其他人)还能理解的代码。模块化、适当的异常处理和规范的文件操作是实现这一目标的关键。
