Python 函数调用使用默认值参数 — 谈谈可变对象的坑?!【前置补充】

针对前一篇文档《Python 函数中使用默认值参数 — 谈谈可变对象的坑?!》再进行补充说明

在 python 中进行函数调用,其参数可以使用 “默认值参数” 类型,那么当为 “默认值” 参数传入一个 “对象” 的时候,有什么限制和要求么?

参数的默认值可以是:

  • 可变对象,如列表、字典、集合
  • 不可变对象,如整数、浮点数、字符串和元组

一. 参数默认值使用可变对象

1. 调用时不传实参

函数定义中,形参被赋予了一个默认值,且该默认值为一个可变对象(空白的列表)

  • id(): 该方法可以输出对象的唯一标识,用于区分是否为同一个对象
  • 连续调用该函数两次
  • 观察:函数内部对局部变量 lt 的处理
# 定义一个函数,其形参 lt 是一个 “可变对象(列表)”
def fun(lt=[]):
    print(f'函数内:lt = {lt},  指向对象:id(lt)={id(lt)}')

# 连续两次调用函数,且均不传参
fun()
fun()

# 输出为:对象id相同,证明二者为同一个对象
函数内:lt = [],  指向对象:id(lt)=2333811036544
函数内:lt = [],  指向对象:id(lt)=2333811036544

结合图形分析:

  • 第一次调用,在函数内部为局部变量(即形参) lt 创建了一个对象,编号为2333811036544
  • 第二次调用,由于不需要额外的处理活动,因此在函数内部复用了前面创建的 lt 对象,不再创建一个新的对象
    • 原因:二者id编号相同

2. 调用时传入实参

  • 函数定义中,形参被赋予了一个默认值,且该默认值为一个可变对象(列表)
  • 在函数外部定义了两个列表对象 lst01 和 lst02,调用函数时分别将这两个变量传入
  • 观察:函数内部对局部变量 lt 的处理,是新建还是复用?
# 定义一个函数,形参lt使用了 “可变对象(列表)”
def fun(lt=[]):
    print(f'函数内对象:lt = {lt},  指向对象:id(lt)={id(lt)}')


lst01 = [6, 6, 6]
print(f'函数外对象:lst01 ,指向对象:id(lst01)={id(lst01)}')
lst02 = [8, 8, 8]
print(f'函数外对象:lst02 ,指向对象:id(lst02)={id(lst02)}')

# 连续两次调用函数,且传入不同的实参
fun(lst01)
fun(lst02)

# 输出为:
函数外对象:lst01 ,指向对象:id(lst01)=2379271796992
函数外对象:lst02 ,指向对象:id(lst02)=2379271892928
函数内对象:lt = [6, 6, 6],  指向对象:id(lt)=2379271796992
函数内对象:lt = [8, 8, 8],  指向对象:id(lt)=2379271892928

结合图形分析:

  • 在函数外定义了两个列表对象 lst01 和 lst02,从输出可见二者的对象编号不相等,即不是相同的对象
  • 第一次调用,传入了 lst01 对象(的引用),其编号为2379271796992
    • 在函数中创建局部变量(即形参 lt),使其指向 lst01 变量所指向的对象空间,即这两个变量(lt 和 lst01)都指向了同一个变量空间
    • 此时通过 lt 进行修改的时候,实际和通过 lst01 进行修改的效果是相同的
  • 第二次调用与第一次调用同理,但是由于形参要接收新的传值,因此就不会复用了,而是创建一个新的局部变量(也叫 lt),使其指向 lst02 变量所指向的对象空间
  • 从程序输出结果看,两个对象的 id 不同,确实分别指向了两个不同的对象

3. 先传参调用再不传参调用

  • 先进行传参的调用,再进行不传参的调用
  • 观察:函数内部对局部变量 lt 的处理,是新建还是复用?
# 定义一个函数,形参lt使用了 “可变对象(列表)”
def fun(lt=[]):
    print(f'函数内对象:lt = {lt},  指向对象:id(lt)={id(lt)}')


lst01 = [6, 6, 6]
print(f'函数外对象:lst01 ,指向对象:id(lst01)={id(lst01)}')
lst02 = [8, 8, 8]
print(f'函数外对象:lst02 ,指向对象:id(lst02)={id(lst02)}')

# 调用:先传参
fun(lst01)
fun(lst02)

# 调用:再不传参,相对前面案例仅增加了这一行代码
fun()  

# 输出为:
函数外对象:lst01 ,指向对象:id(lst01)=2844358899968
函数外对象:lst02 ,指向对象:id(lst02)=2844358995904
函数内对象:lt = [6, 6, 6],  指向对象:id(lt)=2844358899968
函数内对象:lt = [8, 8, 8],  指向对象:id(lt)=2844358995904
函数内对象:lt = [],  指向对象:id(lt)=2844358103424    # 输出了一个新的对象地址

结合图形分析:

  • 前两次调用,都传参了,在函数内分别创建了两个局部变量(同名的 lt),且分别指向新传入的对象(引用)(2844358899968、2844358995904)
  • 第三次调用,没有传参,在函数内创建的局部变量 lt 指向了一个新的列表对象 (2844358103424)(是一个空的列表对象)

 

4. 案例分析

需求描述:定义一个函数,为传入的列表(list)尾部添加一个 “end” 元素。

如传入列表: [ 1, 2, 3, 4 ], 期望输出列表为:[ 1, 2, 3, 4, 'end' ]

4.1. 实现代码

  • 创建函数,参数使用可变对象(列表),而且设置了默认值(空列表)
  • 调用函数,传入准备好的实参(lst1)
  • 执行结果:在原列表基础上增加了 “end” 列表项,符合需求
def add_end(lt=[]):
    # 完成需求逻辑,为传入的列表增加 “end” 元素
    lt.append('end')
    return lt

lst1 = [1, 2, 3, 4]
print(add_end(lst1))

lst2 = [5, 6, 7, 8]
print(add_end(lst2))

# 结果输出:符合需求
[1, 2, 3, 4, 'end']
[5, 6, 7, 8, 'end']

4.2. 调整代码后的疑问

  • 在后面增加两次不传参的调用(由于使用了默认参数,因此调用的时候可以不传参)
  • 观察结果:不传参调用后,好像原有列表内容被 “清空”,而且最后一次向尾部添加了两个 “end” 元素。为什么???
def add_end(lt=[]):
    lt.append('end')
    return lt


lst1 = [1, 2, 3, 4]
print(add_end(lst1))

lst2 = [5, 6, 7, 8]
print(add_end(lst2))

# 增加两次不传参调用
print(add_end())
print(add_end())

# 结果输出:
[1, 2, 3, 4, 'end']
[5, 6, 7, 8, 'end']
['end']
['end', 'end']

结合图形分析:

  • 前两次传参调用,每次都创建一个新的局部变量(lt),并分别指向被传入的对象,
    • 局部变量(lt)和函数外部变量(lst01)都指向了同一个对象空间,所以通过 lt 修改后,该空间的内容变化增加了一个 “end” 元素
  • 第三次无参调用,又创建了一个新的局部变量(lt),指向了一个空白的对象空间,修改后,该空间的内容变化增加了一个 “end” 元素
  • 第四次无参调用,复用局部变量(lt),因此修改后,最终出现了两个 “end” 元素

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值