Skip to content

Python 变量工程指南

Python 3.10+Code Quality

变量是程序与数据交互的核心媒介。从计算机的角度来看,变量只是用来从内存找到某个数据的标记,叫什么名字无关紧要。但正是这些对计算机无关痛痒的东西,直接决定了人们对代码的"第一印象"。

好的变量命名并非为计算机而写,而是为每个阅读代码的人而写(包括你自己)。


一、🎯 变量基础用法 (Variable Fundamentals)

1.1 动态类型与赋值

Python 是动态类型语言,无须预先声明变量类型,直接赋值即可:

python
>>> name = 'Alice'
>>> print(f'Hello, {name}!')
Hello, Alice!

1.2 变量解包 (Unpacking)

变量解包是 Python 最优雅的赋值语法之一,正式名称为 Extended Iterable UnpackingPEP 3132),允许我们把一个可迭代对象的所有成员一次性赋值给多个变量。

基础解包

python
>>> coordinates = [10, 20]
# 左侧变量数必须与列表长度相等
>>> x, y = coordinates
>>> x
10

嵌套解包

使用小括号 (...) 可以一次展开多层嵌套数据:

python
>>> user_data = [1, ['Alice', 95]]
>>> user_id, (username, score) = user_data
>>> user_id
1
>>> username
'Alice'

动态解包 (星号表达式 *)

使用 *variable 可以贪婪地捕获多个值,并将其作为列表赋值给变量:

python
>>> items = ['first', 'apple', 'banana', 'cherry', 'last']
>>> head, *middle, tail = items
>>> head
'first'
>>> middle
['apple', 'banana', 'cherry']
>>> tail
'last'

贪婪 (Greedy) 的含义

"贪婪"在计算机领域有特殊含义:当一个操作可以选择捕获 1 个或 10 个对象时,它总是选择结果更多的那种。星号表达式正是如此——它会尽可能多地捕获中间元素。

对比传统切片

动态解包对比切片赋值语法更直观、更易读:

python
data = ['first', 'a', 'b', 'c', 'last']

# ✅ 动态解包:直观
head, *middle, tail = data

# 🔁 等价的切片赋值:繁琐
head, middle, tail = data[0], data[1:-1], data[-1]

在循环中使用解包

python
>>> records = [('Alice', 100), ('Bob', 85)]
>>> for name, score in records:
...     print(f'{name}: {score}')
Alice: 100
Bob: 85

1.3 单下划线变量名 _

单下划线 _ 是 Python 中一个特殊的约定俗成变量名,常用于以下场景:

作为占位符忽略变量

在解包时,使用 _ 明确表示"我不关心这个值":

python
>>> coordinates = [10, 20, 30]
# 只需要 x 和 z,忽略 y
>>> x, _, z = coordinates

>>> items = ['first', 'a', 'b', 'c', 'last']
# 忽略头尾之间的所有变量
>>> head, *_, tail = items

交互式命令行的特殊用法

在 Python 交互式环境中,_ 默认保存上一个表达式的返回值:

python
>>> 'hello'.upper()
'HELLO'
>>> print(_)  # _ 保存着上一个表达式的结果
HELLO

1.4 变量交换技巧

Python 可以在一行语句里同时操作多个变量,最经典的用法是交换两个变量的值:

python
>>> a, b = 1, 2
>>> a, b = b, a  # 一行完成交换
>>> a
2

二、🧠 命名哲学 (The Philosophy of Naming)

"There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton

变量名是代码中最基础的语义单元。优秀的命名能让代码像自然语言一样流畅阅读。

2.1 PEP 8 命名规范

Python 社区遵循 PEP 8 标准。以下是核心规范:

实体类型命名风格示例说明
变量/函数snake_caseuser_id, calculate_total小写,下划线分隔
常量SCREAMING_SNAKE_CASEMAX_RETRY_COUNT全大写,模块级
类/异常PascalCaseUserProfile, ValidationError首字母大写
私有成员_leading_underscore_internal_cache仅内部使用
避免使用单字符 l, O, I易与数字 1/0 混淆

重要

代码符合 PEP 8 规范应该作为对 Python 程序员的基本要求之一。假如一份代码的风格与 PEP 8 大相径庭,就基本不必继续讨论它优雅与否了。

2.2 描述性命名原则

✅ 描述性要强 (Be Descriptive)

变量名应准确描述其内容业务含义,而非其数据类型:

❌ 弱描述性✅ 强描述性改进理由
datauser_profile_chunksdata 过于泛指
temppending_request_id临时变量也有具体用途
flagis_authenticated布尔值应清晰表达状态
resultactive_usersresult 是通用词

✅ 要尽量短 (Be Concise)

在可接受的长度范围内,描述性越强越好。但过长的名字会适得其反。

诀窍:结合代码情境和上下文简化命名。

python
# ❌ 名字过长
def upgrade_user(user):
    points_required_for_level_three_upgrade = get_level_points(3)
    if user.points >= points_required_for_level_three_upgrade:
        ...

# ✅ 结合上下文简化
def upgrade_user(user):
    level3_points = get_level_points(3)  # 上下文已表明含义
    if user.points >= level3_points:
        ...

✅ 匹配类型直觉 (Match Type Intuition)

利用前缀或后缀建立类型直觉,减少大脑解析负担:

  • Boolean: 使用 is_, has_, can_, should_, allow_ 前缀
    • is_active, has_permission, can_edit
  • Int/Float: 使用数字语义词或 _id, _count, _length 后缀
    • user_id, retry_count, max_length
    • 🔴 反模式:避免用名词复数(如 apples)作为数字变量名,易与 list[Apple] 混淆
  • Collection: 使用复数名词或 _list, _map, _set 后缀
    • users, order_list, id_map

2.3 超短命名 (Short Names)

在特定惯例下,超短命名是可接受的:

场景示例说明
循环索引i, j, k约定俗成
坐标/数学x, y, n数学惯例
异常e, ex常见缩写
文件对象f, fp常见缩写
国际化_ (gettext)Django 惯例
python
# Django 国际化的惯用缩写
from django.utils.translation import gettext as _
print(_('Welcome'))

三、🏗️ 类型系统工程 (Type System Engineering)

Python 是动态强类型语言,但这不代表我们应放弃类型约束。自 Python 3.5 引入 Type Hints(PEP 484)以来,渐进式类型系统已成为 Python 工程标配。

3.1 为什么要写类型注解?

虽然 Python 解释器在运行时会忽略类型注解,但它们带来巨大的工程价值:

  1. 可读性提升:不再需要阅读函数实现就能知道参数类型
  2. IDE 智能感知:获得精准的代码补全和错误提示
  3. 静态代码分析:配合 Mypy 等工具,在运行前发现类型错误
  4. 更易维护:清晰定义接口,方便团队协作和重构

3.2 现代类型注解语法 (Python 3.10+)

python
# Python 3.9+:可直接使用内置集合类型
def process_data(
    user_ids: list[int],                  # 内置 list
    options: dict[str, str] | None = None # 使用 | 替代 Optional
) -> tuple[bool, str]:
    ...

# Python 3.10+:更简洁的 Union 语法
def greet(name: str | None) -> str:
    return f'Hello, {name or "Guest"}!'

3.3 保持变量的一致性

在使用变量时,需保证两方面的一致性:

  1. 名字一致性:同一概念在项目中应使用相同的名字
  2. 类型一致性:不要让同一个变量指向不同类型的数据
python
# ❌ Bad: 类型复用,欺骗读者直觉
def process():
    users = {'data': ['Alice', 'Bob']}  # Dict
    ...
    users = []  # 变成了 List!

# ✅ Good: 定义新变量
def process():
    users_map = {'data': ['Alice', 'Bob']}
    ...
    user_list = []

Mypy 会对这种"变量类型不一致"报错:Incompatible types in assignment


四、⚙️ 编程建议 (Best Practices)

4.1 变量定义遵循"就近原则"

反模式:在函数开头定义所有变量(像 C 语言旧规范)。

python
def generate_report(data):
    # ❌ 预定义所有变量,读者不知道它们何时会被用到
    sections = []
    charts = []
    summary = {}
    
    # ... 50行代码后 ...
    sections.append(...)

最佳实践:在真正需要使用变量的时候再定义它。

python
def generate_report(data):
    # 处理 sections
    sections = []
    sections.append(...)
    
    # 处理 charts
    charts = []
    charts.append(...)

4.2 定义临时变量提升可读性

不要直接写复杂的布尔表达式。将其提取为具名变量,让代码读起来像句子。

python
# ❌ Hard to read
if user.is_active and (user.role == 'admin' or user.level > 5):
    grant_access(user)

# ✅ Easy to read:临时变量让逻辑自解释
is_eligible = user.is_active and (user.role == 'admin' or user.level > 5)
if is_eligible:
    grant_access(user)

4.3 控制作用域内变量数量

当函数内变量过多时,通常意味着函数过于复杂。解决办法:

  1. 提炼数据类:将相关变量归组为类
  2. 拆分函数:将复杂函数拆分为多个职责单一的小函数
python
from dataclasses import dataclass

# ❌ 变量过多
def import_users(fp):
    duplicated, banned, normal = [], [], []
    success_count, fail_count = 0, 0
    ...

# ✅ 使用数据类归组
@dataclass
class ImportResult:
    success_count: int = 0
    fail_count: int = 0
    duplicated: list = None
    banned: list = None
    normal: list = None

4.4 能不定义变量就别定义

如果一个变量只用一次,且逻辑非常简单,直接内联可能更好(YAGNI 原则)。

python
# ❌ Over-engineering
def get_user_summary(user_id):
    user = get_user(user_id)
    orders = get_orders(user_id)
    result = {'user': user, 'orders': orders}
    return result

# ✅ Concise
def get_user_summary(user_id):
    return {
        'user': get_user(user_id),
        'orders': get_orders(user_id)
    }

4.5 不要使用 locals()

Python 之禅:显式优于隐式 (Explicit is better than implicit)

不要为了偷懒,在 Django 模板渲染等场景使用 locals() 批量传递变量:

python
# ❌ 隐晦:读者不知道传了什么
return render(request, 'page.html', locals())

# ✅ 显式:一目了然
return render(request, 'page.html', {
    'user': user,
    'orders': orders,
    'is_vip': is_vip
})

五、🔨 重构实战案例 (Case Study)

以下是一个典型的"能跑但难读"的冒泡排序代码重构案例。

5.1 Legacy Code (原始代码)

python
def custom_sort(nums):
    j = len(nums) - 1
    while j > 0:
        for i in range(j):
            if nums[i] % 2 == 0 and nums[i + 1] % 2 == 1:
                nums[i], nums[i + 1] = nums[i + 1], nums[i]
                continue
            elif (nums[i + 1] % 2 == nums[i] % 2) and nums[i] > nums[i + 1]:
                nums[i], nums[i + 1] = nums[i + 1], nums[i]
                continue
        j -= 1
    return nums

问题分析

  1. 变量名无意义:j, i, nums 难以理解
  2. 逻辑嵌套过深
  3. 缺乏类型注解
  4. 缺乏空行,代码密集难读

5.2 Clean Code (重构后)

python
def odd_even_sort(numbers: list[int]) -> list[int]:
    """执行奇偶冒泡排序。
    
    规则:
    1. 偶数视为比奇数大(偶数会冒泡到右侧)
    2. 同为奇数或偶数时,按数值大小排序
    """
    stop_position = len(numbers) - 1
    
    while stop_position > 0:
        for i in range(stop_position):
            current, next_item = numbers[i], numbers[i + 1]
            current_is_even = (current % 2 == 0)
            next_is_even = (next_item % 2 == 0)
            
            should_swap = False

            # 规则一:前偶后奇,必须交换
            if current_is_even and not next_is_even:
                should_swap = True
            # 规则二:奇偶性相同,数值大的后移
            elif (current_is_even == next_is_even) and (current > next_item):
                should_swap = True

            if should_swap:
                numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
        
        stop_position -= 1
        
    return numbers

重构收益

  • 语义化stop_position 明确了循环边界
  • 逻辑解耦:引入 should_swap 临时变量
  • 中间变量current_is_even 消除了魔法表达式
  • 空行利用:适当的空行将代码分块,增加"呼吸感"

六、✅ 最佳实践清单 (Checklist)

优先级检查项描述
P0PEP 8变量命名是否遵循 PEP 8 规范?
P0描述性变量名是否具有足够的描述性?
P1类型注解是否为关键函数添加了类型注解?
P1一致性同一概念是否使用了一致的命名和类型?
P2解包是否利用解包语法简化了多变量赋值?
P2临时变量复杂表达式是否提取为有意义的临时变量?
P3就近原则变量定义是否遵循了就近原则?
P3变量数量作用域内变量数量是否过多?

七、📚 延伸阅读


← 返回 Python 深度研究