⌨️ 第四章 流程图与算法
📄 一句话概括

变量不是全楼通用:函数里出生的局部变量出了门就消失,写在文件顶层的全局变量全楼可见——作用域就是一个变量的【有效半径】,半径越小越好管。

📝 本节和前面几节的关系

4-4~4-5 一直在函数里过日子,这一篇讲函数【围墙内外】的差别。回收 4-5 的【函数内换房号不影响外面】——那正是作用域隔离的日常表现。

一、两个世界:全局与局部

campus = "总部"        # 全局变量:写在文件顶层,全楼可见

def show():
    room = "301"       # 局部变量:出生在函数内,出门即消失
    print(campus, room)   # 全局能读

show()                 # 总部 301
print(campus)          # 总部
print(room)            # NameError!room 已经不存在了

规则一句话:函数内出生的只在函数内有效;全局的谁都能读。 Python 查变量按【局部 → 全局 → 内置】逐层往外找(LEGB 的简化版),就近优先。

二、函数内能读全局,但赋值=造局部

这是作用域的头号陷阱:

count = 0

def bump():
    print(count)       # 能读:0
    count = count + 1  # SyntaxError/UnboundLocalError!
                       # 赋值让 Python 把 count 判为局部,可它还没出生就要读自己

bump()

赋值语句让名字变成局部——count = count + 1 右边的 count 于是指向【还没出生的局部变量】。要真改全局必须声明 global count——但这是本节最后才推荐的招。局部优先:函数里有个 name = "x",全局的 name 就被遮住——函数内看到的一定是自己的。

三、为什么【少用全局变量】

全局变量像全楼公告栏:任何人都能改、改完所有人受影响——【谁改的】【什么时候改的】无从查起。真实理由三条:

  1. 改一处牵全身:函数 A 改了全局值,函数 B 悄悄跟着错——bug 隔空传播;
  2. 没法单测:函数行为依赖外部状态,调用两次结果都可能不同;
  3. 并发炸弹:多线程同时改一个全局值,数据说坏就坏(服务器课程的内容)。

正解=参数进、返回值出(4-4 的机器论):数据走【投料口】和【出料口】,不抄近道翻墙。

四、动手 Demo:遮蔽与改墙外

level = "全局"

def shadow():
    level = "局部"          # 遮蔽:函数内 level 是自己的
    print("函数内:", level)

shadow()
print("函数外:", level)      # 全局没被碰

counter = 0
def bump():
    global counter           # 声明后才能真改全局
    counter += 1

bump(); bump()
print("counter =", counter)  # 2

看到什么算成功:函数内: 局部、函数外: 全局、counter = 2。

读结果:第一对演示遮蔽——函数内的赋值造了一个同名局部,墙外的安然无恙;后面演示 global 声明——能改,但每写一处 global 都该问自己:这数据该不该当参数传?

五、30 秒实验

跑上面第一段【UnboundLocalError】版代码——报错信息写着 local variable referenced before assignment。30 秒记住:函数内的赋值=造局部,哪怕那行看起来像在改全局。

六、常见误区

  1. 【全局变量在任何地方都能直接改】——Python 里错。函数内直接赋值造的是局部;改全局必须 global 声明(读倒是随便读)。
  2. 【局部变量会用完内存所以要常驻】——错。局部变量随函数调用出生、返回即回收(3-16 调用栈弹栈),这恰恰是它省心之处;全局才是常驻内存的。
  3. 【用 global 很方便,多用无妨】——反了。global 是【这函数依赖并修改外部状态】的自首书;能用参数和返回值解决的,一律翻墙之外走正门。
自测三问(点开对答案) 1. **函数内 name = "x" 对全局的 name 有什么影响?** 答:没有影响——赋值造了同名的局部变量(遮蔽),全局的安然无恙。 2. **为什么函数内 count = count + 1 会报错(当 count 是全局时)?** 答:赋值让 Python 把 count 判定为局部,右边却在读这个【还没出生】的局部变量——UnboundLocalError。 3. **少用全局变量的三个理由?** 答:改一处牵全身(bug 隔空传播)、无法单测(行为依赖外部状态)、并发不安全——正解是参数进返回值出。

相关笔记