Python编程风格指南
基于Google的Python风格指南的全面的Python编程指导原则。当Claude需要编写Python代码、审查Python代码风格问题、重构Python代码或提供Python编程指导时使用。涵盖语言规则(导入、异常、类型注释)、风格规则(命名约定、格式化、文档字符串)以及最佳实践,以实现清晰、可维护的Python代码。
核心理念
保持一致性。 与你周围的代码风格相匹配。使用这些指导原则作为默认值,但始终优先考虑与现有代码的一致性。
语言规则
导入
仅对包和模块使用import语句,不要针对单个类或函数。
正确:
from doctor.who import jodie
import sound_effects.utils
错误:
from sound_effects.utils import EffectsRegistry # 不要直接导入类
导入格式
- 分组导入:标准库、第三方库、应用程序特定
- 在每个组内按字母顺序排列
- 使用绝对导入(不是相对导入)
- 每行一个导入(
typing或collections.abc中的多个项目除外)
# 标准库
import os
import sys
# 第三方库
import numpy as np
import tensorflow as tf
# 应用程序特定
from myproject.backend import api_utils
异常
适当使用异常。不要使用裸except:子句来抑制错误。
正确:
try:
result = risky_operation()
except ValueError as e:
logging.error(f"Invalid value: {e}")
raise
错误:
try:
result = risky_operation()
except: # 太宽泛,隐藏错误
pass
类型注释
为所有函数签名添加注释。类型注释提高了代码可读性并在早期捕获错误。
一般规则:
- 为所有公共API添加注释
- 使用内置类型(
list、dict、set)而不是typing.List等。(Python 3.9+) - 直接导入typing符号:
from typing import Any, Union - 使用
None而不是type(None)或NoneType
def fetch_data(url: str, timeout: int = 30) -> dict[str, Any]:
"""从URL获取数据。"""
...
def process_items(items: list[str]) -> None:
"""处理项目列表。"""
...
默认参数值
不要在函数定义中使用可变对象作为默认值。
正确:
def foo(a: int, b: list[int] | None = None) -> None:
if b is None:
b = []
错误:
def foo(a: int, b: list[int] = []) -> None: # 可变默认值 - 错误!
b.append(a)
真/假评估
尽可能使用隐式假。空序列、None和0在布尔上下文中为假。
正确:
if not users: # 首选
if not some_dict:
if value:
错误:
if len(users) == 0: # 冗长
if users == []:
if value == True: # 永远不要显式与True/False比较
推导式和生成器
对于简单情况,使用推导式和生成器。保持它们易于阅读。
正确:
result = [x for x in data if x > 0]
squares = (x**2 for x in range(10))
错误:
# 太复杂
result = [
x.strip().lower() for x in data
if x and len(x) > 5 and not x.startswith('#')
for y in x.split(',') if y
] # 使用常规循环代替
Lambda函数
仅对单行代码使用lambda。对于任何复杂情况,请定义适当的函数。
正确:
sorted(data, key=lambda x: x.timestamp)
可接受,但更喜欢命名函数:
def get_timestamp(item):
return item.timestamp
sorted(data, key=get_timestamp)
风格规则
行长度
最大行长度:80个字符。对于导入、URL和无法拆分的长字符串,允许例外。
缩进
每个缩进级别使用4个空格。永远不要使用制表符。
对于悬挂缩进,将包装的元素垂直对齐或使用4个空格的悬挂缩进:
# 与开头分隔符对齐
foo = long_function_name(var_one, var_two,
var_three, var_four)
# 悬挂缩进(4个空格)
foo = long_function_name(
var_one, var_two, var_three,
var_four)
空白行
- 顶级定义之间两个空白行
- 方法定义之间一个空白行
- 在函数内适当使用空白行以显示逻辑部分
命名约定
| 类型 | 约定 | 示例 |
|---|---|---|
| 包/模块 | lower_with_under |
my_module.py |
| 类 | CapWords |
MyClass |
| 函数/方法 | lower_with_under() |
my_function() |
| 常量 | CAPS_WITH_UNDER |
MAX_SIZE |
| 变量 | lower_with_under |
my_var |
| 私有 | _leading_underscore |
_private_var |
避免:
- 除了计数器/迭代器(
i、j、k)之外的单字符名称 - 任何名称中的破折号
__double_leading_and_trailing_underscore__(Python保留)
注释和文档字符串
文档字符串格式
对所有公共模块、函数、类和方法使用Google风格的文档字符串。
函数文档字符串:
def fetch_smalltable_rows(
table_handle: smalltable.Table,
keys: Sequence[bytes | str],
require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
"""从Smalltable获取行。
从由table_handle表示的Table实例中检索与给定键相关的行。
字符串键将被UTF-8编码。
参数:
table_handle: 一个打开的smalltable.Table实例。
keys: 表示要获取的表行的键的字符串序列。字符串键将被UTF-8编码。
require_all_keys: 如果为True,如果任何键缺失则引发ValueError。
返回:
一个将键映射到相应的表行数据的字典。每一行都表示为字符串元组。
引发:
IOError: 访问smalltable时发生错误。
ValueError: 一个键缺失且require_all_keys为True。
"""
...
类文档字符串:
class SampleClass:
"""这里总结类。
更长的类信息...
更长的类信息...
属性:
likes_spam: 一个布尔值,表示我们是否喜欢SPAM。
eggs: 我们下蛋的整数计数。
"""
def __init__(self, likes_spam: bool = False):
"""根据spam偏好初始化实例。
参数:
likes_spam: 定义实例是否表现出这种偏好。
"""
self.likes_spam = likes_spam
self.eggs = 0
块和内联注释
- 使用完整的句子和正确的大写
- 块注释与代码缩进到同一级别
- 内联注释应至少由2个空格分隔
- 谨慎使用内联注释
# 块注释解释以下代码。
# 可以跨越多行。
x = x + 1 # 内联注释(谨慎使用)
字符串
对于格式化,使用f-strings(Python 3.6+)。
正确:
x = f"name: {name}; score: {score}"
可接受:
x = "name: %s; score: %d" % (name, score)
x = "name: {}; score: {}".format(name, score)
错误:
x = "name: " + name + "; score: " + str(score) # 避免+用于格式化
日志记录
使用%格式化进行日志记录,而不是f-strings(允许延迟评估):
logging.info("Request from %s resulted in %d", ip_address, status_code)
文件和资源
始终使用上下文管理器(with语句)进行文件操作:
with open("file.txt") as f:
data = f.read()
语句
通常避免在一行上放置多个语句。
正确:
if foo:
bar()
错误:
if foo: bar() # 避免
主函数
对于可执行脚本,使用:
def main():
...
if __name__ == "__main__":
main()
函数长度
保持函数专注且合理大小。如果一个函数超过大约40行,考虑拆分它,除非它仍然非常易读。
类型注释细节
向前声明
使用字符串引用进行向前引用:
class MyClass:
def method(self) -> "MyClass":
return self
类型别名
为复杂类型创建别名:
from typing import TypeAlias
ConnectionOptions: TypeAlias = dict[str, str]
Address: TypeAlias = tuple[str, int]
Server: TypeAlias = tuple[Address, ConnectionOptions]
TypeVars
为TypeVars使用描述性名称:
from typing import TypeVar
_T = TypeVar("_T") # 好:私有的,无限制的
AddableType = TypeVar("AddableType", int, float, str) # 好:描述性的
泛型
始终为泛型类型指定类型参数:
正确:
def get_names(employee_ids: list[int]) -> dict[int, str]:
...
错误:
def get_names(employee_ids: list) -> dict: # 缺少类型参数
...
导入类型
直接导入typing符号:
from collections.abc import Mapping, Sequence
from typing import Any, Union
# 对于容器使用内置类型(Python 3.9+)
def foo(items: list[str]) -> dict[str, int]:
...
常见模式
属性
对于简单的属性访问,使用属性:
class Square:
def __init__(self, side: float):
self._side = side
@property
def area(self) -> float:
return self._side ** 2
条件表达式
对于简单条件,使用三元运算符:
x = "yes" if condition else "no"
上下文管理器
在适当的时候创建自定义上下文管理器:
from contextlib import contextmanager
@contextmanager
def managed_resource(*args, **kwargs):
resource = acquire_resource(*args, **kwargs)
try:
yield resource
finally:
release_resource(resource)
代码检查
对所有Python代码运行pylint。仅在必要时抑制警告,并提供清晰的解释:
dict = 'something' # pylint: disable=redefined-builtin
总结
编写Python代码时:
- 为所有函数使用类型注释
- 一致地遵循命名约定
- 为所有公共API编写清晰的文档字符串
- 保持函数专注且合理大小
- 对于简单情况,使用推导式
- 在布尔上下文中更喜欢隐式假
- 对于格式化,使用f-strings
- 始终使用上下文管理器进行资源管理
- 运行pylint并修复问题
- 与现有代码保持一致性
额外资源
有关特定主题的详细参考,请参阅:
- references/advanced_types.md - 包括Protocol、TypedDict、Literal、ParamSpec等高级类型注释模式
- references/antipatterns.md - 常见的Python错误及其修复
- references/docstring_examples.md - 所有Python构造的全面文档字符串示例