python中def是一个可执行语句,用来定义一个函数。当解释器执行def语句时,其会在内存中创建一个函数对象,除此之外,还会创建一个该函数对应的本地命名空间。这里需要注意的有两点:1 该命名空间是在执行def语句时就已经创建的,而不是在函数调用时; 2 这个命名空间中有包含哪些变量。
对于2点,取决于函数签名已经函数类型。如果函数具有默认参数,那么该命名空间会包含默认参数变量,此外,如果该函数还是一个闭包,那么该命名空间还包含了外层函数的自由变量(即在外层函数定义,但是在内层函数中引用的变量)。
由于在执行def语句时,其命名空间就已经创建,而不是在调用时创建,且后续调用时,无论多少次调用,每次调用函数生成的多个命名空间是共享这些变量(即默认参数以及自由变量)的,而不是每次调用重新定义这些变量,因为这些变量的定义在执行def语句(即定义函数时)就定义好了。因此,如果默认参数或者自由变量是可变对象,那么如果函数中有对这些对象做原地的修改,则这个修改会对所有的调用都起作用。
看下面第一个例子。下面这个例子中,默认参数a是可变对象,且函数中对a进行了原地修改,根据上述的分析,后面每次调用f1,a并不会重新定义,而是在上次调用的基础上进一步进行修改更新a的值。
def f1(a=[1]):
a.append(1)
print(a)
f1() # output --> [1,1]
f1() # output --> [1,1,1]
f1() # output --> [1,1,1,1]
再看下面的第二个例子。下面这个例子中,inner函数是一个闭包,其引用了外层函数的变量a,因此a作为自由变量会在inner函数的本地命名空间中。根据上述分析,多次调用f1,会对a的值不断更新;但是如果重新执行定义inner的def语句,就会重新创建一次inner的初始命名空间,所以其中的初始变量也会重新定义,因此调用f2不会在f1的基础上更新a的值,而是同样的在上一次f2调用的基础上更新a的值。
def outer():
a = []
def inner():
a.append(1)
print(a)
return inner
f1 = outer()
f2 = outer()
f1() # output --> [1]
f1() # output --> [1,1]
f1() # output --> [1,1,1]
f2() # output --> [1]
f2() # output --> [1,1]
f2() # output --> [1,1,1]