函数定义时的默认参数值

函数定义时的默认参数值

在Python官方文档的教程 4.8.1 中有以下内容:

i = 5

def f(arg=i):
    print(arg)

i = 6
f()

The default values are evaluated at the point of function definition in the defining scope, so that the codes above will print 5.
原文地址:
https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions

这里看得有点懵,丢给 chatgpt 给我解释:
在 Python 中,函数定义时的默认参数值是在函数定义时就被计算和存储的,而不是在函数调用时才计算。因此,如果默认参数值依赖于某个变量的值,那么这个变量的值是在函数定义时确定的,即使在之后这个变量的值发生了变化,函数调用时默认参数值依然是函数定义时的值。

然后它又自作主张给我扩展,输出了以下两段代码:

# codes 1
def append_to_list(value, lst=[]):
    lst.append(value)
    return lst

list1 = append_to_list(1)
list2 = append_to_list(2)

print(list1)  # 输出: [1, 2]
print(list2)  # 输出: [1, 2]

在这个例子中,lst 的默认值是一个空列表[]。但因为默认参数是在定义时计算的,所以所有调用都会共享同一个列表。要避免这种情况,可以使用 None 作为默认值,并在函数内部进行处理:

# codes 2
def append_to_list(value, lst=None):
    if lst is None:
        lst = []
    lst.append(value)
    return lst

list1 = append_to_list(1)
list2 = append_to_list(2)

print(list1)  # 输出: [1]
print(list2)  # 输出: [2]

很好,我更懵了。

最后我理解啦:
# codes 1: 共享默认参数值
def append_to_list(value, lst=[]):
    lst.append(value)
    return lst

list1 = append_to_list(1)
list2 = append_to_list(2)

print(list1)  # 输出: [1, 2]
print(list2)  # 输出: [1, 2]

第一次调用:函数的参数 lst 的默认值空列表 ‘[]’ 中增加了 1,list1 指向这个原本是空列表的列表 ‘[1]’ 。
第二次调用:在这个列表 ‘[1]’ 中增加了 2,然后 list2 指向这个刚刚还是 ‘[1]’ 的列表 ‘[1, 2]’ ,同时 list1 仍指向这个列表,此时这个列表已经变成 ‘[1, 2]’ 。
所以,两次调用结束后,list1 和 list2 都指向 ‘[1, 2]’ ,打印输出结果都是 ‘[1, 2]’ 。

每次调用这个函数时,如果没有提供 lst 参数,函数会使用同一个默认的空列表 ‘[]’ 。
每次调用这个函数时,都会在这个同一个默认列表上进行修改(在这个列表上添加新的元素),所以多次调用函数得到的多个列表是相等的(都包含所有添加的元素),因为它们都指向同一个列表,这个列表就是函数定义时创建的默认空列表。

为了避免这种情况,可以使用第二段代码中的方法:

# codes 2 独立默认参数值
def append_to_list(value, lst=None):
    if lst is None:
        lst = []
    lst.append(value)
    return lst

list1 = append_to_list(1)
list2 = append_to_list(2)

print(list1)  # 输出: [1]
print(list2)  # 输出: [2]

在第二段代码的方法中,
每次调用这个函数时,如果没有提供 lst 参数,函数会创建一个新的空列表 lst = [] 。
这样就保证了每次调用函数时,lst 都是一个新的列表,避免了多个列表指向同一个默认列表的问题。

Ok,到此为止我能够区分共享默认参数值独立默认参数值了。

那么,新的问题是:
Q: 第一次调用函数后增加 1 得到 list1 = [1] , 第二次调用函数后增加 2 得到 list2 = [1, 2] 同时 list1 此时仍然等于 [1] ,这样该怎么实现?
A: 确保每次调用函数时都在一个新的列表上进行操作。可以通过在每次调用时显式传递列表来实现这一点:

# codes 3
def append_to_list(value, lst=None):
    if lst is None:
        lst = []
    lst.append(value)
    return lst
    
list1 = append_to_list(1)  # list1 = [1]
list2 = append_to_list(2, list1.copy())  # list2 = [1, 2]

print(list1)  # 输出: [1]
print(list2)  # 输出: [1, 2]

第三段代码和第二段代码中,函数的定义完全一致。区别仅在于第二次调用。

在第三段代码中,
第二次调用这个函数时,提供了 lst 参数,函数不再创建新的空列表,而是直接在提供的 lst 参数( list1 的一个副本 ‘[1]’ )中添加新的元素,但 list1 本身不受影响。

这里再给一段写完这篇自己再跑了一趟的代码,注意注释内容:

# codes 4
def append_to_list(value, lst=[]):
    lst.append(value)
    return lst

print(append_to_list(1))  # 输出:[1],此时 lst = [1]
print(append_to_list(2))  # 输出:[1, 2],此时 lst = [1, 2]

list1 = append_to_list(3)  # 此时 lst = [1, 2, 3]
print(append_to_list(3))  # 输出:[1, 2, 3, 3],此时 lst = [1, 2, 3, 3]
print(list1)  # 输出:[1, 2, 3, 3]
# 注意以上三行和以下三行的区别
list2 = append_to_list(4)
print(list2)  # 输出:[1, 2, 3, 3, 4],此时 lst = [1, 2, 3, 3, 4]
print(list1)  # 输出:[1, 2, 3, 3, 4],list1 和 list2都指向 lst
补充理解:

“指向”
一个变量“指向”某个对象时,意思是这个变量包含了一个对该对象的引用或内存地址。
在 Python 中,列表、字典等可变对象是通过引用传递的,因此两个变量可以“指向”同一个对象。
例如,在前文的代码中,list1 = lst 和 list2 = lst 都会让 list1 和 list2 指向同一个列表对象 lst。
“显示传递”
指的是在调用函数时,明确地传递参数,而不是依赖函数内部的默认值或隐式行为。
如果没有显示传递副本,函数内部的默认值(例如一个空列表)会被多次使用和修改。这可能导致意外的共享和修改,从而影响到所有引用了这个列表的地方。
通过明确传递副本,你可以确保每次函数调用都是独立的,不会干扰其他变量的状态。

30/07/2024

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值