内容概览

  1. 动态参数
  2. 形参的顺序问题
  3. 名称空间与作用域
  4. 加载顺序
  5. 取值顺序
  6. 内置函数
  7. 高阶函数
  8. 关键字 global
  9. 关键字 nonlocal

动态参数

  1. 前面文章提到,形参有三种:位置参数默认参数万能参数.其中万能参数又称动态参数,包括: args *kwargs
  2. 定义函数时,* 与 ** 表示函数的聚合
# 定义函数时,*/** 表示函数的聚合
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


形参的顺序问题

  1. 思考:假设有以下形参,该怎么排序才正确?

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

名称空间与作用域

  1. 名称空间

① 程序开始运行时是逐行解释的,即从上往下执行代码
② 当遇到了初始化对象命令时,在内存中存放一个变量与值的对应关系的空间,这个空间称为『全局名称空间』
③ 如果程序走到一个函数时,看到函数的定义,会把函数名与函数体的对应关系在内存中存放一个命名空间,但是,里面没有任何东西
④ 当遇到函数调用时,会在内存中再开辟一个临时的名称空间,存放的是函数体里面的变量与值.注意,该名称空间会随着函数的结束而消失
⑤ 全局名称空间:存放的是 py 文件中变量与值的对应关系
⑥ 局部名称空间:临时存放比如函数体里面的变量与值的对应关系
⑦ 内置名称空间:内置函数及关键字等等

2. 作用域

① 作用域,分为全局作用域和局部作用域
② 全局作用域:全局名称空间、内置名称空间
③ 局部作用域:局部名称空间

加载顺序

① 所谓加载顺序,即加载到内存的顺序
② 内置名称空间 > 全局名称空间 > 局部名称空间
③ 解释器加载到内存中才能写代码,这个时候内置函数已经一起加进内存里,所以它是最先加载的
④ 接下来运行程序时,肯定是先加载全局名称空间
⑤ 遇到函数之类的代码块时才加载局部名称空间

取值顺序

  1. 就近原则
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

  1. 当某个变量只存在局部名称空间时
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

  1. 该关键字只存在于 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