[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)是否可以被改变 ...
随机推荐
- Forward团队-爬虫豆瓣top250项目-团队编程项目开发环境搭建过程
本次结对编程和团队项目我都需要用python环境,所以环境的搭建是一样的.(本文部分内容引用自己博客:http://www.cnblogs.com/xingyunqi/p/7527411.html) ...
- bzoj3262(cdq分治模板)
裸的cdq,注意去重: #include<iostream> #include<cstdio> #include<cmath> #include<cstrin ...
- MySQL查询实例
单表查询查询所有列 1 SELECT * FROM product; 查询指定列 1 SELECT pro_name,price,pinpai FROM product; 添加常量列 1 SELECT ...
- HashMap原理、源码、实践
HashMap是一种十分常用的数据结构,作为一个应用开发人员,对其原理.实现的加深理解有助于更高效地进行数据存取.本文所用的jdk版本为1.5. 使用HashMap <Effective JAV ...
- robotium测试创建java文件和junit文件区别
两者本身差别不大,只是构造方式不同,我一般都用java文件来创建. 1.当有源码测试时,创建junit测试文件 ActivityInstrumentationTestCase2<T>T可以 ...
- 实验8 LCD8*8点阵
1.控制点阵红绿交替显示,分别从上到下,从左到右循环闪烁三次 接线: P0接J12.P1接J20.P2接J19 /** 1.控制点阵红绿交替显示,分别从上到下,从左到右循环闪烁三次 **/ #incl ...
- 魔方Newlife.Cube权限系统的使用及模版覆盖详解
讲人:大石头 时间:2018-11-14 晚上20:00 地点:钉钉群(组织代码BKMV7685)QQ群:1600800 内容:魔方Newlife.Cube权限系统的使用及模版覆盖详解 准备 源码地址 ...
- 浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded
在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关 ...
- Android 四大组件之" ContentProvider "
前言 ContentProvider作为Android的四大组件之一,是属于需要掌握的基础知识,可能在我们的应用中,对于Activity和Service这两个组件用的很常见,了解的也很多,但是对Con ...
- css3的帧动画
概述 前几天刚好看到一个用了CSS3帧动画的页面,对它非常感兴趣,就研究了一下,记录在下面,供以后开发时参考,相信对其他人也有用. PS:以后别人问我用过什么CSS3属性的时候,我也可以不用说常见的a ...