Python自动化脚本踩坑记录:过来人的经验之谈
·
写了几年代码,踩过无数坑。今天把自动化脚本开发中常见的"坑"整理出来,都是血的教训。这些坑新人很容易踩,提前知道能少走很多弯路。
一、文件操作坑
坑1:没关文件导致锁文件
# ❌ 危险:文件未关闭,Windows下可能导致文件被锁
def write_log(message):
f = open('log.txt', 'a', encoding='utf-8')
f.write(message)
# 没关!如果抛异常就永远不会关
# ✓ 正确:使用with自动关闭
def write_log(message):
with open('log.txt', 'a', encoding='utf-8') as f:
f.write(message)
坑2:读写前不检查文件存在
# ❌ 危险:文件不存在会抛异常
with open('config.json') as f:
config = json.load(f)
# ✓ 正确:先检查
def read_json_safe(path):
if not os.path.exists(path):
return {}
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
# ✓ 更好:使用异常处理
def read_json_safe(path):
try:
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return {}
坑3:写入不创建父目录
# ❌ 危险:目录不存在会报错
with open('data/output/result.txt', 'w') as f:
f.write('result')
# ✓ 正确:确保目录存在
os.makedirs('data/output', exist_ok=True)
with open('data/output/result.txt', 'w') as f:
f.write('result')
# ✓ 更好:用pathlib
from pathlib import Path
output_path = Path('data/output/result.txt')
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text('result', encoding='utf-8')
二、日期时间坑
坑4:时区问题
# ❌ 危险:忽略时区,服务器在不同时区会出问题
from datetime import datetime
now = datetime.now() # 本地时间,没有时区信息
# ✓ 正确:使用UTC或明确时区
from datetime import datetime, timezone
utc_now = datetime.now(timezone.utc) # UTC时间
print(utc_now) # 2024-01-20 12:00:00+00:00
# 保存到文件用ISO格式
with open('log.txt', 'a') as f:
f.write(utc_now.isoformat() + '\n')
# ✓ 处理带时区的时间
from datetime import timedelta
cst = timezone(timedelta(hours=8))
cst_now = datetime.now(cst)
坑5:日期格式不一致
# ❌ 危险:不同环境日期格式不同
timestamp = datetime.now().strftime('%Y/%m/%d') # Windows可能是中文
# ✓ 正确:使用ISO格式或明确格式
timestamp = datetime.now().isoformat() # 2024-01-20T12:00:00.000000
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 明确分隔符
# ✓ 跨平台安全:月份和日期补零
timestamp = datetime.now().strftime('%Y-%m-%d') # 总是YYYY-MM-DD
三、网络请求坑
坑6:不处理超时
# ❌ 危险:请求永不返回(网络问题)
response = requests.get(url)
# ✓ 正确:设置超时
response = requests.get(url, timeout=30)
# ✓ 更安全:区分连接超时和读取超时
from requests.exceptions import ReadTimeout, ConnectTimeout
try:
response = requests.get(url, timeout=(5, 30)) # (连接超时, 读取超时)
except (ConnectTimeout, ReadTimeout) as e:
print(f"请求超时: {e}")
坑7:SSL证书验证失败
# ❌ 危险:忽略SSL验证(生产环境不安全)
response = requests.get(url, verify=False)
# ✓ 正确:更新证书或使用正确的CA
# 方案1:更新certifi的证书
import certifi
response = requests.get(url, verify=certifi.where())
# 方案2:如果是自签名证书,单独处理
if url.startswith('https://internal.corp'):
response = requests.get(url, verify='/path/to/corp-ca.crt')
else:
response = requests.get(url)
坑8:不处理编码
# ❌ 危险:默认编码可能不对
content = response.text
# ✓ 正确:手动指定编码
content = response.content.decode('utf-8')
# ✓ 或使用apparent_encoding
content = response.text
if not content:
content = response.content.decode(response.apparent_encoding)
四、并发坑
坑9:线程不安全
import threading
# ❌ 危险:共享变量被多线程修改
counter = 0
def increment():
global counter
for _ in range(100000):
counter += 1 # 非原子操作,结果不可预测
threads = [threading.Thread(target=increment) for _ in range(10)]
for t in threads: t.start()
for t in threads: t.join()
print(counter) # 结果不确定,通常小于1000000
# ✓ 正确:使用锁
from threading import Lock
counter = 0
lock = Lock()
def increment():
global counter
for _ in range(100000):
with lock:
counter += 1
# ✓ 或使用线程安全的数据结构
from collections import Counter
counter = Counter()
def increment():
for _ in range(100000):
counter['count'] += 1
坑10:进程池内存泄漏
# ❌ 危险:大对象传给子进程导致内存翻倍
def process_large_data(data):
return heavy_processing(data)
large_data = load_huge_file() # 占用1GB内存
pool = multiprocessing.Pool(4)
results = pool.map(process_large_data, [large_data] * 10) # 每个进程复制1GB
# ✓ 正确:使用共享内存或分片处理
def process_chunk(chunk):
return heavy_processing(chunk)
large_data = load_huge_file()
chunks = split_into_chunks(large_data, 10) # 分成10份
results = pool.map(process_chunk, chunks)
五、字符串坑
坑11:字符串拼接性能
import time
# ❌ 慢:循环中频繁拼接字符串
result = ""
for i in range(10000):
result += str(i) + ","
# ✓ 快:用join
parts = [str(i) for i in range(10000)]
result = ",".join(parts)
# ✓ 更快:io.StringIO
from io import StringIO
buf = StringIO()
for i in range(10000):
buf.write(str(i))
buf.write(",")
result = buf.getvalue()
坑12:JSON序列化大数据
import json
# ❌ 慢:大数据量JSON序列化很慢
data = {'items': list_of_million_items}
json_str = json.dumps(data)
# ✓ 快:用ujson或orjson
import orjson
json_bytes = orjson.dumps(data) # 返回bytes
json_str = json_bytes.decode()
# ✓ 更快:分块写入
def write_large_json(data, path, chunk_size=10000):
with open(path, 'wb') as f:
f.write(b'{"items":[')
items = data['items']
for i in range(0, len(items), chunk_size):
chunk = items[i:i+chunk_size]
chunk_json = orjson.dumps(chunk).decode()[1:-1] # 去掉[]
if i > 0:
f.write(',')
f.write(chunk_json)
f.write(b']}')
六、路径坑
坑13:相对路径基准不对
# ❌ 危险:相对路径基于当前工作目录,不是脚本目录
with open('config.json') as f: # 基于当前运行目录
config = json.load(f)
# ✓ 正确:基于脚本所在目录
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent.resolve()
config_path = SCRIPT_DIR / 'config.json'
with open(config_path) as f:
config = json.load(f)
坑14:Windows路径斜杠问题
# ❌ 危险:硬编码斜杠
path = "data\\output\\file.txt" # 只在Windows有效
# ✓ 正确:用pathlib或os.path
from pathlib import Path
path = Path("data") / "output" / "file.txt" # 自动处理跨平台
# 或
import os
path = os.path.join("data", "output", "file.txt")
七、环境坑
坑15:pip安装不指定版本
# ❌ 危险:依赖更新后代码可能不兼容
# requirements.txt
requests
pandas
# ✓ 正确:锁定版本
# requirements.txt
requests==2.28.2
pandas==1.5.3
openpyxl==3.1.2
# 定期更新并测试
# pip list --outdated # 查看可更新的包
坑16:不处理环境变量不存在
import os
# ❌ 危险:环境变量不存在返回None
api_key = os.environ['API_KEY'] # KeyError!
# ✓ 正确:提供默认值
api_key = os.environ.get('API_KEY', 'default_key')
# ✓ 或明确检查
api_key = os.environ.get('API_KEY')
if not api_key:
raise ValueError("API_KEY environment variable is required")
总结
| 类型 | 常见坑 | 解决方案 |
|---|---|---|
| 文件 | 不关文件、不检查存在、不创建目录 | with语句、检查存在、makedirs |
| 时间 | 时区问题、格式不一致 | 使用UTC、ISO格式 |
| 网络 | 无超时、不处理证书、不处理编码 | timeout参数、正确编码 |
| 并发 | 线程不安全、内存泄漏 | 使用锁、分片处理 |
| 字符串 | 循环拼接、JSON性能 | join、orjson |
| 路径 | 相对路径基准、斜杠问题 | pathlib、绝对路径 |
| 环境 | 不锁版本、不处理环境变量 | 锁定版本、get带默认值 |
记住这些坑,代码写的时候多留个心眼,能避免大部分问题。遇到问题不要慌,冷静分析,问题总能解决。
更多推荐
所有评论(0)