一、学弟的困惑

十天前一个夜阑人静、月明星稀的夜晚,我和我的朋友们正在学校东门的小餐馆里吃着方圆3里内最美味的牛蛙,唱着最好听的歌儿,畅聊人生的意义。突然,我的手机一震,气氛瞬间就安静下来,看着牛蛙碗里三双贪婪的筷子,我犹豫了:不——我的肉…但是本着不让人久等的原则,我不舍地放下了筷子。点亮屏幕,我的眉头不禁紧锁,事情好像并不简单…

什么,还上升到了去医院的程度?现在的年轻人怎么了,怎么那么不注意安全,嗨,真是一届不如一届了,不过也好,没受伤就好…正当我沉浸在我自己的瞎想时,一张图片紧接着医院那条发了过来…嗯?好熟悉的图!

嗯…,这不是PyCharm嘛…原来是Python…啊不,我的牛蛙…当我还在想这会是个啥问题时,学弟发出了追问三连:

我是谁?我从哪里来?我的牛蛙怎么没了?

右手无意思地点开了那张承载着学弟追问三连的图,我倒要看看,什么问题耽误了我吃肉的最佳时机。

忽略学弟那莫名其妙的文件命名,以及那三位数的行数,学弟的问题由六行代码引出:

  1. def li_si(a,ls=[]):  
  2.     ls.append(a)  
  3.     return ls  
  4. print(li_si(7))  
  5. print(li_si(15))  
  6. print(li_si(45,[1,5,7]))  
  7. print(li_si(78))  

    一个函数,两个参数,其中一个是默认的空列表,函数里,列表对第一个参数执行append操作,返回列表。

    四个print(),每个print()的参数是一个函数调用,第一二四个函数调用只有一个参数,第二个参数使用的默认值。

    这会有啥问题?结果是显而易见的嘛。

看来学弟进度有点慢啊。这么基础的知识,怎么会扯上这么多,什么"局部变量",什么"全局变量",还有"参数"之类,引得我嘴角上扬,感觉空气中充满了快活的空气。

我夹起了一块牛蛙肉,真香。

瞄了一眼程序的输出结果,瞳孔瞬间放大。

不好,有诈!我仿佛听到一声惊雷,右手一抖,我的牛蛙掉到了大白菜汤里,啊,牛蛙,你还是想回家啊。

哈哈,顾不得牛蛙了,看来学弟提了一个好问题,C语言里那一套规则似乎不起作用了。

放下筷子,虔诚的拿起了可以打开未知世界大门的手机,思绪进入计算机世界,这几行代码在执行时,到底发生了什么。

二、C语言里的函数调用

当编译器遇到一个函数调用时,它产生代码传递参数并调用函数。C语言里所有的参数均以"传值调用"方式传递,而对于数组参数,传递的则是常量指针(数组)的拷贝。每次函数调用时,被调用的函数都有自己独有的栈空间,里面存储了函数的参数、局部变量等信息,函数返回后,栈空间被释放。

而Python的解释器是用C写的,Python里的list底层就是C语言的可变数组,就是一个指针。

基于这种认知,我设想的运行结果应该是,第一二四个函数使用的默认参数list,每次调用时,默认参数都回有一个值,这个值是不确定的(后面会提到,在Python里,可变类型竟然还真是确定的),所以每次调用时默认参数都(应该)指向空的数组,结果应该就是返回只有a一个元素的列表。

但是现在运行结果显示,这三次函数调用时似乎指向了同一个列表,这就奇怪了。

三、我的猜想

本身应该是局部变量的参数,运行时却有了全局变量的效果(我终于还是提到了学弟问的那几个词…),看着代码,我有了这样几个猜测…

猜想1: 学弟这几行代码所在行数为106-112,有没有可能在之前的代码中,ls已经被定义过了,所以在后面的代码中,全局的ls覆盖了局部的ls,造成了这种参数全局的效果。

猜想2: 现在我也好奇当时我为嘛会想到这个…这解释器怎么可能会跨行优化这种…可能是被牛蛙冲昏了头脑。

猜想3: 这个我做过实验,对同一个函数多次调用,每次函数局部变量的地址都相同。所以我怀疑,默认参数所在内存区域的值,一直没被修改,所以每次都一样。不过这样就有了一个悖论,第三次函数调用没有使用默认的参数,内存区域的值理应被修改,但是第四次调用时又回到了前两种情况。

四、放"码"过来

回到学校后,终于有机会能实际跑跑这奇怪的代码了,毕竟脑子不能编译、解释代码,还是要上机。

首先,直接跑这7行代码,看看结果。

嗯,和学弟的结果一样,可以排除含有全局变量的情况1了。

看看每次函数调用时默认参数的值与地址。

这结果部分地验证了猜想3,每次使用默认参数时都指向了同一个地址。

换一下,默认参数改为一个数字,这不会还指同一块吧。

嗯…还指向同一块,难不成这个默认参数的值放常量池了,怎么老是指一个地儿…啊,对象,突然想起一句话,"Python里万物皆为对象",这么想来,每一个数字都有自己单独的地址了。嗯,实验一下。

果然,都是对象。面向对象的特性爬出了书本,以这样一种方式在我的面前刷了一波存在感。

因此,默认的参数ls,指向的也是同一个列表对象。而想要该变量指向新的列表的话,就得重新赋值。

重新赋值后,就得到了预期的结果。

五、可变类型与默认参数

Python的内建标准类型有一种分类标准是分为可变类型与不可变类型:

  • 可变类型:列表、字典
  • 不可变类型:数字、字符串、元组

变量保存的实际都是对象的引用,所以在给一个不可变类型(比如int)的变量a赋新值的时候,实际上是在内存中新建了一个对象,并讲a指向这个对象,然后将原对象的引用计数-1。

所以当函数参数是默认列表时,它始终指向同一个对象,除非重新赋值,否则它并不会重新创建一个新列表。也就是说,多次调用函数执行append操作,实际上是对同一个对象进行操作。

参考:Python——可变类型与不可变类型(即为什么函数默认参数要用元组而非列表)

python之函数默认参数及注意点

[Python]可变类型,默认参数与学弟的困惑的更多相关文章

  1. [python]一个关于默认参数的老问题和一个有关优化的新问题

    一个老问题: def func(defau=[]): defau.append(1) return defau print(func())#print[1] print(func())#print[1 ...

  2. Python进阶-函数默认参数

    Python进阶-函数默认参数 写在前面 如非特别说明,下文均基于Python3 一.默认参数 python为了简化函数的调用,提供了默认参数机制: def pow(x, n = 2): r = 1 ...

  3. 在python函数中默认参数的一些坑

    一.默认参数 python为了简化函数的调用,提供了默认参数机制: 这样在调用pow函数时,就可以省略最后一个参数不写: 在定义有默认参数的函数时,需要注意以下: 必选参数必须在前面,默认参数在后: ...

  4. 经典面试题-python函数之默认参数

    1.可变的默认参数----list  示例: def add(a, mylist=[]): # print(id(mylist)) mylist.append(a) return mylist pri ...

  5. Python——可变类型与不可变类型(即为什么函数默认参数要用元组而非列表)

    Python 的内建标准类型有一种分类标准是分为可变类型与不可变类型: 可变类型:列表.字典 不可变类型:数字.字符串.元组 因为变量保存的实际都是对象的引用,所以在给一个不可变类型(比如 int)的 ...

  6. Python函数的默认参数的设计【原创】

    在Python教程里,针对默认参数,给了一个“重要警告”的例子: def f(a, L=[]): L.append(a) return L print(f(1)) print(f(2)) print( ...

  7. python中的默认参数

    https://eastlakeside.gitbooks.io/interpy-zh/content/Mutation/ 看下面的代码 def add_to(num, target=[]): tar ...

  8. Python中的默认参数(转)

    add by zhj: Python设计者为何将默认参数设计成这样呢?参见Python函数参数默认值的陷阱和原理深究 原文:https://github.com/acmerfight/insight_ ...

  9. 理解python可变类型vs不可变类型,深拷贝vs浅拷贝

    核心提示: 可变类型 Vs 不可变类型 可变类型(mutable):列表,字典 不可变类型(unmutable):数字,字符串,元组 这里的可变不可变,是指内存中的那块内容(value)是否可以被改变 ...

随机推荐

  1. eclipse/sublime 等宽字体设置

    转载请注明出处:http://www.cnblogs.com/wubdut/p/4621889.html 使用ubuntu14.04会产生很多想日犬的地方.大家一般习惯于使用 eclipse 进行 j ...

  2. 3D打印切片软件介绍

    熟悉3D打印的流程的人都知道,在建立了3D模型以后要就进行切片,但是什么是切片呢?切片实际上就是讲3D模型转化为3D打印机本身可以执行的代码,G代码,M代码. 3D打印流程 今天我们简要的介绍3款切片 ...

  3. Gitlab 赋予某台机器git clone的权限 Deploy key

    开发项目CI(持续化部署)的时候,需要赋予jeckins所在的机器从gitlab远程仓库克隆代码到本地的权限. 之前我们基本都是通过管理gitlab某个项目的成员的方式,管理gitlab的权限. 但是 ...

  4. [php] php - json_encode 函数

    json_encode()函数, $arr= array("key"=>null); echo json_encode($arr);{"key":null ...

  5. 报错:Missing type map configuration or unsupported mapping

    报错:Missing type map configuration or unsupported mapping □ 背景 当把View Model转换成Domain Model保存的时候,发生在Au ...

  6. SignalR 设计理念(一)

    SignalR 设计理念(一) 实现客户端和服务器端的实时通讯. 问题阐述 客户端提供的方法不确定! 客户端的方法参数不确定! 不同的名称和参数要分别调用指定的方法! 调用客户端方法时,忽略大小写! ...

  7. python--Websocket实现, 加密 sha1,base64

    需要用到gevent-websocket包,这里我们用下图这个 一.websocket简单实现 ep1.py from geventwebsocket.handler import WebSocket ...

  8. powerDesigner 把name项添加到注释(comment)

    第一次写博客,分享一点经验吧,平时大家用powerDesigner的时候,pd是不会把name项默认添加到comment的,所以生成的数据库表里面也没有中文字段的注释. 我在网上查了一下.有解决方案了 ...

  9. vue.js 的起步

    vue.js 的起步 转载 作者:伯乐在线专栏作者 - 1000copy 点击 → 了解如何加入专栏作者 如需转载,发送「转载」二字查看说明 介绍 vue.js 是一个客户端js库,可以用来开发单页应 ...

  10. Tools - 文本编辑器Notepad++

    00 - NotePad++ 官网 01 - Notepad++修改主题 依次点击设置---语言格式设置---选择主题,在显示界面中修改相关设置(背景色.前景色.字体等). 02 - Notepad+ ...