以下是一份结合PEP8 规范、最佳实践及常见注意事项的 Python 编码规范整理,适用于团队协作与个人项目:
代码布局与格式
缩进
- 规则:使用4 个空格(禁止使用 Tab 键)。
- 多行缩进:垂直对齐或悬挂缩进(后续行多缩进一级)。
# 正确:与括号对齐 result = some_function(arg1, arg2, arg3, arg4) # 正确:悬挂缩进(多一层缩进) result = some_function( arg1, arg2, arg3, arg4 )
在 Python 编码规范中,垂直对齐和悬挂缩进是两种处理代码换行的缩进方式,目的是让多行代码更清晰易读。
垂直对齐(Vertical Alignment)
- 定义:当一行代码过长需要换行时,续行的代码与包裹元素的起始位置对齐(例如括号、方括号、花括号等)。这种对齐方式通过视觉上的整齐排列,强调代码的结构层级。
- 适用场景:函数调用、列表/字典/元组定义、条件判断等需要多行表达的场景。
- 优点:代码结构直观,容易看出参数或元素的层级关系。
- 缺点:当包裹元素(如括号)位置较深时,可能导致续行代码缩进过多,占用行宽。
悬挂缩进(Hanging Indent)
- 定义:当一行代码过长需要换行时,续行的代码比父级代码多一层缩进(通常为 4 个空格)。首行不放置任何元素,续行代码从下一行开始。
- 适用场景:函数定义/调用、字典/列表等需要换行且父级代码较长的情况。
- 优点:避免首行过长,代码层次分明,适合复杂结构。更符合 PEP8 对行长度的限制(79 字符)。
- 缺点:首行可能显得空荡,需要适应这种风格。
PEP8 的推荐
- -优先使用悬挂缩进:PEP8 推荐在函数定义、调用等场景中使用悬挂缩进,因为它更符合行长度限制,且易于工具自动格式化。
- -垂直对齐的例外:当代码逻辑需要强调对齐关系时(如字典键值对),垂直对齐更直观。
行长度
- 规则:每行不超过79 字符(PEP8 推荐),可放宽至 88-100 字符(现代 IDE 支持)。
- 换行策略:
- 在运算符前换行。
- 使用反斜杠\ 或括号包裹换行。
# 正确:运算符后换行 total = (value1 + value2 + value3 - value4)
空格与运算符
- 运算符两侧加空格,但括号内不需要:
# 正确 x = 1 + 2 if a > 5 and b < 10: # 错误 x=1+2
- 逗号、分号后加空格,前面不加:
# 正确 arr = [1, 2, 3] # 错误 arr = [1 ,2 ,3]
空行
- 函数/类之间:用2 个空行分隔。
- 函数内部:用1 个空行分隔逻辑块。
def func1(): pass def func2(): pass
导入(Imports)
- 模块导入顺序:标准库 → 第三方库 → 本地模块,每组用空行分隔。
- 禁止通配符:禁止使用from module import *。
import os import sys from django.http import HttpResponse from myapp.utils import helper
命名规范
基本规则
- 变量/函数:小写 + 下划线(snake_case),如calculate_total。
- 类名:首字母大写(CamelCase),如ClassName。
- 常量:全大写 + 下划线,如MAX_LENGTH。
- 私有成员:前缀单下划线_private_var,双下划线 __mangled_name(名称修饰)。
避免的命名
- 单字母变量(除非在循环或 lambda 中)。
- 保留关键字(如list、str)或易混淆名称(如 l, O)。
代码风格
表达式与语句
避免无关空格:
# 错误 func (arg1, arg2) # 正确 func(arg1, arg2)
- 链式比较:允许if 0 < x < 10。
- 条件简写:避免冗余代码。
# 冗余 if x == True: # 正确 if x:
函数与方法
- 函数长度不超过 50行,功能单一(一个函数只做一件事)。
- 参数默认值:避免可变对象(如列表、字典)。
# 错误 def func(a=[]): # 正确 def func(a=None): a = a or []
在 Python 中,函数参数的默认值如果是可变对象(如列表、字典等),可能会导致意外的副作用。这是因为默认值在函数定义时就被计算并存储,后续调用会共享同一个可变对象。以下是详细解释和示例:
问题的根源:Python 的默认参数值是在函数定义时就被计算并存储的,而不是每次调用时重新创建。如果默认值是可变对象(如 `[]` 或 `{}`),所有未显式传递该参数的调用都会共享同一个对象。
示例代码:
def append_to_list(value, lst=[]): lst.append(value) return lst print(append_to_list(1)) # 输出 [1] print(append_to_list(2)) # 输出 [1, 2] ❌ 预期是 [2]
为什么会出现问题?
- 第一次调用 `append_to_list(1)` 时,`lst` 使用默认的空列表 `[]`,结果变为 `[1]`。
- 第二次调用 `append_to_list(2)` 时,`lst` 仍然指向第一次调用时修改后的列表 `[1]`,所以结果变为 `[1, 2]`。
如何避免?
正确的做法是将默认值设为不可变对象(如 `None`),然后在函数内部判断并初始化可变对象:
修正代码:
def append_to_list(value, lst=None): if lst is None: lst = [] # 每次调用时创建新列表 lst.append(value) return lst print(append_to_list(1)) # 输出 [1] print(append_to_list(2)) # 输出 [2] ✅
类型注解:推荐使用类型提示(Python 3.5+)。
def greet(name: str) -> str: return f"Hello, {name}"
类设计
- 类的职责明确,避免“上帝类”。
- 使用 `@property` 装饰器管理属性访问。
- 优先使用组合而非继承。
单一职责原则(SRP)
- 定义:一个类应该只有一个引起它变化的原因(即一个类只做一件事)。
- 核心思想:将功能拆分为独立的模块,每个模块负责一个明确的职责。
示例对比:
# 错误设计:一个类同时处理用户认证、数据存储和日志记录。 # 上帝类的典型表现 class UserManager: def login(self, username, password): ... # 认证 def save_to_db(self, user_data): ... # 数据存储 def write_log(self, message): ... # 日志记录 # 正确设计:职责拆分到不同类。 class UserAuthenticator: def login(self, username, password): ... # 只负责认证 class UserRepository: def save(self, user_data): ... # 只负责存储 class Logger: def log(self, message): ... # 只负责日志
如何判断职责是否明确?
- 类的名称是否清晰描述其功能?(如 `UserAuthenticator` 而非 `UserManager`)
- 能否用一句话描述类的职责?(例如:“这个类负责验证用户身份”)
- 修改某个功能时,是否需要改动多个类?
什么是“上帝类”(God Class)?
定义
- 上帝类:一个类承担了太多职责,包含大量方法和属性,甚至直接操作其他类的数据。
- 典型特征:
- 代码量庞大(数千行)。
- 直接依赖多个外部模块或数据。
- 包含大量不相关的方法(如同时处理网络请求、数据库操作、业务逻辑)。
上帝类的危害:
- 代码耦合度高:牵一发而动全身,修改一处可能引发多处错误。
- 难以测试:依赖复杂,单元测试难以覆盖所有场景。
- 可读性差:新成员需要花费大量时间理解类的功能。
如何避免上帝类?
按功能拆分:将大类分解为多个小类,每个类负责单一功能。
class OrderCreator: ... # 创建订单 class InventoryUpdater: ... # 更新库存 class PaymentProcessor: ... # 处理支付 class NotificationSender: ... # 发送通知
使用组合而非继承:
- 继承:容易导致父类膨胀(如 `Animal` 类包含所有动物的方法)。
- 组合:通过注入依赖对象,灵活扩展功能。
# 错误:继承导致冗余 class Bird(Animal): def fly(self): ... # 鸟类会飞 def swim(self): ... # 但有些鸟不会游泳 # 正确:通过组合分离能力 class Bird: def __init__(self, fly_behavior, swim_behavior): self.fly = fly_behavior self.swim = swim_behavior
依赖注入(Dependency Injection):将依赖的外部服务(如数据库、网络)通过构造函数或方法参数传入,而非在类内部直接创建。
# 错误:类内部直接依赖数据库 class UserRepository: def __init__(self): self.db = MySQLClient() # 紧耦合 # 正确:通过依赖注入解耦 class UserRepository: def __init__(self, db_client): # 可传入任意数据库客户端 self.db = db_client
异常处理
基本原则
- 避免捕获所有异常(`except:` ❌),明确指定异常类型。
- 异常处理用于预期可能出错的代码,而非控制流程。
- 自定义异常应继承 `Exception` 基类。
明确异常类型:避免裸 except:。
# 错误 try: ... except: pass # 正确 try: ... except ValueError as e: logger.error(e)
异常消息:提供清晰错误信息。
raise ValueError("Invalid value: expected int, got str")
注释与文档
代码注释
- 行内注释:在代码后空两格写 `#`,注释内容简明。
- 块注释:用于复杂逻辑解释,每行以 `# `开头。
- 避免无意义的注释(如 `# 赋值给x`)。
文档字符串(Docstring)
- 模块/函数/类:使用三重双引号”””…”””,遵循 Google 或 NumPy 风格。
def calculate_sum(a: int, b: int) -> int: """Add two numbers. Args: a (int): First number. b (int): Second number. Returns: int: Sum of a and b. """ return a + b
Google 风格文档字符串
特点:
- 简洁直观:语法简单,强调自然语言描述。
- 结构化标签:用 `Args`、`Returns`、`Raises` 等标签分块。
- 类型标注可选:参数类型可写在描述中,或与参数名结合。
- 广泛适用:适合中小型项目或快速编写文档。
格式示例:
def add(a: int, b: int) -> int: """计算两个整数的和。 Args: a (int): 第一个加数。 b (int): 第二个加数。 Returns: int: 两个数的和。 Raises: ValueError: 如果输入非整数。 Examples: >>> add(2, 3) 5 """ if not isinstance(a, int) or not isinstance(b, int): raise ValueError("输入必须是整数") return a + b
核心标签:
- `Args`: 参数说明(可标注类型)。
- `Returns`: 返回值说明。
- `Raises`: 可能抛出的异常。
- `Examples`: 使用示例(可包含 Doctest)。
NumPy 风格文档字符串
特点:
- 详细规范:严格分块,适合复杂函数或库。
- 类型强制标注:参数和返回值必须明确类型。
- 多级分隔线:用 `———-` 分隔不同区块。
- 科学计算偏好:常见于数据科学和数值计算项目(如 NumPy、Pandas)。
格式示例
def divide(dividend: float, divisor: float) -> float: """计算两个数的除法。 Parameters ---------- dividend : float 被除数。 divisor : float 除数,必须非零。 Returns ------- float 除法结果。 Raises ------ ZeroDivisionError 如果除数为零。 Examples -------- >>> divide(10.0, 2.0) 5.0 """ if divisor == 0: raise ZeroDivisionError("除数不能为零") return dividend / divisor
核心标签:
- `Parameters`: 参数说明(强制类型标注)。
- `Returns`: 返回值说明。
- `Raises`: 异常说明。
- `See Also`: 相关函数或类。
- `Notes`: 额外注意事项。
- `Examples`: 使用示例。
TODO 注释
- 标记待完成事项。
# TODO: Implement error handling here.
最佳实践与注意事项
代码可读性
- 避免魔法代码:明确优于隐晦。
# 错误:魔法数字 if status == 2: # 正确:使用常量 STATUS_COMPLETED = 2 if status == STATUS_COMPLETED:
- 单一职责原则:一个函数只做一件事。
性能优化
避免低效操作
- 字符串拼接使用 `join` 而非 `+`。
- 优先用列表推导式代替 `for` 循环生成列表。
- 减少全局变量访问(局部变量更快)。
高效数据结构
- 频繁查找用 `字典` 或 `集合`(O(1) 复杂度)。
- 大数据处理优先考虑生成器(`yield`)。
兼容性与版本
- Python 2/3 兼容:明确代码运行环境(Python 2 已停止维护)。
- 环境依赖:使用txt 或 pyproject.toml 管理包。
工具推荐
- 代码检查:
- flake8:综合检查(PEP8 + 代码复杂度)。
- pylint:更严格的静态分析。
- 自动格式化:
- black:无配置强制统一格式。
- isort:自动排序导入语句。
- 类型检查:
- mypy:静态类型检查。