Python 编码规范 旨在提供一套指导原则和最佳实践,以确保 Python 代码的一致性、可读性、可维护性、可协作性和**“Pythonic”**(符合 Python 语言哲学)风格。Python 社区的核心编码规范是 PEP 8 (Python Enhancement Proposal 8),它定义了 Python 代码的风格指南。遵循 PEP 8 不仅能让你的代码更容易被其他 Python 开发者理解,也能提高代码本身的质量和减少潜在错误。

核心思想:一致性至关重要。代码是写给人看的,不是机器。清晰、简洁、可读的代码能够极大地提高开发效率和项目成功率。


一、Python 编码哲学与 PEP 8

Python 语言的设计哲学(可在 import this 中查看“The Zen of Python”)强调简洁、明确和可读性。PEP 8 是将这些哲学转化为具体编码实践的基石。

PEP 8 是什么?
PEP 8 是 Python 官方的风格指南,由 Guido van Rossum (Python 创始人)、Barry Warsaw 和 Nick Coghlan 共同撰写。它提供了关于如何编写一致、易于阅读的 Python 代码的建议,涵盖了从缩进、命名到导入方式等方方面面。

为什么它如此重要?

  • 统一性:所有遵循 PEP 8 的 Python 代码看起来都相似,降低了阅读他人代码的认知负担。
  • 可读性:PEP 8 的许多规则都是为了最大化代码的可读性而设计的。
  • 可维护性:一致且清晰的代码更容易维护和调试。
  • 专业性:遵循社区约定是成为一名合格的 Python 开发者的一部分。

二、自动化工具

在现代 Python 开发中,不再需要手动记忆和检查所有 PEP 8 规则。自动化工具可以为你完成大部分工作。

2.1 Black (代码格式化工具)

  • 独断专行 (Opinionated)Black 是一款不妥协的 Python 代码格式化工具。它没有太多配置选项,旨在“格式化一次,然后就忘了它”。
  • 优点:解决了团队内部关于代码格式的争论,所有代码都将拥有统一的风格。
  • 安装pip install black
  • 使用
    1
    2
    black .          # 格式化当前目录及所有子目录下的 Python 文件
    black your_file.py # 格式化单个文件

2.2 Flake8 (代码风格检查工具)

  • 静态分析Flake8 是一个用于检查 Python 代码是否符合 PEP 8 规范以及发现一些常见编程错误的工具。它集成了 pycodestyle (PEP 8 检查器)、pyflakes (错误检查器) 和 McCabe 复杂度检查器。
  • 优点:在代码提交前发现风格和潜在的运行时问题。
  • 安装pip install flake8
  • 使用
    1
    2
    flake8 .          # 检查当前目录及所有子目录下的 Python 文件
    flake8 your_file.py # 检查单个文件

2.3 isort (导入排序工具)

  • 导入整理isort 能够自动对 Python 文件中的 import 语句进行分组和排序,使其符合 PEP 8 规范。
  • 优点:保持导入语句的整洁和一致性。
  • 安装pip install isort
  • 使用
    1
    2
    isort .          # 排序当前目录及所有子目录下的 Python 文件的导入语句
    isort your_file.py # 排序单个文件的导入语句

三、格式化 (PEP 8 Essentials)

这些是 PEP 8 中最基础且最常自动化的格式规则。

3.1 缩进 (Indentation)

  • 4 个空格:每个缩进级别使用 4 个空格。绝不允许使用 Tab 字符

  • 连续行缩进:对于换行的语句,使用额外的缩进来区分它们与正常的代码块。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Good
    def long_function_name(
    param_one, param_two,
    param_three, param_four):
    print(param_one)

    # Bad (使用 Tab 字符)
    def some_func():
    \tprint("Hello") # 这里是 Tab 字符,不可见

3.2 行长度 (Line Length)

  • 最大 79 字符:所有行都应限制在 79 个字符以内。对于文档字符串和注释,行长度应限制在 72 个字符以内。

  • 为什么?:提高代码的可读性,尤其是在进行代码审查或使用分屏显示时。

  • 换行

    • 使用括号 ()、方括号 []、花括号 {} 进行隐式行连接。
    • 使用反斜杠 \ 进行显式行连接 (通常不推荐,能用隐式连接就用)。
    1
    2
    3
    4
    5
    6
    7
    # Good (隐式行连接)
    income_tax_rate = (long_variable_name_one +
    long_variable_name_two -
    long_variable_name_three)

    # Bad (超出行长度限制)
    very_very_very_very_very_very_very_very_very_very_long_variable = 100

3.3 空行 (Blank Lines)

  • 顶级定义之间:模块级函数和类定义之间用两个空行分隔。

  • 方法定义之间:类中的方法定义之间用一个空行分隔。

  • 逻辑块之间:在函数或方法内部,可以使用空行来分隔逻辑相关的代码块,提高可读性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # Good
    class MyClass:
    def __init__(self, value):
    self.value = value

    def method_one(self):
    # ...
    pass

    def method_two(self):
    # ...
    pass


    def top_level_function():
    # ...
    pass

3.4 空格 (Whitespace)

  • 运算符两侧:在二元运算符(=+-*/ 等)的两侧各放置一个空格。

  • 逗号、分号后:逗号、分号和冒号后面需要空格,但前面不需要。

  • 参数列表、索引前后:避免在括号、方括号和花括号紧邻的内部使用空格。

  • 避免尾随空格:行尾不应有空格。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Good
    a = b + 1
    my_list = [1, 2, 3]
    print(func(arg1, arg2))

    # Bad
    a=b+1
    my_list = [ 1,2,3 ]
    print ( func ( arg1, arg2 ) )

3.5 导入 (Imports)

  • 每行一个导入import 语句通常每行只导入一个模块。

  • 分组和排序:导入语句应该按以下顺序分组,每组之间用空行分隔:

    1. 标准库导入 (Python 内置模块)。
    2. 第三方库导入。
    3. 本地应用程序/库特定的导入。
  • 绝对导入:通常推荐使用绝对导入而不是相对导入,因为它更清晰。

  • 避免通配符导入from module import * 会污染命名空间,降低代码可读性,应避免。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # Good (isort 会自动整理成这样)
    import os
    import sys

    from datetime import datetime, timedelta

    import requests
    from loguru import logger

    from my_project.utils import helper
    from my_project.models import User

四、命名规范 (Naming Conventions)

良好的命名是代码自解释的关键。

4.1 变量、函数、方法

  • snake_case (蛇形命名法):所有字母小写,单词之间用下划线 _ 分隔。
    1
    2
    3
    4
    # Good
    user_name = "Alice"
    def calculate_total_amount():
    pass
  • 私有/受保护成员:以一个下划线开头 (_single_leading_underscore)。这是一种约定,表示这些成员是内部使用的,不应该被外部直接访问。Python 解释器不会阻止你访问它们。
    1
    2
    3
    class MyClass:
    def __init__(self):
    self._internal_data = [] # 内部使用
  • 名称长度与作用域:变量名应与其作用域成正比。局部变量可以短,全局变量和导出变量应更具描述性。

4.2 类名

  • CapWords (大驼峰命名法 / PascalCase):每个单词的首字母大写,不使用下划线。

    1
    2
    3
    4
    5
    6
    # Good
    class MyClass:
    pass

    class HTTPRequest:
    pass

4.3 常量

  • ALL_CAPS (全大写,下划线分隔):所有字母大写,单词之间用下划线 _ 分隔。用于表示值在程序生命周期内不变的量。
    1
    2
    3
    # Good
    MAX_CONNECTIONS = 100
    DEFAULT_TIMEOUT_SECONDS = 30

4.4 模块和包

  • 模块snake_case (小写,单词之间用下划线分隔)。文件名为 my_module.py
  • lowercase_without_underscores (全小写,不使用下划线)。目录名为 my_package/

4.5 特殊命名

  • __double_leading_and_trailing_underscore__ (双下划线前后缀):用于 Python 特殊方法(也称为“魔法方法”或“dunder methods”),如 __init__, __str__, __add__。这些方法具有特殊的含义,不应自定义创建。
  • __double_leading_underscore (双下划线前缀):用于类中,触发名称修饰(name mangling)。这意味着该属性在类外部访问时会被 Python 自动修改名称,以避免子类属性冲突。但通常不推荐过度使用,_single_leading_underscore 更常用。
    1
    2
    3
    class MyClass:
    def __init__(self):
    self.__private_data = 10 # 实际会被访问为 _MyClass__private_data
  • _ (单下划线)
    • 在交互式解释器中,_ 代表上一个表达式的结果。
    • 在变量赋值时,用作不关心的占位符。
    • 有时也用作临时或“未使用的”变量名。
    1
    2
    3
    for _ in range(5):
    # 循环 5 次,不关心循环变量
    pass

五、注释 (Comments)

注释的目的是解释代码的**“为什么”,而不是“是什么”**。清晰的代码比详细的注释更重要。

5.1 块注释 (Block Comments)

  • 解释复杂性:用于解释复杂或非显而易见的逻辑、设计决策或代码背后的意图。

  • 与代码保持相同缩进:块注释应该与它们所描述的代码块保持相同的缩进级别。

  • # 开头:每行以 # 开头,后面跟一个空格。

  • 段落分离:段落之间用一个空行分隔,空行也以 # 开头。

    1
    2
    3
    4
    5
    6
    7
    8
    # This block of code calculates the Fibonacci sequence
    # up to a given number n using an iterative approach.
    # It avoids recursion to prevent potential stack overflow
    # for large values of n.
    a, b = 0, 1
    for _ in range(n):
    print(a)
    a, b = b, a + b

5.2 行内注释 (Inline Comments)

  • 谨慎使用:只在代码行本身无法解释清楚时使用。

  • 与代码分隔:行内注释应该至少与代码分隔两个空格。

  • 避免冗余:不要为显而易见的代码添加注释。

    1
    2
    x = 10  # Set x to 10
    x = 10 # 这是一个计数器,用于跟踪尝试次数

    第二个注释是坏的示例x 的用途应该通过更好的变量名(如 attempt_count)来表示。

5.3 文档字符串 (Docstrings) (PEP 257)

  • 必需:所有模块、类、函数、方法都应该有文档字符串。

  • 三引号:使用三对双引号 """Docstring content"""

  • 单行 Docstring:如果文档字符串适合单行,结束三引号应与开始三引号在同一行。

    1
    2
    3
    def add(a, b):
    """Return the sum of two numbers."""
    return a + b
  • 多行 Docstring

    • 第一行是简短的摘要。
    • 摘要后空一行。
    • 接下来是更详细的描述。
    • 最后一行是独立的结束三引号。
    • 常用格式:reStructuredText, Google Style, NumPy Style 是流行的多行文档字符串格式,通常配合 Sphinx 等工具生成文档。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    def calculate_area(radius: float) -> float:
    """Calculate the area of a circle given its radius.

    This function computes the area using the formula A = pi * r^2.

    Args:
    radius: The radius of the circle (a positive float).

    Returns:
    The calculated area of the circle.

    Raises:
    ValueError: If the radius is negative.
    """
    if radius < 0:
    raise ValueError("Radius cannot be negative.")
    return 3.14159 * radius * radius

5.4 TODO 注释

  • 标记待办事项:使用 TODO:FIXME: 前缀标记未来需要完成、改进或修复的任务。

  • 说明:通常包含任务的简要描述和(可选的)负责人。

    1
    2
    # TODO: Add proper error handling for file operations
    # FIXME(john): This temporary solution needs to be refactored before release

六、编程建议 (Programming Recommendations)

6.1 错误处理

  • 使用异常:Python 推荐使用异常 (try...except) 来处理可预见的错误情况,而不是返回错误码。

  • 不要捕获过于宽泛的异常:避免只使用 except Exception: 或裸 except:,这会捕获所有错误,包括系统错误,导致难以调试。应捕获特定的异常类型。

  • finally 进行资源清理:使用 finally 块来确保资源(如文件句柄、网络连接)无论是否发生异常都能被正确关闭或释放。

  • with 语句:对于支持上下文管理器协议的对象(如文件、锁),优先使用 with 语句,它能自动处理资源的获取和释放。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # Good
    try:
    with open("data.txt", "r") as f:
    content = f.read()
    except FileNotFoundError:
    print("Error: File not found.")
    except IOError as e:
    print(f"Error reading file: {e}")
    else: # 如果 try 块没有抛出异常,则执行 else 块
    print("File read successfully.")
    finally:
    print("Finished file operation.")

    # Bad (宽泛的异常捕获)
    try:
    # some risky operation
    pass
    except: # 不应捕获所有异常
    print("An error occurred.")

6.2 字符串

  • F-strings 优先 (Python 3.6+):F-strings (格式化字符串字面量) 是最推荐的字符串格式化方式,它简洁、易读且高效。

  • str.format():在 Python 3.5 及之前版本或需要更复杂格式化逻辑时使用。

  • 避免字符串拼接用 + 在循环中:在循环中频繁使用 + 拼接字符串会导致性能问题,因为字符串是不可变的,每次拼接都会创建新对象。应使用 list.join() 方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # Good (f-string)
    name = "Alice"
    age = 30
    message = f"Hello, {name}! You are {age} years old."

    # Good (str.format)
    message = "Hello, {}! You are {} years old.".format(name, age)

    # Good (list.join for multiple concatenations)
    words = ["Hello", "world", "Python"]
    sentence = " ".join(words) # "Hello world Python"

    # Bad (低效的循环拼接)
    long_string = ""
    for char_code in range(97, 123):
    long_string += chr(char_code)

6.3 列表推导式 (List Comprehensions)

  • 优先使用:当需要从现有列表或其他可迭代对象创建新列表时,优先使用列表推导式,它比传统的 for 循环更简洁、更 Pythonic。

  • 简洁明了:如果逻辑变得过于复杂,可考虑使用普通的 for 循环。

    1
    2
    3
    4
    5
    6
    7
    8
    # Good
    squares = [x * x for x in range(10) if x % 2 == 0] # [0, 4, 16, 36, 64]

    # Bad (冗余的 for 循环)
    squares = []
    for x in range(10):
    if x % 2 == 0:
    squares.append(x * x)

6.4 迭代器和生成器

  • 内存效率:对于处理大数据集或无限序列时,使用迭代器和生成器可以显著节省内存。

  • 生成器表达式:类似于列表推导式,但使用 () 创建生成器对象而不是列表。

    1
    2
    3
    4
    5
    6
    7
    # Good (生成器表达式)
    gen = (x * x for x in range(1000000) if x % 2 == 0)
    # gen 是一个生成器,不会立即计算所有值

    # Bad (列表推导式可能会占用大量内存)
    full_list = [x * x for x in range(1000000) if x % 2 == 0]
    # full_list 会立即在内存中创建所有值

6.5 类型提示 (Type Hinting) (PEP 484, PEP 526)

  • 提高可读性:为函数参数、返回值和变量添加类型提示,使代码意图更明确。

  • 静态分析:与 mypy 等静态类型检查工具配合使用,可以在运行时前发现类型相关的错误。

  • 协作优势:在团队项目中,类型提示有助于其他开发者更快理解代码接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # Good
    from typing import List, Dict, Union

    def greet(name: str) -> str:
    return f"Hello, {name}!"

    def process_data(data: List[Dict[str, Union[str, int]]]) -> Dict[str, int]:
    processed_result: Dict[str, int] = {}
    # ... logic ...
    return processed_result

    class User:
    name: str
    age: int = 0 # 实例变量的类型提示

七、工具推荐

除了上述的 Black, Flake8, isort 外,还有一些工具可以进一步提升代码质量:

  • mypy:静态类型检查器。强制执行类型提示并查找代码中潜在的类型不匹配问题。
    • pip install mypy
    • mypy your_file.py
  • pylint:一个更强大的 linter,除了 PEP 8 检查外,还会检查代码中的错误、不好的实践、代码异味(code smell)和复杂度。
    • pip install pylint
    • pylint your_file.py

八、总结

Python 编码规范并非僵化的教条,而是经过社区长期实践形成的共识,旨在提升代码的整体质量。遵循 PEP 8 和其他最佳实践能够使你的代码更易于阅读、理解、维护和协作。

  • 自动化优先:充分利用 BlackFlake8isort 等工具,让它们处理大部分机械性的风格问题。
  • 命名是艺术:投入时间为变量、函数和类选择清晰、描述性且符合约定的名称。
  • 文档和注释:解释代码的“为什么”,而不是“是什么”。所有公共 API 都应有清晰的文档字符串。
  • Pythonic 风格:熟悉并拥抱 Python 的特性和习惯用法,例如列表推导式、生成器、上下文管理器等。

通过持续学习和实践这些规范,你将能够编写出更高质量、更“Pythonic”的代码,成为一名更优秀的 Python 开发者。