器→工具, 编程语言

Python 编码规范整理版

钱魏Way · · 174 次浏览

以下是一份结合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:静态类型检查。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注