python函数的可变默认参数和不可变默认参数(现象及原因分析)

铺垫

因为在python中,定义变量并不是将一直值赋值给这个变量,而是创建一个对象,然后将这个变量指向该对象。
例如:
a = 123
实际执行步骤:
在内存中创建对象123,内存地址为0xAABBCCDD
将变量a指向内存地址0xAABBCCDD
所以,如果这时你打印id(a)和id(123),值都是0xAABBCCDD
不只是数字、字符串,列表、字典等可变对象也一样。

如果是不可变对象(数字,字符串等),对变量的修改,是修改该变量的指向。
如果是可变的对象(列表,字典等),如果是在原对象上面修改(如list.append,dict.update),那么就是修改该对象指向的内存,该变量的id不变;如果是重新赋值,那么就是修改该对象的指向。

晋级

现在引入另一个常见问题,函数的默认参数。(可变默认参数 和 不可变默认参数的现象及原因)

不可变参数

例如:
def func(a = 123):
print(id(a), a)
func是个函数,它也是一个对象。
在执行到def时,该对象已经创建。它和class一样,也有自己的一些参数。
对象func中,变量a的id是0x0000AAAA,值是123。变量a的指向已经确定,每次执行该函数时,变量a初始指向的地址不变。
func()
执行上面这个命令时,变量a的id是0x0000AAAA,值是123。
func(456)
执行上面这个命令时,变量a的初始id是0x0000AAAA,然后重新指向对象456的id(0x00000456)。值是456。
func()
指向上面的命令,变量a的id又变成了0x0000AAAA,值是123。

可变参数

如果默认参数是可变对象
例如:
def func2(a = []):
a.append(‘abcd’)
print(a)
函数对象func2已经定义完了,该函数对象可以用id(func2)查看该对象的内存地址,同样,你也可以用func2_tmp = func2进行指定别名。
函数对象func2有一个参数,是变量a。变量a的id是0x0000ABCD,是一个列表。变量a的指向已经确定,每次执行该函数时,变量a初始指向的地址不变。
func2()
输出[‘abcd’]
执行上面的这个命令,对内存地址0x0000ABCD进行修改。
func2()
输出[‘abcd’, ‘abcd’]
执行上面的这个命令,对内存地址0x0000ABCD再次进行修改,所以输出[‘abcd’, ‘abcd’]
tmp_var = [‘1234’]
func2(tmp_var)
输出[‘1234’, ‘abcd’]
执行上面的这个命令,变量tmp_var指向0x00001234,修改对象func2的属性变量a,指向0x00001234,
然后在内存0x00001234追加’abcd’,所以现在内存0x00001234的值是[‘1234’, ‘abcd’]。
然后你再打印tmp_var,发现也变成了[‘1234’, ‘abcd’]。

扩展

在类中也会有这种问题

class Dog:

    tricks = []             # mistaken use of a class variable

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

知识点

定义变量是修改变量的指向。修改变量的值,有时是修改指向,有时是修改原指向内存的值。
函数在定义的地方就已经创建完对象了,所以函数参数的默认值只会执行一遍。
如果函数的形参是可变对象,那么修改函数的形参,也会同时修改实参。

你可能感兴趣的:(python)