函数的进阶

函数的嵌套

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

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

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

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

 全局变量global

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

a = 10
def func():
    global a
    a = 20

print(a)
func()
print(a)

结果为:
10
20

 

 一旦作用域中使用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

闭包

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

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

判断一个函数是不是闭包函数,可以使用方法__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 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

上面的第二个例子为什么是【1,1】,因为函数也是对象,python把函数的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期 。查看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参数的默认值 。

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

#使用影子拷贝创建一个新的对象,永远不能改变传入的参数 
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)
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

函数的销毁

全局函数销毁

重新定义同名函数 ,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

 

上一篇:自旋锁(spinLock)


下一篇:【STM32F407】第8章 ThreadX NetXDUO之TCP服务器