[Python]可变类型,默认参数与学弟的困惑
一、学弟的困惑
十天前一个夜阑人静、月明星稀的夜晚,我和我的朋友们正在学校东门的小餐馆里吃着方圆3里内最美味的牛蛙,唱着最好听的歌儿,畅聊人生的意义。突然,我的手机一震,气氛瞬间就安静下来,看着牛蛙碗里三双贪婪的筷子,我犹豫了:不——我的肉…但是本着不让人久等的原则,我不舍地放下了筷子。点亮屏幕,我的眉头不禁紧锁,事情好像并不简单…
什么,还上升到了去医院的程度?现在的年轻人怎么了,怎么那么不注意安全,嗨,真是一届不如一届了,不过也好,没受伤就好…正当我沉浸在我自己的瞎想时,一张图片紧接着医院那条发了过来…嗯?好熟悉的图!
嗯…,这不是PyCharm嘛…原来是Python…啊不,我的牛蛙…当我还在想这会是个啥问题时,学弟发出了追问三连:
我是谁?我从哪里来?我的牛蛙怎么没了?
右手无意思地点开了那张承载着学弟追问三连的图,我倒要看看,什么问题耽误了我吃肉的最佳时机。
忽略学弟那莫名其妙的文件命名,以及那三位数的行数,学弟的问题由六行代码引出:
- def li_si(a,ls=[]):
- ls.append(a)
- return ls
- print(li_si(7))
- print(li_si(15))
- print(li_si(45,[1,5,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]一个关于默认参数的老问题和一个有关优化的新问题
一个老问题: def func(defau=[]): defau.append(1) return defau print(func())#print[1] print(func())#print[1 ...
- Python进阶-函数默认参数
Python进阶-函数默认参数 写在前面 如非特别说明,下文均基于Python3 一.默认参数 python为了简化函数的调用,提供了默认参数机制: def pow(x, n = 2): r = 1 ...
- 在python函数中默认参数的一些坑
一.默认参数 python为了简化函数的调用,提供了默认参数机制: 这样在调用pow函数时,就可以省略最后一个参数不写: 在定义有默认参数的函数时,需要注意以下: 必选参数必须在前面,默认参数在后: ...
- 经典面试题-python函数之默认参数
1.可变的默认参数----list 示例: def add(a, mylist=[]): # print(id(mylist)) mylist.append(a) return mylist pri ...
- Python——可变类型与不可变类型(即为什么函数默认参数要用元组而非列表)
Python 的内建标准类型有一种分类标准是分为可变类型与不可变类型: 可变类型:列表.字典 不可变类型:数字.字符串.元组 因为变量保存的实际都是对象的引用,所以在给一个不可变类型(比如 int)的 ...
- Python函数的默认参数的设计【原创】
在Python教程里,针对默认参数,给了一个“重要警告”的例子: def f(a, L=[]): L.append(a) return L print(f(1)) print(f(2)) print( ...
- python中的默认参数
https://eastlakeside.gitbooks.io/interpy-zh/content/Mutation/ 看下面的代码 def add_to(num, target=[]): tar ...
- Python中的默认参数(转)
add by zhj: Python设计者为何将默认参数设计成这样呢?参见Python函数参数默认值的陷阱和原理深究 原文:https://github.com/acmerfight/insight_ ...
- 理解python可变类型vs不可变类型,深拷贝vs浅拷贝
核心提示: 可变类型 Vs 不可变类型 可变类型(mutable):列表,字典 不可变类型(unmutable):数字,字符串,元组 这里的可变不可变,是指内存中的那块内容(value)是否可以被改变 ...
随机推荐
- 中国移动物联网平台数据转发 c# 控制台程序
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.N ...
- POJ2456--Aggressive cows(Binary Search) unsolved
Description Farmer John has built a new long barn, with N (2 <= N <= 100,000) stalls. The stal ...
- Hdu1796 How many integers can you find 2017-06-27 15:54 25人阅读 评论(0) 收藏
How many integers can you find Time Limit : 12000/5000ms (Java/Other) Memory Limit : 65536/32768K ...
- Maven提高篇系列之五——处理依赖冲突
个人分类: Maven 不知道你在使用Maven时是否遇到过诸如"NoSuchMethodError"或"ClassNotFoundException"之类的问 ...
- NameNode工作机制
NameNode工作机制
- javascript Object.create()究竟发生了什么
这是我在博客园的第一篇博客,早上看了一个大牛的博客,关于javascript继承的,对于大牛使用Object.create()实现继承的方式觉得点问题,就自己研究了一下,所以就有了这篇帖子. 本帖 ...
- CentOS下下载软件,不安装的方法
今天来说下在CentOS下下载软件,不安装的方法: 方法一:通过yum自带一个工具:yumdownloader[root@web1 ~]# rpm -qa |grep yum-utils[root@ ...
- caffe 教程
Caffe是一个清晰而高效的深度学习框架,本文详细介绍了caffe的优势.架构,网络定义.各层定义,Caffe的安装与配置,解读了Caffe实现的图像分类模型AlexNet,并演示了CIFAR-10在 ...
- php gettext
安装gettext: ubuntu: apt-get install php-gettext, apt-get install gettext window:开启扩展 修改locale :$ /u ...
- python threading模块2
Thread 是threading模块中最重要的类之一,可以使用它来创建线程.有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法:另一种是创建一个threading.Thread对 ...