Python默认参数的设计陷阱

关于Python的默认参数,发现了一个很奇怪的现象,看如下代码:

def f(a=[]):
    a.append("xyz")
    return a

print(f())
print(f())

print(f([]))
print(f([]))

print("*************")

def f1(a=1):
    a=a+1
    return a

print(f1())
print(f1())

print(f1(2))
print(f1(2))

运行结果如下:

['xyz']
['xyz', 'xyz']
['xyz']
['xyz']
*************
2
2
3
3

可以发现:
函数f(a)的有参调用f([])是没有问题的,但是无参调用f()是有问题的:两次调用的结果应该都是[‘xyz’];
函数f1(a)的有参和无参调用都是正常的。


要了解这个问题的原因,首先要知道Python变量的实质:

  • 其他编程语言:申明及赋值方式
  • Python:创建及指向,类似于指针的方式。先看个简单的例子
a = 8
a = a + 1

对于传统编程语言,上面代码执行方式:
1. 内存中申明一个变量a,a指向一个内存地址如0x0001;
2. 将8存入变量a所指向的内存0x0001;
3. 执行加法操作后得到9的结果,将9这个数值存入并覆盖到a所指向的内存0x0001中。
结论:整个执行过程中,变化的是变量a所指向的内存地址上的值,而a指向的内存地址是不变的

对于Python:
1. 在内存中创建了一个8的对象,假设8的内存地址为0x0002,变量a指向0x0002,即8这个对象;
2. 执行完加法操作后,得到一个值9,也会创建一个9的新对象,内存地址假设为0x0003;
3. 变量a从指向对象8,变成指向对象9,即a指向的是0x0003。
结论:在整个执行过程中,a指向的内存地址是变化的


问题的根本原因
Python Common Gotchas中对改问题的解释如下:

Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.

Python的官方文档中也有提示:

Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:

由此可见,Python中函数默认参数值是在代码compile阶段就已经被定义,即指向一个已经定义好的对象,假设内存地址为0x0010。也就是说之后函数调用时,如果是无参调用,那么,默认参数就是那个在compile阶段就已经存在的对象的指针0x0010。下面来分析文章一开始的两个函数的无参调用:
1.函数f1(a=1),
无参调用f1()之前:a指向0x0010,0x0010中的值为1
无参调用f1():a指向2这个对象,假设2对象的地址为0x0011
无参调用f1()结束后:a重新指向0x0010
2.函数f(a=[])
数组对象的值是可变的,定义函数f(a=[]) 时,compile阶段在内存分配一个空数组,假设首地址为:0x0020
当无参调用函数f()时,虽然函数内部对数组进行了添加元素的改变,但是跟上面f1()不一样,并没有新建一个数组对象,所以参数a始终指向0x0020这个数组对象,不管调用几次都在处理这个数组,该数组就相当于一个全局数组了。类似于下面一段代码:

b=[]
def f(a=b):
    a.append("xyz")
    return a
print(f())
print(f())     

运行结果:

['xyz']
['xyz', 'xyz']

解决办法

def f(a=None):
    if a==None:
        a=[]
    a.append("xyz")
    return a

print(f())
print(f())

print(f([]))
print(f([]))

运行结果:

['xyz']
['xyz']
['xyz']
['xyz']
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值