• 函数的嵌套、作用域和默认值的作用域


    函数的嵌套

    函数嵌套就是在一个函数中定义了另外的一个函数。

    def outer():
        def inner():
            print("inner")
        print("oouter")
        inner()
    
    outer()
    
    结果为:
    oouter
    inner
    
    inner()
    
    结果为:
    
    NameError: name 'inner' is not defined

    由上面的例子可以看到,直接在外面调用inner函数,会抛一个异常,因为内部函数在外面不可见。这就是作用域的概念。

    Python解释器在遇到函数的时候,首先是先在内存中开辟一个内存空间,这个时候只是象征性的将函数名读入内存中,表示这个函数已经存在了,也就是Python解释器遇到一个变量的时候,把变量名和值之间的对应关系记录下来。

    等到函数调用的时候,这个时候解释器才会再开辟一个内存空间,用来存储这个函数里面的内容,函数中的变量会存储在这个新开辟出来的内存中,函数中的变量只在函数的内部使用,而且随着函数的执行完毕,这个内存空间中的所有内容就会被清空。

    命名空间和作用域

    我们给这个“存放名字与值的关系”的空间起了一个名字——叫做命名空间,代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间,在函数的运行中开辟的临时的空间叫做局部命名空间。

    命名空间的本质:存放名字与值的绑定关系

     命名空间一般有三种:

    1. 全局命名空间
    2. 局部命名空间
    3. 内置命名空间

    内置命名空间也就是Python为我们提供的名字,诸如print,str等。

    三种命名空间的加载顺序为:内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)

    而取值顺序为:局部命名空间->全局命名空间->内置命名空间

    x = 1
    def f(x):
        print(x)
    
    f(10)
    print(x)
    
    结果为:
    10
    1

    作用域

    作用域就是一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域。作用域分为全局作用域和局部作用域。

    全局作用域包括内置名称空间和全局名称空间,它再整个文件的任意位置都能被引用,全局有效。 
    而局部作用域包含局部名称空间,它只在局部范围内有效。

    x = 5
    def foo():
        print(x)
    foo()
    
    5
    
    x = 5
    def foo():
        x+=1
        print(x)
    foo()
    
    UnboundLocalError: local variable 'x' referenced before assignment

    全局作用域:它在整个程序运行环境中都可见。

    局部作用域则只是在函数、类等内部可见,局部变量使用范围不能超过其所在的局部作用域

    def outer1():
        g = 65
        def inner():
            print("inner{}".format(g))
            print(chr(g))
        print("outer{}".format(g))
        inner()
    outer1()
    
    结果为:
    outer65
    inner65
    A
    
    def outer2():
        k = 65
        def inner():
            k = 97
            print("inner{}".format(k))
            print(chr(k))
        print("outer{}".format(k))
        inner()
    outer2()
    
    outer65
    inner97
    a

    上面的例子可以发现外层作用域在内层可见,但是内层作用域中,如果定义了一个相同的变量,相当于当前作用域中重新定义了一个新的变量,但是这个o并没有覆盖外层作用域outer中的变量。

    x = 5
    def foo():
        y = x + 1 # 报错吗
        #x += 1 # 报错,报什么错?为什么?换成x=1还有错吗?
        print(x) # 为什么它不报错
    foo()
    
    结果为:
    5
    
    x = 5
    def foo():
        #y = x + 1 # 报错吗
        x += 1 # 报错,报什么错?为什么?换成x=1还有错吗?
        print(x) # 为什么它不报错
    foo()
    
    结果为:
    
    ---------------------------------------------------------------------------
    UnboundLocalError                         Traceback (most recent call last)
    <ipython-input-18-bc16fbefdaf2> in <module>
          4     x += 1 # 报错,报什么错?为什么?换成x=1还有错吗?
          5     print(x) # 为什么它不报错
    ----> 6 foo()
    
    <ipython-input-18-bc16fbefdaf2> in foo()
          2 def foo():
          3     #y = x + 1 # 报错吗
    ----> 4     x += 1 # 报错,报什么错?为什么?换成x=1还有错吗?
          5     print(x) # 为什么它不报错
          6 foo()
    
    UnboundLocalError: local variable 'x' referenced before assignment

    x += 1 其实是 x = x + 1,相当于在foo内部定义了一个局部变量X,这个时候foo内部所有x都是这个局部变量x了,但是这个时候x还没有完成赋值,就被拿到右边做+1操作了,所以会报错。

     全局变量global

    使用global关键字的变量,将函数内的变量声明为使用外部的全局作用域中定义的变量。

    a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)
    
    结果为:
    10
    20

     使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x,在全局作用域中必须有a的定义,要是没有会报错。

    del z
    def foo():
        global z
        z+=2
        print(z)
    foo()
    
    结果为:
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-23-b1d30ebe079c> in <module>
          4     z+=2
          5     print(z)
    ----> 6 foo()
    
    <ipython-input-23-b1d30ebe079c> in foo()
          2 def foo():
          3     global z
    ----> 4     z+=2
          5     print(z)
          6 foo()
    
    NameError: name 'z' is not defined
    #x = 5
    def foo():
        global x
        x = 10
        x += 1 # 报错吗?
        print(x) # 打印什么?
    foo()
    print(x)
    
    结果为:
    11
    11

    上面的这个例子使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x,但是,x = 10 赋值即定义,在内部作用域为一个外部作用域的变量x赋值,不是在内部作用域定义一个新变量,所以x+=1不会报错。注意,这里x的作用域还是全局的。
    总结:

    在函数中,如果x+=1这种特殊形式产生的错误,都是因为先引用后赋值,而Python动态语言是赋值才算定义,才能被引用。所以解决办法,是在这条语句前面增加x=0之类的赋值语句,或者使用global告诉内部作用域,去全局作用域查找变量定义。

    另外,内部作用域使用x=5之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用global声明x为全局的,那么x = 5相当于在为全局作用域的变量x赋值。 

    一旦作用域中使用global声明变量为全局的,那么这个变量相当于在为全局作用域的变量赋值。函数的目的是为了封装,所以应尽量不使用global。就算要用外部变量,也最好使用形参传参来解决。

    globals()和locals() 

    def func():
        a = 12
        b = 20
        print(locals())
        print(globals())
    
    func()
    
    
    结果为:
    
    {'a': 12, 'b': 20}
    {'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', 'def fn(*, x,y):
        print(x,y)
        fn(x=5,y=6)', 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a', 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)', 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)', 'def func():
        a = 12
        b = 20
        print(locals())
        print(globals())
    
    func()'], '_oh': {}, '_dh': ['F:\xpccode'], 'In': ['', 'def fn(*, x,y):
        print(x,y)
        fn(x=5,y=6)', 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a', 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)', 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)', 'def func():
        a = 12
        b = 20
        print(locals())
        print(globals())
    
    func()'], 'Out': {}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x00000000038F0B38>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x00000000058B7A90>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x00000000058B7A90>, '_': '', '__': '', '___': '', '_i': 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)', '_ii': 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)', '_iii': 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a', '_i1': 'def fn(*, x,y):
        print(x,y)
        fn(x=5,y=6)', 'fn': <function fn at 0x0000000005888510>, '_i2': 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a', '_i3': 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)', 'a': 20, 'func': <function func at 0x0000000005DB3620>, '_i4': 'a = 10
    def func():
        global a
        a = 20
    
    print(a)
    func()
    print(a)', '_i5': 'def func():
        a = 12
        b = 20
        print(locals())
        print(globals())
    
    func()'}

    nonlocal关键字


    使用nonlocal关键字,将变量标记为不在本地作用域定义,而在上级的某一级局部作用域中定义,但不能是全局作用域中定义。

    def counter():#这也是一个闭包
        count = 0
        def inc():
            nonlocal count
            count +=1
            return count
        return inc
    foo = counter()
    print(foo())
    print(foo())
    print(foo())
    
    结果为:
    1
    2
    3
    
    a = 50
    def counter():
        nonlocal a
        a+=1
        print(a)
        count = 0
        def inc():
            nonlocal count
            count+=1
            return count
        return inc
    foo = counter()
    print(foo())
    print(foo())
    
    结果为:
    SyntaxError: no binding for nonlocal 'a' found

    上面的第一个例子中,count是外层函数的局部变量,被内部函数引用,内部函数使用nonlocal关键字声明count变量在上级作用域而非本地作用域中定义。

    闭包

    自由变量:未在本地作用域中定义的变量。例如定义在内存函数外的外层函数的作用域中的变量

    闭包是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,而非全局作用域名字的引用。

    def counter():
        c = [0]
        def inc():
            c[0]+=1
            return c[0]
        return inc
    foo = counter()
    print(foo(),foo())
    c = 100
    print(foo())
    
    结果为
    1 2 
    3

    上面的代码,c已经在counter函数中定义过了,而且Inc中的使用方式是为c的元素修改值,而不是重新定义变量。第9行的c和counter中的c不一样,而inc引用的是自由变量正式counter的变量c在Python3中,还可以使用nonlocal关键字。

    判断一个函数是不是闭包函数,可以使用方法__closure__。

    #输出的__closure__有cell元素 :是闭包函数
    def func():
        name = 'eva'
        def inner():
            print(name)
        print(inner.__closure__)
        return inner
    
    f = func()
    f()
    
    结果为:
    
    (<cell at 0x0000000005DC3408: str object at 0x0000000005DA1DF8>,)
    
    eva
    
    #输出的__closure__为None :不是闭包函数
    name = 'egon'
    def func2():
        def inner():
            print(name)
        print(inner.__closure__)
        return inner
    
    f2 = func2()
    f2()
    
    结果为:
    
    None
    egon
    def counter():
        count = 0
        def inc():
            count+=1
            return count
        return inc
    foo = counter()
    foo()
    foo() 
    
    结果为:
    ---------------------------------------------------------------------------
    UnboundLocalError                         Traceback (most recent call last)
    <ipython-input-36-aea1f6d70f76> in <module>
          6     return inc
          7 foo = counter()
    ----> 8 foo()
          9 foo()
         10 
    
    <ipython-input-36-aea1f6d70f76> in inc()
          2     count = 0
          3     def inc():
    ----> 4         count+=1
          5         return count
          6     return inc
    
    UnboundLocalError: local variable 'count' referenced before assignment

    上面这个问题可以使用global解决,但是这使用的是全局变量,就不是闭包了,要是闭包,可以使用nonlocal关键字。

    默认值的作用域

    def foo(xyz = 1):
        print(xyz)
    print(foo())
    print(foo())
    print(xyz)
    
    结果为:
    1
    None
    1
    None
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-26-80bc3f8dd90b> in <module>
          3 print(foo())
          4 print(foo())
    ----> 5 print(xyz)
    
    NameError: name 'xyz' is not defined
    
    
    def foo(xyz=[]):
        xyz.append(1)
        print(xyz)
    
    foo()
    foo()
    print(xyz)
    
    结果为:
    [1]
    [1,1]
    NameError: name 'xyz' is not defined#当前作用域没有xyz变量

    上面的第二个例子为什么是【1,1】,函数也是对象,python把函数的默认值放在了属性中,而xyz用完就消亡了。这个属性就伴随着这个函数对象的整个生命周期 。查看foo.__defaults__属性。


    def foo(xyz=[], u='abc', z=123):
        xyz.append(1)
        return xyz
    print(foo(), id(foo))
    print(foo.__defaults__)
    print(foo(), id(foo))
    print(foo.__defaults__)
    
    结果为 :
    [1] 98987816
    ([1], 'abc', 123)
    [1, 1] 98987816
    ([1, 1], 'abc', 123)

    上面的例子中,函数地址并没有变,就是说函数这个对象的没有变,调用它,它的属性__defaults__中使用元组保存默认值 xyz默认值是引用类型,引用类型的元素变动,并不是元组的变化

    def foo(w, u='abc', z=123):
        u = 'xyz'
        z = 789
        print(w, u, z)
    print(foo.__defaults__)
    foo('magedu')
    print(foo.__defaults__)
    
    结果为:
    ('abc', 123)
    magedu xyz 789
    ('abc', 123)

    属性__defaults__中使用元组保存所有位置参数默认值,它不会因为在函数体内使用了它而发生改变 。

    def fo(w, u='abc', *, z=123, zz=[456]):
        u = 'xyz'
        z = 789
        zz.append(1)
        print(w, u, z, zz)
    
    print(fo.__defaults__)
    fo('magedu')
    print(fo.__kwdefaults__)
    
    ('abc',)
    magedu xyz 789 [456, 1]
    {'z': 123, 'zz': [456, 1]}


    属性__defaults__中使用元组保存所有位置参数默认值 ,属性__kwdefaults__中使用字典保存所有keyword-only参数的默认值 。

    使用可变类型作为默认值,就可能修改这个默认值,有时候这个特性是好的,有的时候这种特性是不好的,有副作用。可以使用以下两种方法按需改变。

    #使用影子拷贝创建一个新的对象,永远不能改变传入的参数
    #函数体内,不改变默认值,xyz都是传入参数或者默认参数的副本,如果就想修改原参数,是无能为力的。
    def
    foo(xyz=[], u='abc', z=123): xyz = xyz[:] # 影子拷贝 xyz.append(1) print(xyz) foo() print(foo.__defaults__) foo() print(foo.__defaults__) foo([10]) print(foo.__defaults__) foo([10,5]) print(foo.__defaults__) 结果为: [1] ([], 'abc', 123) [1] ([], 'abc', 123) [10, 1] ([], 'abc', 123) [10, 5, 1] ([], 'abc', 123)
    #使用不可变类型默认值,如果使用缺省值none就创建一个列表,如果传入一个列表,就修改这个列表。
    def
    foo(xyz=None, u='abc', z=123): if xyz is None: xyz = [] xyz.append(1) print(xyz) foo() print(foo.__defaults__) foo() print(foo.__defaults__) foo([10]) print(foo.__defaults__) foo([10,5]) print(foo.__defaults__) [1] (None, 'abc', 123) [1] (None, 'abc', 123) [10, 1] (None, 'abc', 123) [10, 5, 1] (None, 'abc', 123)

    第一种方法使用影子拷贝创建一个新的对象,永远不能改变传入的参数。

    而第二种方法,通过值的判断就可以灵活的选择创建或者修改传入对象,这种方式灵活,应用广泛 ,很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用法 。

    函数名的本质

    1. 可以被引用。
    2. 可以被当作容器类型的元素

    3. 可以当作函数的参数和返回值。
    #被引用
    def func():
        print('in func')
    
    f = func
    print(f)
    
    结果为:
    <function func at 0x00000000062DEF28>
    
    
    #被当做容器类型的元素
    def f1():
        print('f1')
    
    
    def f2():
        print('f2')
    
    
    def f3():
        print('f3')
    
    l = [f1,f2,f3]
    d = {'f1':f1,'f2':f2,'f3':f3}
    #调用
    l[0]()
    d['f2']()
    
    结果为:
    
    f1
    f2

    变量名解析原则legb

    Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡.

    Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间.

    Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡

    Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如 print(open),print和open都是内置的变量.
    所以一个名词的查找顺序就是LEGB

    函数的销毁

    全局函数销毁

    重新定义同名函数 ,del 语句删除函数对象 ,程序结束时。

    def foo(xyz=[], u='abc', z=123):
        xyz.append(1)
        return xyz
    
    print(foo(), id(foo), foo.__defaults__)
    
    def foo(xyz=[], u='abc', z=123):
        xyz.append(1)
        return xyz
    
    print(foo(), id(foo), foo.__defaults__)
    del foo
    print(foo(), id(foo), foo.__defaults__)
    
    
    结果为:
    [1] 103673096 ([1], 'abc', 123)
    [1] 103671872 ([1], 'abc', 123)
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-58-4163eb30b66f> in <module>
         10 print(foo(), id(foo), foo.__defaults__)
         11 del foo
    ---> 12 print(foo(), id(foo), foo.__defaults__)
    
    NameError: name 'foo' is not defined

    局部函数销毁

    重新在上级作用域定义同名函数,el 语句删除函数名称,函数对象的引用计数减1 ,上级作用域销毁时。

    def foo(xyz=[], u='abc', z=123):
        xyz.append(1)
        def inner(a=10):
            pass
        print(inner)
        def inner(a=100):
            print(xyz)
        print(inner)
        return inner
    bar = foo()
    print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)
    del bar
    print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)
    
    结果为:
    
    <function foo.<locals>.inner at 0x00000000062DEBF8>
    <function foo.<locals>.inner at 0x00000000062DED08>
    103671872 103673096 ([1], 'abc', 123) (100,)
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-60-ad4eb89d71c6> in <module>
         11 print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)
         12 del bar
    ---> 13 print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)
    
    NameError: name 'bar' is not defined
     
     
     
     
  • 相关阅读:
    SEO值得学习建议
    ClientValidationFunction
    readystate的五种状态
    XMLHTTP对象参考
    Provider详解
    有缺点,向左走向右走
    DotNetBar 6.6.0.4 for Vs2005 (+特殊补丁)
    [无敌]一些web开发中常用的、做成cs文件的js代码 转帖来的
    AjaxPro.NET框架生成高效率的Tree(Asp.net 2.0)(示例代码下载)
    Vista 下使用Visual Studio 2005 开发Oracle 方面程序出现的数据连结问题及解决方案
  • 原文地址:https://www.cnblogs.com/xpc51/p/11470318.html
Copyright © 2020-2023  润新知