记录下python中函数中名称空间、高阶函数、内置函数的内容,这一节主要是概念的东西。
命名空间
空间命名,可以参考Java中全局变量和局部变量,python中为全局命名空间和局部命名空间。 命名空间相当于不同的房间里面放了不同的东西(变量),有些公用有些私用。
py程序运行时,解释器会在内存中开辟一个空间,用于保存变量和变量值之间的对应关系,如果是函数,只是将函数名和函数的内容保存到这个名称空间,对里面的变量和逻辑并不关心。一开始函数只是加载进内存,只有当函数被调用和访问的时候,解释器才会根据函数内部声明的变量来进行内部空间的开辟,随着函数执行完毕,这些内部变量占用的空间也会随着函数执行完毕而被清空。
全局&局部&内置命名空间
全局命名空间:在py文件中,函数体外声明的变量都属于全局命名空间。
局部命名空间:当函数执行时,函数体里面会定义一些变量,这个时候就会开辟局部或者临时名称空间,将这些变量和值的对应关系保存,当函数体执行完毕,这些临时名称空间下的变量和值的关系也就消失。
内置命名空间:python解释器提供的一些内置的函数或名字,如list、str、int、print,input等,保存在这个空间,它们可以拿来直接使用,会在python解释器启动的时候加载进内存。
加载&取值顺序
加载顺序(加载到内存的顺序)内置命名空间->全局命名空间->局部命名空间(函数执行时才开辟)。
取值顺序(就近原则,也叫LEGB原则(local enclosing global builtin))局部命名空间→全局命名空间→内置命名空间 ,为单向不可逆。
# 取值顺序
name='clyang'
def print_name():
# 就近原则,print打印的是局部名称空间的name,局部如果没有就从全局找,全局没有就去内置找
# 就近原则
name='messi'
print(name) # messi
print_name()
# 取值顺序 内置名称空间最后加载
def print_name_2():
# 注释掉后,就先去全局找,全局没有,就去内置找
# input='clyang'
print(input)
print_name_2() # <built-in function input>
# 取值顺序 单向不可逆
def print_name_3():
name='messi'
# 这里是先从全局找name,不会从局部找,起点确定后不会逆向回到局部再从新找,叫做单向不可逆
print_name_3()
print(name) # clyang
作用域
作用域就是命名空间作用范围,按照生效范围分为全局作用域和局部作用域。
全局作用域:包含内置命名空间和全局命名空间,在整个文件的任意位置都可以使用。
局部作用域:包含局部命名空间,在函数内部可以使用。
局部作用域可以引用全局作用域的变量,但是不能修改,全局作用域不能引用局部作用域的变量。
date='周六'
def func():
year=2012
print(date)
func() # 周六
# 全局作用域不能引用局部作用域的变量year
print(year) # 报错 NameError: name 'year' is not defined
再看下面例子,虽然局部作用域对date重新赋值,但是这个是在局部新创建了变量并赋值,并不是修改全局作用域的date,因此执行func()函数后打印的是局部的date,后面print(date)打印的是全局的date。
date='周六'
def func():
# 注意,这不是改变,这是在局部新创建了变量并赋值
date='周日'
print(date)
func()
# 这里打印结果还是周六,说明全局作用域的变量没有改变
print(date)
打印结果。
周日
周六
再看下面例子,局部作用域不能修改全局作用域中的变量,当python解释器发现你准备对局部作用域中的某个变量count进行修改时,它会默认你已经在局部作用域定义了这个局部变量,它就会去局部作用域去找这个局部变量,执行时发现并没有局部变量count,只有全局有变量count,就报错‘local variable 'count' referenced before assignment’。
count=1
def revise():
# 报错 UnboundLocalError: local variable 'count' referenced before assignment
# 提示count这个局部变量在定义前就引用,是不允许的
count+=1
print(count)
revise()
以下也是局部作用域修改全局作用域变量的例子,当inner函数直接打印count,是打印的名称空间1的count,但是当需要修改时,这个count就是名称空间2下的局部变量,也是需要先定义。
def func():
count=1 # 局部名称空间1
def inner():
# count+=1 # 局部名称空间2
print(count)
inner()
func()
可以通过globals()和locals()函数分别查看全局及局部作用域内容,主要key-value的形式展示。
a=1
b=2
def func():
name='messi'
score=55
assist=50
print(globals())
print(locals())
func()
执行结果可以看出,a、b和func函数,都是全局作用域内容,name、score、assist都是局部作用域的内容。
{'__name__': '__main__', '__doc__': '
内置函数
', '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10fb2f470>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/yangchaolin/pythonCode/python22期/day10/04 内置函数.py', '__cached__': None, 'a': 1, 'b': 2, 'func': <function func at 0x10fae2268>}
{'name': 'messi', 'score': 55, 'assist': 50}
global nonlocal
当需要在局部命名空间修改全局命名空间的变量,可以使用global,下面例子count就在局部命名空间修改成功,从0变成1。
count=0
def func():
global count
count+=1
print(count)
func()
print(count)
执行结果
0
1
当在局部声明一个全局变量,也可以使用global,下面例子如果print(name)在func()前执行,会报错,因为还没有全局变量,但是执行func()后,会创建全局变量name,再次执行print(name)可以打印结果。另外通过使用 globals(),也可以看到name为全局变量。
def func():
global name
name='messi'
print(name)
# 1 print放在func前会报错
# print(name)
func()
# 2 print放在func后不会报错
print(name)
# 验证是否是全局,下面的方法可以打印出当前作用域的所有全局变量
print(globals())
执行结果
# 执行func结果
messi
# 打印name,局部作用域name变成全局
messi
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x1072e3470>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/yangchaolin/pythonCode/python22期/day11/02 补充的知识点(global,nonlocal).py', '__cached__': None, 'func': <function func at 0x107296268>, 'ret': ['alex', 'messi'], 'ret_2': [20], 'ret_1': [10, 100], 'ret_3': [10, 100], 'count': 0, 'name': 'messi'}
当需要在内层嵌套函数修改外层函数的变量,可以使用nonlocal,其为python3.4后更新,注意nonlocal不能操作全局变量。
def outer():
count=0
def inner():
# 这里依然不能直接修改,内层函数不能修改外层函数的局部变量,使用nonlocal可以解决
nonlocal count
count+=1
print('inner函数执行前:%d'%(count,))
inner()
print('inner函数执行后:%d'%(count,))
outer()
执行结果
inner函数执行前:0
inner函数执行后:1
高阶函数
参考文末博文,当一个函数a作为参数传给另外一个函数b,或者一个函数b的返回值为另外一个函数a(若返回值为该函数本身,则为递归),只要满足其中一个条件,b就是是高阶函数。即当一个函数参数中有函数,或函数的返回值还是函数,则这个函数为高阶函数,如map、filter和reduce都是高阶函数。
以下也是高阶函数,函数里面引用函数,函数里面定义函数。
# 函数引用函数
def func1():
print('i am func1')
print(3)
def func2():
print('i am func2')
func1()
print(4)
print(1)
func2()
print(2)
# 函数里面定义函数
def func2():
print(2)
def func3():
print(6)
print(4)
func3()
print(8)
print(3)
func2()
print(5)
函数引用函数执行结果
1
i am func2
i am func1
3
4
2
函数里面定义函数执行结果
3
2
4
6
8
5
内置函数
python内置函数,可以参考官方文档https://docs.python.org/3.7/library/functions.html?highlight=built#ascii。
相关练习
(1)看代码写结果
def func(*args,**kwargs):
print('args:',args)
print('kwargs:',kwargs)
# 请执行函数,并实现让args的值为(1,2,3,4)
func(1,2,3,4)
# 请执行函数,并实现让args的值为([1,2,3,4],[11,22,33])
func([1,2,3,4],[11,22,33])
# 请执行函数,并实现args的值为([11,22],33),并且kwargs的值为{'k1':'v1','k2':'v2'}
func([11,22],33,k1='v1',k2='v2')
# 如果执行func(*{'messi','ronald','herry'}),请问args和kwargs的值分别是多少?
func(*{'messi','ronald','herry'})
# 如果执行func({'messi','ronald','herry'},[11,22,33]),请问args和kwargs的值分别是多少?
s1={'messi','ronald','herry'}
# s1是set集合
print(s1,type(s1))
func({'messi','ronald','herry'},[11,22,33])
# 如果执行func({'messi','ronald','herry'},[11,22,33],**{'k1':'v1'}),请问args和kwargs的值分别是多少?
func('messi','ronald','herry',[11,22,33],**{'k1':'v1'})
执行结果
args: (1, 2, 3, 4)
kwargs: {}
args: ([1, 2, 3, 4], [11, 22, 33])
kwargs: {}
args: ([11, 22], 33)
kwargs: {'k1': 'v1', 'k2': 'v2'}
args: ('messi', 'herry', 'ronald')
kwargs: {}
{'messi', 'herry', 'ronald'} <class 'set'>
args: ({'messi', 'herry', 'ronald'}, [11, 22, 33])
kwargs: {}
args: ('messi', 'ronald', 'herry', [11, 22, 33])
kwargs: {'k1': 'v1'}
['messi', 'ronald', 'herry']
(2)位置参数一定要在关键字参数的前面,并且参数不能多重赋值,参考代码。
# 注意位置参数一定要在关键字参数的前面,并且参数不能多重赋值,否则报错
def func(name,age=18,email='clyang@163.com'):
print(name)
print(age)
print(email)
# 位置参数一定要在关键字参数的前面,下面的语句编译都不会通过
# func(age=20,'messi')
# 报错func() got multiple values for argument 'name' 参数多重赋值了
# func('messi','messi@163.com',name='clyang')
(3)看代码写结果
def func(users,name):
users.append(name)
return users
result=func(['messi','ronald'],'herry')
print(result)
执行结果
['messi', 'ronald', 'herry']
(4)高阶函数
v1='alex'
def func():
v1='女神'
def inner():
print(v1)
v1='男神'
inner()
# v1='男神'
func()
print(v1)
v1='老男人'
func()
print(v1)
执行结果
男神
alex
男神
老男人
(5)如果函数默认参数,指向的是可变的数据类型,无论调用多少次,这个默认参数在内存中都是同一个。
def func(name, li=[]):
li.append(name)
return li
ret = func('alex')
print(ret)
ret_2 = func('messi')
print(ret_2)
执行结果
['alex']
['alex', 'messi']
再看例子,li如果是默认参数,多次调用不传入[],使用的是同一个列表。
def func(a, li=[]):
li.append(a)
return li
# 参数传了[],就使用新的,否则使用以前的
# print(func(10,)) # [10]
# print(func(20,[])) # [20] # 参数传了就用新的列表
# print(func(100,)) #[10,100] 参数没传就用以前的列表
ret_1 = func(10, )
ret_2 = func(20, [])
ret_3 = func(100, )
# 如果先执行完,再一一打印,就是这个结果,坑太多,有啥用呢?难道我还写代码会经常想这个吗
print(ret_1) # [10,100]
print(ret_2) # [20]
print(ret_3) # [10,100]
(6)局部作用域的坑,下面代码如果count=1不注释,print(count)时会先从局部找,但是局部还未定义就会报错,把count=1注释掉,就会从全局作用域找count,不会给解释器造成困扰。
count = 0
def func():
print(count) # 报错 local variable 'count' referenced before assignment,程序走到这里就会从局部找
# 把这个注释掉,上面的错就不会报了,不会给解释器造成困扰,如果定义了解释器就蒙圈了,不知道你到底要使用局部的还是全局的
# count=1
func()
PS:以上,理解不一定正确,学习就是一个不断认识和纠错的过程,如果有误还请批评指正。
参考博文:
(1)https://www.cnblogs.com/luckinlee/p/11620074.html 名称空间
(2)https://www.cnblogs.com/littlefivebolg/articles/9094942.html 高阶函数
(3)https://zhuanlan.zhihu.com/p/93225449 常见的高阶函数
(4)https://zhuanlan.zhihu.com/p/108021527?from_voters_page=true