作用域
变量的作用域,就是程序里能用这个名字的那一片地方。到第 15 课为止,所有变量都住在同一个大空间里。有了函数就不一样了:每次调用都有自己的一块地方放变量,调用结束,这块地方也就没了。
1def light_street():2 lamps = 33 print(f"点亮了 {lamps} 盏灯")4 5light_street()6print(lamps)
点亮了 3 盏灯
Traceback (most recent call last):
File "main.py", line 6, in <module>
print(lamps)
NameError: name 'lamps' is not defined发生了什么: Python 不认识「lamps」这个名字。可能是拼错了,或者用它之前还没给它赋值。
局部变量
lamps 是在函数里建的,所以是局部变量:它属于这一次调用。出了函数,这个名字就不存在了,最后一行停在 NameError 上。参数也是局部变量。
小技巧: 上面的例子点单步看看。调用一开始,lamps 出现在函数自己的那组变量里;调用一结束,这组变量就没了,lamps 也跟着没了。
想从函数里拿出一个值,别去够它的局部变量,用 return:
1def light_street():2 lamps = 33 return lamps4 5count = light_street()6print(count)
3
每次调用都从头开始。局部变量不会记得上一次调用时的值。
全局变量
在所有函数外面、程序最外层建的变量,是全局变量。函数可以读它:
1price = 42 3def cost(count):4 return count * price5 6print(cost(3))7price = 58print(cost(3))
12 15
函数每次跑的时候才去找 price,所以看到的是最新的值。
同名的两个变量
在函数里面给一个名字赋值,总是建一个新的局部变量,哪怕外面有一个同名的全局变量。全局的那个不会被碰到。
最后一行显示什么?
1lamps = 102 3def night_shift():4 lamps = 25 print(f"里面:{lamps}")6 7night_shift()8print(f"外面:{lamps}")
函数里的 lamps = 2 建了第二个 lamps,是局部的。全局的 lamps 还是 10。
改全局变量:global
那函数想给一个全局的计数器加 1,会怎样?Python 在函数跑之前就定好了:函数里有地方给 count 赋值,所以整个函数里的 count 都是局部的。于是 count += 1 要读局部的 count 时,它还没有值,程序停在 UnboundLocalError 上:
1count = 02 3def add_lamp():4 count += 15 6add_lamp()
Traceback (most recent call last):
File "main.py", line 6, in <module>
add_lamp()
File "main.py", line 4, in add_lamp
count += 1
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value发生了什么: 程序在这里停下来了。
要改全局变量,就在函数开头用 global 说清楚:
1count = 02 3def add_lamp():4 global count5 count += 16 7add_lamp()8add_lamp()9print(count)
2
global 能用,但少用。会改全局变量的函数难读,因为随便哪里的一次调用都可能把值改掉。把值传进去、把结果 return 出来,一般更清楚。
嵌套函数和 nonlocal
函数里面还可以定义函数。里面的函数是外面函数的局部变量,它能读外面函数的变量。想改外面的变量,用 nonlocal:意思是「包着我的那个函数里的变量」。
1def light_route(streets):2 lit = 03 def light_one(street):4 nonlocal lit5 lit += 16 print(f"{lit}. {street}")7 for street in streets:8 light_one(street)9 return lit10 11print(f"一共 {light_route(['磨坊路', '集市'])} 盏")
1. 磨坊路 2. 集市 一共 2 盏
不写 nonlocal 的话,lit += 1 会让 lit 变成 light_one 的局部变量,和上面全局变量的例子一样出错。点单步:light_one 跑的时候,能同时看到它自己的变量和 light_route 的变量。
Python 去哪里找:LEGB
Python 碰到一个名字,会按顺序去四个地方找,用找到的第一个:
| 字母 | 作用域 | 里面有什么 |
|---|---|---|
| L | Local,局部 | 当前函数的变量和参数 |
| E | Enclosing,外层函数 | 包着它的那些函数的变量 |
| G | Global,全局 | 程序最外层建的变量 |
| B | Built-in,内置 | Python 一直都有的名字:print、len、sum…… |
1name = "全局"2 3def outer():4 name = "外层函数"5 def inner():6 print(name)7 print(len(name))8 inner()9 10outer()
外层函数 4
inner 自己没有 name(L),所以 Python 先在 outer 里找到了(E),轮不到全局的那个。len 前三个地方都没有,所以来自内置(B)。四个地方都找不到,就是 NameError。
别挡住内置的名字
内置是最后才找的。所以变量叫 list、sum、max,就把同名的内置函数挡住了,下一次调用它就会出错:
1list = ["铁", "金"]2print(list)3letters = list("ore")
['铁', '金']
Traceback (most recent call last):
File "main.py", line 3, in <module>
letters = list("ore")
TypeError: 'list' object is not callable发生了什么: 它不是函数,后面不能加括号去调用。
现在 list 是你的列表,列表不能调用。换个名字,比如 ores 或 items。尤其小心 list、dict、str、sum、max、min、input 和 type。
练一练
写好代码,点检查。提示随便看,实在想不出来再看答案。每道题的起始代码都有一个作用域的问题:先运行,读一读报错。
拿出来
✓最后一行报 NameError。改一改,让函数返回总数,最后一行显示它。
12
1def add_up(loads):2 total = sum(loads)3 4add_up([3, 4, 5])5print(total)
数一数调用了几次
✓ring() 每调用一次,就该给全局的 rings 加 1。让它能用。
3
1rings = 02 3def ring():4 rings += 15 6ring()7ring()8ring()9print(rings)
被挡住的内置函数
✓变量 sum 挡住了内置的 sum(),所以最后一行出错。给变量换个名字,让两行都能显示。
10 3
1loads = [3, 5, 2]2sum = 03for ore in loads:4 sum += ore5print(sum)6print(sum([1, 2]))
这一课学会了
- 函数里建的变量是局部的,只在这次调用里存在。想拿出值,用
return。 - 最外层建的全局变量,函数里可以读。
- 在函数里给一个名字赋值,它就是局部的,哪怕有同名的全局变量;赋值之前去读,就是
UnboundLocalError。 global 名字让函数能改全局变量;nonlocal 名字让嵌套函数能改外层函数的变量。- Python 按「局部、外层函数、全局、内置」的顺序找名字。别给变量起
list、sum、max这种名字。