Python学习-名称空间、高阶函数、内置函数

记录下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__': '\n内置函数\n', '__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

posted @ 2021-08-11 21:35  斐波那切  阅读(113)  评论(0编辑  收藏  举报