Python 的名字、赋值与作用域入门
Python 的“变量”是名字到对象的绑定;赋值不复制对象,作用域按 LEGB 查找,理解它才能解释可变共享与闭包。
#type / concept
#status / growing
#tech / dev
#resource / python
#tech / lang / python
[!info] 关联笔记
Python 的名字、赋值与作用域入门
这个概念为什么出现
从 C/Java 直觉进入 Python,最容易把:
a = [1, 2]
b = a
理解成“复制了一份列表”。真正发生的是:名字 b 与 a 绑定到同一个列表对象。后续所有“改了参数内部、外面也变了”“可变默认参数诡异”都建立在这层模型上。
[!abstract] 一句话理解 Python 赋值是名字绑定(rebinding),不是把对象拷进盒子;查找名字时沿 LEGB(Local → Enclosing → Global → Built-in)规则进行。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:客服系统把同一张工单列表分给两个处理函数
工单服务把“待处理列表”交给自动标签器与 SLA 检查器。若两者共享同一列表对象,一方 append 会影响另一方——有时是 bug,有时是有意共享。必须先看清绑定。
# ticket_binding_demo.py
# 业务意图:演示“两个名字指向同一工单列表”时的共享副作用。
# 教学点:
# - 赋值绑定对象,不复制 list;
# - is 比较身份,== 比较值;
# - 重新赋值(rebinding)与原地修改(mutate)不是一回事。
def tag_urgent(tickets: list[str]) -> None:
# 原地修改:往共享列表追加标签工单。
tickets.append("T-URGENT")
def main() -> None:
open_tickets = ["T-100", "T-101"]
queue_view = open_tickets # 绑定到同一对象,不是拷贝
print("same object?", queue_view is open_tickets)
tag_urgent(queue_view)
print("after tag:", open_tickets)
# rebinding:名字 open_tickets 指向新列表,不影响 queue_view 仍指向旧对象
open_tickets = ["T-200"]
print("open_tickets now:", open_tickets)
print("queue_view still:", queue_view)
if __name__ == "__main__":
main()
建议运行:
python ticket_binding_demo.py
期望输出:
same object? True
after tag: ['T-100', 'T-101', 'T-URGENT']
open_tickets now: ['T-200']
queue_view still: ['T-100', 'T-101', 'T-URGENT']
结合场景再看三个关注点
- 共享可变对象时,函数参数写入会穿透到调用方。
open_tickets = [...]是换绑名字,不是清空旧列表。- API 若不想共享,应显式拷贝或返回新对象——见后续可变性笔记。
核心概念与准确模型
1. 名字 → 对象
- 对象有身份(
id/is)、类型与值。 - 名字在命名空间里指向对象。
- 多个名字可以指向同一对象。
2. 赋值与增强赋值
x = y:让x指向y当前指向的对象。x += y:对可变对象可能是原地修改;对不可变对象通常是产生新对象再绑定。
3. 作用域 LEGB(入门)
| 层级 | 含义 |
|---|---|
| Local | 函数内赋值的名字 |
| Enclosing | 外层函数(闭包) |
| Global | 模块全局 |
| Built-in | len/str 等内建 |
- 函数内赋值默认创建本地名;若要写外层/全局,需
nonlocal/global。 - 阶段一只要求建立直觉;闭包细节在函数笔记展开。
4. 删除名字
del x删除的是绑定,不一定立刻销毁对象(还要看引用计数/其他引用)。
边界情况与反直觉行为
- 小整数/短字符串驻留是实现优化,
is比较字面量不可当作业务逻辑。业务身份比较用is仅适合None哨兵等明确场景。 - 默认参数在定义时求值且绑定一次:可变默认参数会跨调用共享(经典坑)。
- 类体与函数体都有独立命名空间,但查找规则细节不同。
常见误区
[!warning] 常见误区:把 Python 变量当成“装值的盒子” 错误理解:赋值总是复制内容。
正确模型:赋值复制的是引用/绑定;对象是否复制要看类型与是否显式 copy。
[!warning] 常见误区:用
is比较字符串或数字相等 错误理解:a is b表示值相等。
正确模型:值相等用==;is是身份。
工程实践
- 函数文档写清:参数是否被原地修改。
- 对外返回的可变对象,考虑拷贝或不可变结构。
None用is None/is not None。
本节总结
“变量”在 Python 里首先是名字绑定问题。先建立这层心智,list/dict 的共享、闭包捕获、默认参数坑才会变得可预测。
自测题
代码推理题
def add_tag(items, tag):
items = items + [tag]
return items
base = ["a"]
out = add_tag(base, "b")
print(base, out)
base 是否变化?为什么?
参考答案
base 仍是 ["a"]。函数里 items = items + [tag] 重新绑定本地名 items 到新列表,没有原地修改调用方列表;out 是 ["a", "b"]。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Naming and binding | 规范 | 绑定与作用域 |
| Tutorial – Data Structures | 教程 | 列表操作与共享直觉 |