Python代码规范与最佳实践:PEP 8不是教条,但这些规则值得遵守
良好的代码规范不仅让代码更易读、易维护,也是团队协作的基础。本篇介绍Python的PEP 8规范和常用的最佳实践。
1. PEP 8代码风格
# ===== 缩进 =====
# 使用4个空格,不要用Tab
def function():
if True:
print("正确缩进")
# 续行对齐
result = some_function(
arg1, arg2,
arg3, arg4
)
# 或悬挂缩进
result = some_function(
arg1, arg2, arg3, arg4
)
# ===== 行长度 =====
# 每行最多79字符(代码),72字符(文档/注释)
# 长行可以用括号换行
long_string = (
"这是一个很长的字符串,"
"可以用括号来换行"
)
# ===== 空行 =====
# 顶级定义之间空两行
class MyClass:
pass
def my_function():
pass
# 类内方法之间空一行
class AnotherClass:
def method1(self):
pass
def method2(self):
pass
# ===== 导入 =====
# 导入应该分组,每组之间空一行
# 1. 标准库
# 2. 第三方库
# 3. 本地模块
import os
import sys
import numpy as np
import pandas as pd
from mypackage import mymodule
# 不要使用 from module import *
# 每个导入单独一行
import os
import sys
# 而不是 import os, sys
# ===== 空格 =====
# 括号内不要空格
spam(ham[1], {eggs: 2}) # 正确
spam( ham[ 1 ], { eggs: 2 } ) # 错误
# 逗号后面加空格
x, y = 1, 2
# 运算符两边加空格
x = 1 + 2
y = x * 2
# 但函数参数默认值不加空格
def func(arg1, arg2=None):
pass
# 冒号用于切片时不加空格
lst[1:3]
lst[::2]
2. 命名规范
# ===== 变量和函数:小写下划线 =====
user_name = "张三"
total_count = 100
def calculate_average(numbers):
return sum(numbers) / len(numbers)
# ===== 常量:大写下划线 =====
MAX_SIZE = 1024
DEFAULT_TIMEOUT = 30
PI = 3.14159
# ===== 类名:大驼峰 =====
class UserAccount:
pass
class HTTPConnection:
pass
# ===== 模块名:小写,可用下划线 =====
# my_module.py
# utils.py
# data_processor.py
# ===== 私有成员:单下划线前缀 =====
class MyClass:
def __init__(self):
self._internal_value = 0 # 内部使用
def _helper_method(self): # 内部方法
pass
# ===== 名称修饰:双下划线前缀 =====
class Parent:
def __init__(self):
self.__private = 1 # 会被修饰为 _Parent__private
# ===== 魔术方法:双下划线包围 =====
class Container:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def __str__(self):
return f"Container({self.items})"
# ===== 避免的命名 =====
# 不要用单字符 l, O, I(容易混淆)
# 不要用内置名称:list, dict, str, id, type
# 不要用拼音,用英文
# ===== 好的命名示例 =====
# 动词开头的函数名
def get_user_by_id(user_id):
pass
def calculate_total_price(items):
pass
def is_valid_email(email): # 返回布尔值用is/has/can开头
pass
def has_permission(user, action):
pass
# 名词的变量名
user_list = []
error_message = ""
file_path = ""
3. 代码组织
# ===== 文件结构 =====
"""
模块文档字符串
"""
# 1. 导入
from __future__ import annotations # future导入(如果需要)
import os # 标准库
import sys
import numpy as np # 第三方库
from .utils import helper # 本地导入
# 2. 常量
DEFAULT_VALUE = 100
MAX_RETRIES = 3
# 3. 类型别名
UserDict = dict[str, any]
# 4. 异常类
class CustomError(Exception):
pass
# 5. 辅助函数
def _helper():
pass
# 6. 主要类
class MainClass:
pass
# 7. 主要函数
def main():
pass
# 8. 入口点
if __name__ == '__main__':
main()
# ===== 函数组织 =====
def well_organized_function(arg1, arg2, arg3=None):
"""
函数文档字符串
"""
# 1. 参数验证
if arg1 is None:
raise ValueError("arg1不能为None")
# 2. 初始化
result = []
# 3. 主要逻辑
for item in arg1:
processed = process(item)
result.append(processed)
# 4. 返回结果
return result
# ===== 类组织 =====
class WellOrganizedClass:
"""类文档字符串"""
# 1. 类属性
class_attribute = "value"
# 2. __init__
def __init__(self, value):
self.value = value
# 3. 特殊方法
def __str__(self):
return f"WellOrganizedClass({self.value})"
def __repr__(self):
return self.__str__()
# 4. 属性
@property
def computed_value(self):
return self.value * 2
# 5. 公共方法
def public_method(self):
pass
# 6. 私有方法
def _private_method(self):
pass
# 7. 类方法
@classmethod
def from_string(cls, s):
return cls(int(s))
# 8. 静态方法
@staticmethod
def utility():
pass
4. 文档字符串
# ===== 模块文档 =====
"""
模块名称
模块的简要描述。
详细描述可以有多段。
Example:
>>> import mymodule
>>> mymodule.function()
Attributes:
MODULE_CONSTANT: 模块级常量说明
Todo:
* 待完成的功能
"""
# ===== 函数文档(Google风格) =====
def function_with_docstring(arg1: int, arg2: str, arg3: bool = True) -> dict:
"""函数的简要描述。
更详细的描述可以有多行。
Args:
arg1: 第一个参数的描述。
arg2: 第二个参数的描述。
arg3: 第三个参数的描述,默认为True。
Returns:
返回值的描述。例如:
{'key': 'value'}
Raises:
ValueError: 当arg1为负数时抛出。
TypeError: 当arg2不是字符串时抛出。
Example:
>>> result = function_with_docstring(1, "test")
>>> print(result)
{'key': 'value'}
"""
if arg1 < 0:
raise ValueError("arg1不能为负数")
return {'key': 'value'}
# ===== 类文档 =====
class DocumentedClass:
"""类的简要描述。
更详细的描述。
Attributes:
attr1: 属性1的描述。
attr2: 属性2的描述。
Example:
>>> obj = DocumentedClass("value")
>>> obj.method()
"""
def __init__(self, value: str):
"""初始化DocumentedClass。
Args:
value: 初始值。
"""
self.attr1 = value
self.attr2 = None
def method(self, param: int) -> str:
"""方法的简要描述。
Args:
param: 参数描述。
Returns:
返回值描述。
"""
return str(param)
# ===== NumPy风格文档 =====
def numpy_style_function(param1, param2):
"""
函数简要描述。
详细描述。
Parameters
----------
param1 : int
参数1描述。
param2 : str
参数2描述。
Returns
-------
bool
返回值描述。
See Also
--------
other_function : 相关函数。
Examples
--------
>>> numpy_style_function(1, "test")
True
"""
return True
5. 类型提示
from typing import (
List, Dict, Tuple, Set, Optional, Union,
Callable, Iterator, Generator, Any,
TypeVar, Generic
)
# ===== 基本类型提示 =====
def greet(name: str) -> str:
return f"Hello, {name}"
def add(a: int, b: int) -> int:
return a + b
# ===== 容器类型 =====
def process_list(items: List[int]) -> List[int]:
return [x * 2 for x in items]
def get_user(users: Dict[str, int]) -> Optional[int]:
return users.get("admin")
def get_point() -> Tuple[int, int]:
return (0, 0)
# Python 3.9+ 可以直接用内置类型
def process_list_new(items: list[int]) -> list[int]:
return [x * 2 for x in items]
# ===== Optional和Union =====
def find_user(user_id: int) -> Optional[str]:
"""返回用户名或None"""
return None
def process(value: Union[int, str]) -> str:
"""接受int或str"""
return str(value)
# Python 3.10+ 可以用 |
def process_new(value: int | str) -> str:
return str(value)
# ===== Callable =====
def apply_func(func: Callable[[int, int], int], a: int, b: int) -> int:
return func(a, b)
# ===== 泛型 =====
T = TypeVar('T')
def first(items: List[T]) -> T:
return items[0]
class Stack(Generic[T]):
def __init__(self):
self.items: List[T] = []
def push(self, item: T) -> None:
self.items.append(item)
def pop(self) -> T:
return self.items.pop()
# ===== 类型别名 =====
UserId = int
UserDict = Dict[UserId, str]
def get_users() -> UserDict:
return {1: "Alice", 2: "Bob"}
# ===== 类方法类型 =====
from typing import Self # Python 3.11+
class Builder:
def set_name(self, name: str) -> Self:
self.name = name
return self
# ===== dataclass与类型 =====
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: Optional[str] = None
6. 代码检查工具
# ===== 安装工具 =====
pip install flake8 black isort mypy pylint
# ===== flake8:代码风格检查 =====
flake8 myfile.py
flake8 --max-line-length=100 myfile.py
# .flake8 配置文件
# [flake8]
# max-line-length = 100
# ignore = E501,W503
# ===== black:代码格式化 =====
black myfile.py
black --line-length 100 myfile.py
black --check myfile.py # 只检查不修改
# ===== isort:导入排序 =====
isort myfile.py
isort --check-only myfile.py
# ===== mypy:类型检查 =====
mypy myfile.py
mypy --strict myfile.py
# mypy.ini 配置
# [mypy]
# python_version = 3.10
# warn_return_any = True
# warn_unused_ignores = True
# ===== pylint:综合检查 =====
pylint myfile.py
# ===== pre-commit:Git提交前检查 =====
# .pre-commit-config.yaml
# repos:
# - repo: https://github.com/psf/black
# rev: 23.1.0
# hooks:
# - id: black
# - repo: https://github.com/pycqa/isort
# rev: 5.12.0
# hooks:
# - id: isort
# - repo: https://github.com/pycqa/flake8
# rev: 6.0.0
# hooks:
# - id: flake8
# VS Code settings.json 配置
"""
{
"python.linting.enabled": true,
"python.linting.flake8Enabled": true,
"python.formatting.provider": "black",
"editor.formatOnSave": true,
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}
}
"""
7. 常见反模式
# ===== 反模式1:过长的函数 =====
# 不好
def do_everything(data):
# 100行代码...
pass
# 好:拆分成小函数
def process_data(data):
validated = validate(data)
transformed = transform(validated)
return save(transformed)
# ===== 反模式2:魔法数字 =====
# 不好
if status == 1:
pass
# 好:使用常量或枚举
from enum import Enum
class Status(Enum):
ACTIVE = 1
INACTIVE = 2
if status == Status.ACTIVE:
pass
# ===== 反模式3:过深的嵌套 =====
# 不好
def process(data):
if data:
if data.valid:
if data.ready:
return data.value
# 好:提前返回
def process(data):
if not data:
return None
if not data.valid:
return None
if not data.ready:
return None
return data.value
# ===== 反模式4:重复代码 =====
# 不好
def process_user(user):
print(f"Processing {user.name}")
# 处理逻辑
def process_admin(admin):
print(f"Processing {admin.name}")
# 相同的处理逻辑
# 好:抽取公共逻辑
def process_person(person):
print(f"Processing {person.name}")
# 处理逻辑
# ===== 反模式5:过度使用全局变量 =====
# 不好
config = {}
def init():
global config
config = load_config()
# 好:使用类或依赖注入
class App:
def __init__(self, config):
self.config = config
# ===== 反模式6:捕获所有异常 =====
# 不好
try:
do_something()
except:
pass
# 好:捕获特定异常
try:
do_something()
except ValueError as e:
logger.error(f"值错误: {e}")
except IOError as e:
logger.error(f"IO错误: {e}")
# ===== 反模式7:使用可变默认参数 =====
# 不好
def append(item, lst=[]):
lst.append(item)
return lst
# 好
def append(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
8. Pythonic写法
# ===== 列表推导式 =====
# 不好
squares = []
for x in range(10):
squares.append(x ** 2)
# 好
squares = [x ** 2 for x in range(10)]
# ===== 字典推导式 =====
# 不好
d = {}
for k, v in items:
d[k] = v
# 好
d = {k: v for k, v in items}
# ===== 解包 =====
# 不好
first = lst[0]
rest = lst[1:]
# 好
first, *rest = lst
# ===== 交换变量 =====
# 不好
temp = a
a = b
b = temp
# 好
a, b = b, a
# ===== 条件表达式 =====
# 不好
if condition:
x = 1
else:
x = 2
# 好
x = 1 if condition else 2
# ===== enumerate =====
# 不好
for i in range(len(lst)):
print(i, lst[i])
# 好
for i, item in enumerate(lst):
print(i, item)
# ===== zip =====
# 不好
for i in range(len(names)):
print(names[i], ages[i])
# 好
for name, age in zip(names, ages):
print(name, age)
# ===== 上下文管理器 =====
# 不好
f = open('file.txt')
try:
content = f.read()
finally:
f.close()
# 好
with open('file.txt') as f:
content = f.read()
# ===== 字符串连接 =====
# 不好
s = ""
for item in items:
s += str(item)
# 好
s = "".join(str(item) for item in items)
# ===== 检查空集合 =====
# 不好
if len(lst) == 0:
pass
# 好
if not lst:
pass
# ===== 字典get =====
# 不好
if key in d:
value = d[key]
else:
value = default
# 好
value = d.get(key, default)
# ===== any/all =====
# 不好
found = False
for item in items:
if condition(item):
found = True
break
# 好
found = any(condition(item) for item in items)
9. 项目结构
my_project/
├── README.md # 项目说明
├── LICENSE # 许可证
├── pyproject.toml # 项目配置(推荐)
├── setup.py # 安装脚本(可选)
├── requirements.txt # 依赖列表
├── requirements-dev.txt # 开发依赖
├── .gitignore # Git忽略文件
├── .env.example # 环境变量示例
│
├── src/ # 源代码
│ └── my_package/
│ ├── __init__.py
│ ├── main.py
│ ├── config.py
│ ├── utils/
│ │ ├── __init__.py
│ │ └── helpers.py
│ └── models/
│ ├── __init__.py
│ └── user.py
│
├── tests/ # 测试代码
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_main.py
│ └── test_utils.py
│
├── docs/ # 文档
│ ├── index.md
│ └── api.md
│
├── scripts/ # 脚本
│ └── setup_db.py
│
└── data/ # 数据文件
└── sample.csv
# pyproject.toml 示例
[project]
name = "my_package"
version = "0.1.0"
description = "项目描述"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"requests>=2.28.0",
"pandas>=1.5.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"black>=23.0.0",
"flake8>=6.0.0",
"mypy>=1.0.0",
]
[tool.black]
line-length = 100
[tool.isort]
profile = "black"
line_length = 100
[tool.mypy]
python_version = "3.10"
warn_return_any = true
10. 总结
🔑 核心要点
| 规范 | 要点 |
|---|---|
| PEP 8 | 4空格缩进,79字符行宽 |
| 命名 | 函数小写下划线,类大驼峰 |
| 文档 | 使用docstring,Google或NumPy风格 |
| 类型提示 | 使用typing模块,提高可读性 |
| 工具 | black格式化,flake8检查,mypy类型检查 |
| Pythonic | 列表推导式,上下文管理器,解包 |
✅ 学习检查清单
- 了解PEP 8基本规范
- 掌握命名规范
- 会写文档字符串
- 了解类型提示
- 会使用代码检查工具
📖 下一步学习
掌握了代码规范后,让我们学习常用第三方库:
常见问题 FAQ
💬 black和autopep8怎么选?
black是”无争议”格式化器,不给你选择,团队统一用black最省心。autopep8更温和,只修复PEP 8违规。推荐用black+isort组合。
💬 类型提示是必须的吗?
不是强制的,Python运行时不检查类型。但强烈推荐:配合Pylance/mypy可以在编辑时发现类型错误,相当于免费的静态分析。公共API和函数签名至少加上类型提示。
� 系列导航
- 上一篇:29 - Python代码调试技巧
- 当前:30 - Python代码规范与最佳实践
- 下一篇:31 - Python常用第三方库