函数的嵌套、作用域和默认值的作用域
函数的嵌套
函数嵌套就是在一个函数中定义了另外的一个函数。
def outer(): def inner(): print("inner") print("oouter") inner() outer() 结果为: oouter inner inner() 结果为: NameError: name 'inner' is not defined
由上面的例子可以看到,直接在外面调用inner函数,会抛一个异常,因为内部函数在外面不可见。这就是作用域的概念。
Python解释器在遇到函数的时候,首先是先在内存中开辟一个内存空间,这个时候只是象征性的将函数名读入内存中,表示这个函数已经存在了,也就是Python解释器遇到一个变量的时候,把变量名和值之间的对应关系记录下来。
等到函数调用的时候,这个时候解释器才会再开辟一个内存空间,用来存储这个函数里面的内容,函数中的变量会存储在这个新开辟出来的内存中,函数中的变量只在函数的内部使用,而且随着函数的执行完毕,这个内存空间中的所有内容就会被清空。
命名空间和作用域
我们给这个“存放名字与值的关系”的空间起了一个名字——叫做命名空间,代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间,在函数的运行中开辟的临时的空间叫做局部命名空间。
命名空间的本质:存放名字与值的绑定关系
命名空间一般有三种:
- 全局命名空间
- 局部命名空间
- 内置命名空间
内置命名空间也就是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这个不可变的值作为默认参数,可以说这是一种惯用法 。
函数名的本质
- 可以被引用。
-
可以被当作容器类型的元素
- 可以当作函数的参数和返回值。
#被引用 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