Python 的名字、赋值与作用域入门

Python 的“变量”是名字到对象的绑定;赋值不复制对象,作用域按 LEGB 查找,理解它才能解释可变共享与闭包。

#type / concept #status / growing #tech / dev #resource / python #tech / lang / python

[!info] 关联笔记

Python 的名字、赋值与作用域入门

这个概念为什么出现

从 C/Java 直觉进入 Python,最容易把:

a = [1, 2]
b = a

理解成“复制了一份列表”。真正发生的是:名字 ba 绑定到同一个列表对象。后续所有“改了参数内部、外面也变了”“可变默认参数诡异”都建立在这层模型上。

[!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']

结合场景再看三个关注点

  1. 共享可变对象时,函数参数写入会穿透到调用方。
  2. open_tickets = [...] 是换绑名字,不是清空旧列表。
  3. API 若不想共享,应显式拷贝或返回新对象——见后续可变性笔记。

核心概念与准确模型

1. 名字 → 对象

  • 对象有身份(id/is)、类型与值。
  • 名字在命名空间里指向对象。
  • 多个名字可以指向同一对象。

2. 赋值与增强赋值

  • x = y:让 x 指向 y 当前指向的对象。
  • x += y:对可变对象可能是原地修改;对不可变对象通常是产生新对象再绑定。

3. 作用域 LEGB(入门)

层级含义
Local函数内赋值的名字
Enclosing外层函数(闭包)
Global模块全局
Built-inlen/str 等内建
  • 函数内赋值默认创建本地名;若要写外层/全局,需 nonlocal / global
  • 阶段一只要求建立直觉;闭包细节在函数笔记展开。

4. 删除名字

  • del x 删除的是绑定,不一定立刻销毁对象(还要看引用计数/其他引用)。

边界情况与反直觉行为

  1. 小整数/短字符串驻留是实现优化,is 比较字面量不可当作业务逻辑。业务身份比较用 is 仅适合 None 哨兵等明确场景。
  2. 默认参数在定义时求值且绑定一次:可变默认参数会跨调用共享(经典坑)。
  3. 类体与函数体都有独立命名空间,但查找规则细节不同。

常见误区

[!warning] 常见误区:把 Python 变量当成“装值的盒子” 错误理解:赋值总是复制内容。
正确模型:赋值复制的是引用/绑定;对象是否复制要看类型与是否显式 copy。

[!warning] 常见误区:用 is 比较字符串或数字相等 错误理解:a is b 表示值相等。
正确模型:值相等用 ==is 是身份。

工程实践

  • 函数文档写清:参数是否被原地修改。
  • 对外返回的可变对象,考虑拷贝或不可变结构。
  • Noneis 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教程列表操作与共享直觉
创建于 2026/7/15 更新于 2026/7/15