首页 > 编程语言 >Python的函数的缺省值参数(空列表)之坑

Python的函数的缺省值参数(空列表)之坑

时间:2023-04-12 20:15:08浏览次数:34  
标签:调用 函数 lst Python 缺省值 对象 之坑 指向 append

From

https://www.jianshu.com/p/9f899d829562

 

def add(x,lst = []):
    if not(x in lst):
        lst.append(x)
    return lst

list1 = add(1)
print(list1)

list2 = add(2)
print(list2)

list3 = add(3,[11,12,13,14])
print(list3)

list4 = add(4)
print(list4)

运行结果如下:

[1]
[1, 2]
[11, 12, 13, 14, 3]
[1, 2, 4]

 

Python缺省值参数之坑


    最近学习编程的过程中,被一个大坑折磨了挺久。

    当时我正在编写一个函数,函数的过程的参数是一个带缺省值的列表对象,该函数需要对列表进行修改,最终函数的返回值是这个修改后的列表。整个函数可以抽象成下面这样简单的函数:

 

 

 

  修改列表函数

    可以看到append_lst( )这个函数特别简单,就是将输入变量lst的尾部添加一个1,最后将其返回。最下面两行是将调用得到的列表赋值给lst_update,然后将其打印出来。

    试着执行一下:

 

 

 


  第一次调用append_lst( )

    输出结果是一个只有一个元素的列表[1],没什么奇怪的,对吧?

    继续调用一次append_lst( )函数,也就是继续执行一次上面jupyter_notebook中的第二个单位格,得到如下结果:

 

 

 

  第二次调用append_lst( )

    嗯?为什么结果是[1,  1],第二次执行的时候,函数append_lst( ) 一字没改,调用的方式也一个字没有改,结果理应和第一次调用显示相同才对!这次的结果居然是列表[1, 1],而不是上一次调用得到的[1]!到底是什么力量在作祟,还是出现了某种神秘的意外?

    再调用一次看看吧:

 

 

 

第三次调用append_lst( )

    诶呦我的天,真是奇了怪了!第三次调用的结果比第二次还要长!同样的函数,三次调用三次结果都不同,这函数真是太不稳定了!小小的函数体中到底蕴藏了什么样的秘密?

    在向各路大神求助的过程中,这个坑,曾经坑倒无数学习Python的少年,绝对算得上Python的经典大坑。这道题甚至出现在很多人的面试题中。

 

 

 

Python面试题

破局关键:可变对象

    其中有一位大神一阵见血地指出了关键:说并不是所有带有缺省值的函数都会这么不讲道理,只有列表、集和等这样的可变对象才会出现这样的情况。这需要理解Python中的一个特性:所有的变量名都是各种对象的名字,不是对象的本身,而一个对象可以有多个变量名指向它。

    对于int,float,string, tuple, dict这些不可变的变量,我们用赋值语句对他们进行赋值看起来像是修改了他们的值,但是本质上只是创建了一个新的对象,并且将原来的变量名指向新创建的对象。真正的修改是针对list、set这些可变变量而言的,如下图:

 

 

 修改列表

    首先创建一个[1, 2, 3]列表对象,并且用my_lst1这个变量名指向它,然后用另一个变量名my_lst_2指向my_lst_1所指向的对象,这样两个变量名就都指向了同样的对象[1, 2, 3]。通过"A is B"这样的判断语句,发现两个变量确实都指向了同样的对象。然后用append( )函数修改my_lst1所指向的对象,这时候打印出my_lst1,相应的值确实发生了改变。而在此刻打印出my_lst2的值,会发现即便刚刚没有手动修改my_lst2的值,但是其结果还是跟着改变了。这个实验可以说明:列表是一个可变对象,这个对象的值修改时,所有指向这个对象的变量也会跟着修改。

根本原因

    大神说,在理解了可变变量的原理后,就可以理解一开始的问题了。再看一眼一开始的函数:

 

 

 

pend_lst( )函数

        第一次调用函数时,变量lst确实被缺省值赋予了空列表,此后,根据append( )方法,lst被修改为[1],并且作为返回值传递给了lst_update。在这个时候,lst和lst_update两个变量同时指向了相同的对象[1]。在Python中对象的作用域由变量名决定,这个时候,由于指向对象[1]的变量lst_update是全局变量, 指向[1]的另一个变量lst因此也获得了全局变量的声明周期(但不是说lst就成了全局变量,因为lst并不能在函数append_lst( )之外的地方被使用),也就是说,lst这个变量并不会像普通的局部变量一样随着函数的结束而消亡。

    这样第二次调用append_lst( )函数的时候,因为变量名lst依旧指向着[1]这个对象,所以其不在会被def append_lst(lst=[ ])语句重新赋予初始值[ ]。最后函数调用后返回的结果自然也是再对象[1]的基础上再添加,变成[1, 1]。

    第三次调用时,lst和append_lst共同指向了[1, 1],调用的结果也是再[1, 1]上再添加,变成成了[1, 1, 1]。

实验验证

    其实要验证大神的说法,也很简单,只需要在每次调用append_lst( )函数的时候用"is"方法判断变量lst和变量lst_update是否指向同样的对象就行:

 

 

 


  验证第一次调用  

 

 

验证第二次调用  

 

 

验证第三次调用

    很容易看出在第一次调用的时候,由于lst被赋予了空列表,其与lst_update指向的对象不是用一个,它们的id号也不是相同的。而在第二次和第三次调用的时候,lst和lst_update确实都指向这同样的对象,从它们的id号相同也可以看出。由此可见大神的说法确实是对的。

解决办法

    大神说对了,但是这个坑背后的深层次原因就是Python处理机制的问题了。我以为先不用太去深究背后深层次原因,但是认得这个坑,知道这个坑的解决办法还是必要的。

    下次如果遇到这样的情况:某个函数的参数是个可变对象,函数要对这个对象进行修改。可以不需要设置返回值和参数对象相同。因为可变对象在函数内进行修改,函数外指向该对象的全局变量也会得到修改。同时可以用bool值作为函数的返回类型,告诉调用处是否修改成功,如下图所示:

 

 

 

  一种代替原方案的模式

        当然在具体的情况中,也应该想想有什么更好的方式去解决,总之下次遇到可变变量作为有缺省值的函数参数这样的坑的时候,千万不能一头跳进去。

标签:调用,函数,lst,Python,缺省值,对象,之坑,指向,append
From: https://www.cnblogs.com/emanlee/p/17311058.html

相关文章

  • Python程序笔记20230304
    抛硬币实验random模块importrandomrandom.randint(a,b)返回一个随机整数N,范围是:a<=N<=brandom.choice("ilovefishc")从"ilovefishc"这个字符串中随机选出一个字符。编写一个双色球的开奖模拟程序importrandomred=random.sample(range(1,34),6)blue=r......
  • Python+selenium点击网页上指定坐标
     fromseleniumimportwebdriverfromselenium.webdriver.common.action_chainsimportActionChainsdefclick_locxy(dr,x,y,left_click=True):'''dr:浏览器x:页面x坐标y:页面y坐标left_click:True为鼠标左键点击,否则为右键点击''&#......
  • (三)python多进程multiprocessing模块的变量传递问题:父进程中的numpy.array对象隐式序列
    参考:https://docs.python.org/zh-cn/3/library/multiprocessing.htmlcloudpickle——Python分布式序列化的专用模块python多进程multiprocessing模块的变量传递问题:父进程中的numpy.array对象隐式序列化到子进程后的inplace操作的问题-Death_Knight-博客园(cnblogs.com)......
  • python之封装mysql
     数据查询类封装1.功能分析可以连接不同sql数据库查一条数据,多条数据可以获取不同格式的数据2.封装成数据库查询类封装思路:数据库查询模块有多个功能,且需要复用,所以封装成类在构造方法中创建连接创建对象方法实现各种查询#-*-coding:utf-8-*-#@Time......
  • 更新 Python 的 setuptools 包
    [......
  • python自动化之unittest
    一、官方文档:https://docs.python.org/zh-cn/3/library/unittest.html二、unittest使用unittest使用规范需要导入unittest包测试类必须继承unittest.TestCase类测试方法必须要以test开头测试用例名称不能相同,相同的情况可能出现用例遗漏执行的情况ddt使用事项见下方......
  • python关于*args所能接收的参数、关于**kwargs所接收的参数详解
    1#!/usr/bin/envpython2#-*-coding:utf8-*-3#python-day32-20170110:456#关于*args所能接收的参数78#这种接收的是位置参数,可变长9deffunc1(*args):10print(args,type(args))1112#传入位置参数可以被args所接收,以元组的形式来保存......
  • python get请求抓取网页
    importrequestsurl='https://www.douban.com/search?'#req=requests.get(url)headers={"User-Agent":"Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/94.0.4606.81Safari/537.36......
  • Python 环境迁移
    平时用python环境会装一堆依赖,也包括自己的模块,要迁移到陌生环境,得好好处理才行。下面介绍个方法,实践过还可以:总结下步骤:miniconda或conda安装一个python环境,python版本最好和后面新环境一样。condacreate-nszpython=3.6.8在该conda里面装好自己的各种依赖,并跑起来。pi......
  • linux下使用pyinstaller打包python脚本
    最近给运维写一个系统维护的python脚本,但是被告知生产环境有很多服务器没有安装python解释器,于是在网上搜索多pyinstaller解决方案,本文简单记录之。下载pyinstaller。最新的pyinstaller版本为2.0版本,官方网站http://www.pyinstaller.org。下载链接:http://jaist.dl.sourceforge......