内容概览
- 动态参数
- 形参的顺序问题
- 名称空间与作用域
- 加载顺序
- 取值顺序
- 内置函数
- 高阶函数
- 关键字 global
- 关键字 nonlocal
动态参数
- 前面文章提到,形参有三种:位置参数默认参数万能参数.其中万能参数又称动态参数,包括: args *kwargs
- 定义函数时,* 与 ** 表示函数的聚合
# 定义函数时,*/** 表示函数的聚合
def func(*args, **kwargs):
print(args) # (1, 2, 3)
print(kwargs) # {'name': 'Jane', 'age': 18}
func(1, 2, 3, name="Jane", age=18)
3. *的魔性用法
a, b, *args = [i for i in range(20)]
print(a) # 0
print(b) # 1
print(args) # [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
4. 调用函数时,* 与 ** 表示函数的打散
def func(*args):
print(args)
func(*[1, 2, 3], *(4, 5, 6), *"abc")
# 运行结果:
(1, 2, 3, 4, 5, 6, 'a', 'b', 'c')
def func(**kwargs):
print(kwargs)
func(**{"name": "Jane", "age": 16}, **{"sex": "female"})
# 运行结果:
{'name': 'Jane', 'age': 16, 'sex': 'female'}
def func(*args, **kwargs):
print(args)
print(kwargs)
func(*[1, 2, 3], *(4, 5, 6), *"abc", **{"name": "Jane", "age": 16}, **{"sex": "female"})
# 运行结果:
(1, 2, 3, 4, 5, 6, 'a', 'b', 'c')
{'name': 'Jane', 'age': 16, 'sex': 'female'}
5. 一个小细节
# 字典里的 key 不能是数字,必须是字符串
def func(*args, **kwargs):
print(args)
print(kwargs)
func(*[1, 2, 3], *(4, 5, 6), *"abc", **{1: "Jane", "age": 16})
# 运行结果:
Traceback (most recent call last):
File "test01.py", line 5, in <module>
func(*[1, 2, 3], *(4, 5, 6), *"abc", **{1: "Jane", "age": 16})
TypeError: func() keywords must be strings
形参的顺序问题
- 思考:假设有以下形参,该怎么排序才正确?
args, *kwargs
2. 位置参数一定要在默认参数的前面
def func(a, b, sex="female"):
print(a)
print(b)
print(sex)
func(1, 2, sex="male")
# 运行结果:
1
2
male
# 其实函数调用的时候,这里不用写明 sex="male",结果一样
def func(a, b, sex="female"):
print(a)
print(b)
print(sex)
func(1, 2, "male")
3. *args 要在位置参数和默认参数的中间
def func(a, b, *args, sex="female"):
print(a)
print(b)
print(args)
print(sex)
func(1, 2, "a", "b", sex="male")
# 运行结果:
1
2
('a', 'b')
male
4. **kwargs 要放在最后面
def func(a, b, *args, sex="male", **kwargs):
print(a)
print(b)
print(args)
print(sex)
print(kwargs)
func(1, 2, "a", "b", sex="female", name="Jane")
# 运行结果:
1
2
('a', 'b')
female
{'name': 'Jane'}
5. 总结
形参的排序:
位置参数 > args > 默认参数 > *kwargs
名称空间与作用域
- 名称空间
① 程序开始运行时是逐行解释的,即从上往下执行代码
② 当遇到了初始化对象命令时,在内存中存放一个变量与值的对应关系的空间,这个空间称为『全局名称空间』
③ 如果程序走到一个函数时,看到函数的定义,会把函数名与函数体的对应关系在内存中存放一个命名空间,但是,里面没有任何东西
④ 当遇到函数调用时,会在内存中再开辟一个临时的名称空间,存放的是函数体里面的变量与值.注意,该名称空间会随着函数的结束而消失
⑤ 全局名称空间:存放的是 py 文件中变量与值的对应关系
⑥ 局部名称空间:临时存放比如函数体里面的变量与值的对应关系
⑦ 内置名称空间:内置函数及关键字等等
2. 作用域
① 作用域,分为全局作用域和局部作用域
② 全局作用域:全局名称空间、内置名称空间
③ 局部作用域:局部名称空间
加载顺序
① 所谓加载顺序,即加载到内存的顺序
② 内置名称空间 > 全局名称空间 > 局部名称空间
③ 解释器加载到内存中才能写代码,这个时候内置函数已经一起加进内存里,所以它是最先加载的
④ 接下来运行程序时,肯定是先加载全局名称空间
⑤ 遇到函数之类的代码块时才加载局部名称空间
取值顺序
- 就近原则
def func():
name = "aaa"
print(name)
func() # aaa
name = "bbb"
def func():
name = "aaa"
print(name)
func() # aaa
# 上面的 print(name),其结果就是采用就近原则获取的
name = "bbb"
def func():
print(name)
func() # bbb
# 这个时候在函数内部找不到变量 name,于是在函数外部找到该变量及对应的值
2. LEGB 原则,也是就近原则
name = "bbb"
def func():
name = "aaa"
# print(name)
def inner():
name = "ccc"
print(name)
inner()
# print(name)
func()
# 这里根据就近原则得知,运行结果应该是 ccc
# 如果把 print(name) 放在 name = "aaa" 下面,则结果是:aaa
# 如果把 print(name) 放在函数外下面,则结果是:bbb
3. 总结
取值顺序:
局部名称空间 > 全局名称空间 > 内置名称空间
内置函数 globals()、locals()
① globals():返回一个字典,包含全局作用域的所有内容
② locals():也返回一个字典,包含当前作用域的内容
③ 注意:无论是加载顺序还是取值顺序,都是单向不可逆的!
name = "Jane"
age = 16
def func():
name = "John"
age = 18
print(globals())
print(locals())
# 运行结果:
{'__name__': '__main__',...
'__file__': 'test01.py', ...
'name': 'Jane', 'age': 16,
'func': <function func at 0x7fba8f9941e0>}
{'__name__': '__main__', ...
'__file__': 'test01.py',...
'name': 'Jane', 'age': 16,
'func': <function func at 0x7fba8f9941e0>}
name = "Jane"
age = 16
def func():
name = "John"
age = 18
print(globals())
print(locals())
func()
# 运行结果:
{'__name__': '__main__',...
'__file__': 'test01.py', ...
'name': 'Jane', 'age': 16,
'func': <function func at 0x7fca13c511e0>}
{'name': 'John', 'age': 18}
# 原因解析:这两个 print 函数都放在 func() 函数内了
# 所以 locals() 能把当前具体的局部作用域全部打印出来
name = "Jane"
age = 16
def func():
name = "John"
age = 18
def inner():
print(globals())
print(locals())
inner()
func()
# 运行结果:
{'__name__': '__main__',...
'__file__': 'test01.py', ...
'name': 'Jane', 'age': 16,
'func': <function func at 0x7fca13c511e0>}
{}
# 原因解析:locals() 返回的是当前作用域的内容
# 注意,所谓的『当前』,在这里就是指 inner() 函数内部
# 显然该函数内容啥也没有,因此返回的结果是一个空字典
高阶函数
永远记住一点:代码是从上往下开始执行的!
def func1():
print(111)
def func2():
print(222)
func1()
def func3():
print(333)
func(2)
print(555)
func3()
print(666)
# 运行结果:
555
333
222
111
666
# 原因解析:先执行 print(555)
# 然后执行 fun3(),先执行 print(333),再执行 func2()
# 注意:此时 func3() 并没有结束运行,而是要等 func2() 执行完后才结束
# 此时执行 func2(), 先执行 print(222), 再执行 func1(),逻辑和上面的一样
# 当执行到 print(111) 之后,func1() 执行完毕,然后 func2() 跟着执行完毕
# 再是一开始执行的函数 func3() 执行完毕
# 最后才执行 print(666)
def wrapper():
print(111)
def inner():
print(222)
def func():
print(333)
print(444)
inner()
print(555)
wrapper()
# 运行结果:
111
444
222
555
# 原因解析:还是那句话!程序是从上往下开始执行的
# 首先执行 print(111),这个无争议
# 接下来,为什么是先执行 print(444) 不是先执行 inner() 呢?
# 原因很简单,因为它没有被调用!
# 等执行完 print(444) 后,inner() 被调用,然后开始执行该函数
# 发现该函数内部还有一个函数 func(),为什么它没被执行呢?
# 还是同样的原因,它始终没有被调用!
# 最后执行 print(555)
关键字 global
- 当某个变量只存在局部名称空间时
def func():
name = "aaa"
func()
print(name)
# 运行结果
Traceback (most recent call last):
File "test01.py", line 5, in <module>
print(name)
NameError: name 'name' is not defined
# 原因解析:name 在局部名称空间内
# 因此 print(name) 时找不到 name
# 这里一定要注意与上面的就近原则的一个示例的区别:
name = "bbb"
def func():
print(name)
func()
2. 使用 global 可以在局部声明一个变量
def func():
global name
name = "aaa"
func()
print(name) # aaa
# 原因解析:这里的 global name 表示这个局部名称空间的变量 name 对应的在全局名称空间也会同步
# 即相当于 func() 外有 name = "aaa" 这个变量与值的对应关系
3. 使用 global 后,局部变量的值改变了,全局的也跟着改变
def func():
global name
name = "aaa"
name = "bbb"
func()
print(name) # bbb
# 注意函数执行结束后,全局的变量指向的值在内存中还存在
# 所以 print(name) 才会有结果
4. 使用 global 后局部变量可以操控全局的同一个变量对应的值
name = "bbb"
def func():
global name
name = "aaa"
func()
print(name) # aaa
# 注意与这里的区别
name = "ccc"
def func():
global name
name = "aaa"
func()
print(name) # aaa
name = "bbb"
print(name) # bbb
5. 局部作用域不能对全局作用域的变量进行修改,只能引用
count = 1
def func():
count += 1
func()
# 运行结果:
Traceback (most recent call last):
File "test01.py", line 5, in <module>
func()
File "test01.py", line 3, in func
count += 1
UnboundLocalError: local variable 'count' referenced before assignment
# 报错提示局部变量 count 还没声明就使用
6. 使用 global 之后,局部作用域就可以对全局作用域的变量进行修改
count = 1
def func():
global count
count += 1
print(count)
func() # 2
关键字 nonlocal
- 该关键字只存在于 Python3 版本中,是子级对父级的操控。不能操控全局变量
name = "aaa"
def func():
nonlocal name
print(name)
func()
# 运行结果:
File "test01.py", line 3
nonlocal name
^
SyntaxError: no binding for nonlocal 'name' found
2. 内层函数可以引用外层函数的变量
def wrapper():
name = "aaa"
def inner():
print(name)
inner()
wrapper() # aaa
3. 内层函数只能引用外层函数的变量,不能对其改变
def wrapper():
name = "aaa"
def inner():
name += "bbb"
inner()
wrapper()
# 运行结果:
Traceback (most recent call last):
File "test01.py", line 7, in <module>
wrapper()
File "test01.py", line 5, in wrapper
inner()
File "test01.py", line 4, in inner
name += "bbb"
UnboundLocalError: local variable 'name' referenced before assignment
4. 使用 nonlocal 之后内层函数能对外层函数的变量进行修改
def wrapper():
name = "aaa"
def inner():
nonlocal name
name += "bbb"
print(name)
inner()
wrapper() # aaabbb
5. 再次复习一遍 nonlocal 的用法,先目测下面代码执行后的结果再看解析
def wrapper():
name = "aaa"
def inner():
nonlocal name
name += "bbb"
print(name)
print("111", name)
inner()
print("222", name)
wrapper()
# 运行结果:
111 aaa
aaabbb
222 aaabbb
# 原因解析:首先 wrapper() 调用 wrapper 函数后
# 先执行 print("111", name). 此时 name 就是 name = "aaa" 对应的值,即打印结果是 111 aaa
# 然后 inner() 调用 inner 函数,内存函数的变量改变了外层函数的变量对应的值
# 此时 print(name) 时 name = "aaabbb", 即打印结果是 aaabbb
# 注意:外层函数的变量对应的值已经被内存函数的改变了
# 因此 print("222", name) 时,name = "aaabbb",故打印结果是 222 aaabbb