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

函数的嵌套

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

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):\n    print(x,y)\n    fn(x=5,y=6)', 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a', 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a)', 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a)', 'def func():\n    a = 12\n    b = 20\n    print(locals())\n    print(globals())\n\nfunc()'], '_oh': {}, '_dh': ['F:\\xpccode'], 'In': ['', 'def fn(*, x,y):\n    print(x,y)\n    fn(x=5,y=6)', 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a', 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a)', 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a)', 'def func():\n    a = 12\n    b = 20\n    print(locals())\n    print(globals())\n\nfunc()'], '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\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a)', '_ii': 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a)', '_iii': 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a', '_i1': 'def fn(*, x,y):\n    print(x,y)\n    fn(x=5,y=6)', 'fn': <function fn at 0x0000000005888510>, '_i2': 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a', '_i3': 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a)', 'a': 20, 'func': <function func at 0x0000000005DB3620>, '_i4': 'a = 10\ndef func():\n    global a\n    a = 20\n\nprint(a)\nfunc()\nprint(a)', '_i5': 'def func():\n    a = 12\n    b = 20\n    print(locals())\n    print(globals())\n\nfunc()'}

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

 

 
 
 
 

posted on 2019-09-06 15:50  xpc199151  阅读(436)  评论(0编辑  收藏  举报

导航