zoukankan      html  css  js  c++  java
  • 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__': '
    内置函数
    ', '__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

  • 相关阅读:
    android学习日记19--四大组件之BroadcastReciver(广播接收者)
    android学习日记19--四大组件之Services(服务)
    android学习日记18--Adapter简介
    android学习日记17--Gallery(画廊视图)
    android学习日记16--GridView(网格视图)
    android学习日记15--WebView(网络视图)
    android学习日记14--网络通信
    android报错及解决2--Sdcard进行文件的读写操作报的异常
    android学习日记13--数据存储之File存储
    自定义跨浏览器的事件处理程序
  • 原文地址:https://www.cnblogs.com/youngchaolin/p/15118126.html
Copyright © 2011-2022 走看看