在编程的世界里,优雅的代码,从来都不是为了取悦计算机,而是为了善待未来的自己、同事,甚至那个素未谋面却要维护你代码的陌生人。

我们写的代码,终将被阅读,所以我认为好的代码自己会说话,而我们要做的就是让它说的更清楚,更有逻辑。

命名艺术:让代码自解释

在主流的编程语言中,变量命名风格主要分为驼峰命名派(CameCase)和蛇形命名派(snake_case),其中驼峰又分为大驼峰(CameCase)和小驼峰(cameCase)。在Python中,大驼峰和蛇形命名两种风格在不同场景中应用的都比较频繁,大驼峰命名主要应用在类名的定义上,其他场景绝大部分都是使用蛇形命名。另一方面 Python 社区普遍遵循的是 PEP 8 规范,强调一致性与可读性。

命名规范速查

类型 推荐命名方式 示例 说明
模块 lower_with_under utils.py, _private.py 简短小写,避免下划线开头
lowercase mypackage 不建议使用下划线
CapWords(大驼峰) DatabaseConnection, User 异常类应以 Error 结尾
函数/方法 lower_with_under() get_user_by_id() 动词开头,描述行为
常量 CAPS_WITH_UNDER MAX_RETRIES, API_TIMEOUT 全大写,表示不可变值
布尔变量 is_xxx, has_xxx is_connected, has_data 明确表达真假状态
私有成员 __private_attr __password, __init_db() 双下划线触发名称重整

命名编写原则

  1. 遵循PEP 8原则(Python官方制定的编码风格指南和建议);
  2. 命名需要做到名如其意,不要吝啬名字的长度,但除了一些特殊场景,变量名一般不建议超过4个单词;
  3. 变量的描述性要强,比如:冬天的梅花比花的描述性更强;
  4. 当命名与关键字冲突时,在变量末尾加下划线,比如:class_;

优秀命名实践

  1. 增强描述性
1
2
3
4
5
6
# (bad) 描述性弱的名字,过于抽象,看不懂在做什么
value = process(s.strip())
data = get_info()

# (good) 描述性强的名字,尝试从用户输入里解析一个用户名
username = extract_username(input_string.strip())
  1. 避免歧义
1
2
3
4
5
6
7
# 容易混淆
l = [] # 看起来像数字1
O = 0 # 看起来像数字0

# 清晰明确
users_list = []
default_value = 0
  1. 类型暗示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 集合类型
user_ids_set = {1, 2, 3}
configuration_dict = {"timeout": 30}

# 布尔类型
should_retry = True
is_connected = False
has_permission = True

# 数值类型
port、age # 表示数字的单词
user_id, host_id # 使用_id结尾
max_length、users_count # 使用length/count相关词
retry_count = 3
total_amount = 100.50

注释之道:解释「为什么」而非「是什么」

好的注释应该解释设计意图,而不是重复代码内容。

Python中的注释主要分为以#开头的单行注释、和以三连字符串("""...""")表示的多行注释或文档注释。

注释编写原则

  • 代码块注释,在代码块上一行的相同缩进处以 # 开始书写注释;代码块注释最需要写注释的地方是代码中那些技巧性的部分;对于复杂的操作,应该在其操作开始之前写上若干行注释,对于不是一目了然的代码,应该在其行尾添加注释;
  • 代码行注释,在代码行的尾部跟2个空格,然后以 # 开始书写注释,行注释尽量少写;
  • TODO注释应该在所有开头处包含“TODO”字符串,紧跟着用括号括起来你的名字,email地址或其他标识符,然后是一个可选的冒号;
  • 对于TODO注释的目的是用来表示“将来做某件事”,建议添加指定的日期;
  • 英文注释开头要大写,结尾要写标点符号,避免语法错误和逻辑错误,中文注释也是相同要求;
  • 改动代码逻辑时,一定要及时更新相关的注释;

注释实践

1. 文档注释(Docstrings)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class DatabaseConnection:
"""数据库连接管理类

提供连接池管理、自动重连和查询重试功能
支持MySQL和PostgreSQL数据库

Attributes:
pool_size: 连接池大小
timeout: 查询超时时间(秒)
max_retries: 最大重试次数
"""

def __init__(self, connection_string: str, pool_size: int = 10):
"""初始化数据库连接

Args:
connection_string: 数据库连接字符串
pool_size: 连接池大小,默认为10

Raises:
ConnectionError: 当连接数据库失败时
"""
self.connection_string = connection_string
self.pool_size = pool_size
self._initialize_pool()

def execute_query(self, query: str, parameters: dict = None) -> list:
"""执行SQL查询

Args:
query: SQL查询语句
parameters: 查询参数字典

Returns:
查询结果列表

Raises:
QueryTimeoutError: 查询超时时
DatabaseError: 数据库错误时
"""
# 方法实现
pass

2. 为什么注释(Why Comments)

1
2
3
# 使用快速排序而不是内置sort,因为需要稳定排序
# 并且在处理大量数据时性能更好
quicksort(data)

3. 警示注释

1
2
3
4
# 注意:这个函数修改传入的列表,而不是返回新列表
# 使用前请确保不需要原始数据
def process_in_place(items):
# ... 实现细节

4. 代码逻辑注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def merge_sorted_arrays(arr1: list, arr2: list) -> list:
"""合并两个已排序数组

使用双指针法实现O(n)时间复杂度合并
适用于大规模数据合并场景
"""
result = []
i = j = 0

# 双指针遍历,选择较小元素加入结果
while i < len(arr1) and j < len(arr2):
if arr1[i] < arr2[j]:
result.append(arr1[i])
i += 1
else:
result.append(arr2[j])
j += 1

# 将剩余元素添加到结果中
result.extend(arr1[i:])
result.extend(arr2[j:])

return result
  1. TODO注释
1
2
3
# TODO(ssw@gmail.com): Use a "*" here for string repetition.

# TODO(Luke) Change this to use relations.
  1. 改良一些不好的注释习惯
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 过度注释
# 不好:注释重复代码内容
x = x + 1 # 给x加1

# 好:注释解释为什么这样做
x = x + 1 # 补偿数组索引从0开始的问题

# 2.魔法数字
# 不好:直接使用魔法数字
if len(users) > 100:
# ...

# 好:使用有意义的常量
MAX_USERS_THRESHOLD = 100
if len(users) > MAX_USERS_THRESHOLD:
# ...

# 3. 过长的函数名
# 不好:过于冗长
def get_user_information_from_database_by_user_id():
# ...

# 好:简洁明确
def get_user_by_id(user_id):
# ...

导包规范:整洁的代码门面

导入语句应该按照标准库 → 第三方库 → 本地应用的顺序分组排列:

导包编写原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from __future__ import annotations

# 1. 标准库导入
import os
import sys
from pathlib import Path
from typing import List, Dict, Optional

# 2. 第三方库导入
import requests
from django.conf import settings
import pandas as pd

# 3. 本地应用导入
from .models import User, Profile
from .utils.helpers import logger, config
from .exceptions import CustomError

# 模块级常量
DEFAULT_TIMEOUT = 30
MAX_RETRIES = 3

优雅导包实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 每行一个导入
import json
import time

# 使用括号的多行导入
from collections import (
defaultdict,
OrderedDict,
namedtuple
)

# 使用别名简化长模块名
import matplotlib.pyplot as plt
import numpy as np

格式之美:缩进、空行与空格

Python的强制缩进本身就是一个约束美,但我们仍需注意细节,让代码看着更舒服。

缩进与换行

  • 使用 4 个空格 缩进(不混用 Tab)
  • 单行不超过 120 字符
  • 超长语句可用括号隐式续行:
1
2
3
4
5
6
7
result = (
process_data(
input_data,
transform_fn,
validate=True
)
)

空行规范

  • 文件级函数/类之间:两个空行
  • 类内方法之间:一个空行
  • 函数内部逻辑块之间:一个空行
  • 文件末尾:保留一个空行

空格规范

场景 示例
操作符两边 a + b, x == y
逗号后 [1, 2, 3], func(a, b)
冒号后(字典) {"name": "Alice"}
注释符号后 # 这是一个注释
括号内不加空格 (1, 2), [x for x in data]

自动化: 让工具守护代码规范

手动遵循规范太累?那就让工具替我们守规。

常用工具链

  • Ruff: 极速的Python linter和格式化工具

  • Black: 无妥协的代码格式化器

  • isort: 自动规范import语句顺序

  • mypy: 静态类型检查

3. 自动化工具配置

pyproject.toml 配置示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[tool.ruff]
line-length = 120
select = ["E", "F", "W", "I", "B", "C", "N", "A", "S", "T", "Q"]
ignore = ["E501"] # 忽略行长警告(由 black 处理)
target-version = "py312"

[tool.black]
line-length = 120
target-version = ['py312']
skip-string-normalization = true

[tool.isort]
profile = "black"
line_length = 120
known_first_party = ["myapp"]

预提交钩子配置

安装后运行 pre-commit install,每次提交自动格式化 + 检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.0
hooks:
- id: ruff
args: [--fix, --show-fixes]
- repo: https://github.com/psf/black
rev: 23.10.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.6.0
hooks:
- id: mypy

写在最后

优秀的代码风格,不是限制,而是解放,它解放了我们的注意力,让我们不再纠结“这变量是啥”,不再困惑“这段代码想干啥”,让我们能够专注于解决真正重要的事:解决问题,创造价值。


本站由 sswfive 使用 Stellar 1.33.1 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站总访问量