一、学弟的困惑

十天前一个夜阑人静、月明星稀的夜晚,我和我的朋友们正在学校东门的小餐馆里吃着方圆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. springMvc里的mvc:resources与静态资源的访问

    在进行Spring MVC的配置时,通常我们会配置一个dispatcher servlet用于处理对应的URL.配置如下:   <servlet>   <servlet-name&g ...

  2. Qt 中的消息对话框

    1. QMessagebox 类的几个静态成员函数,可以直接调用创建对话框 StandardButton critical(QWidget * parent, const QString &  ...

  3. 主题模型之潜在语义分析(Latent Semantic Analysis)

    主题模型(Topic Models)是一套试图在大量文档中发现潜在主题结构的机器学习模型,主题模型通过分析文本中的词来发现文档中的主题.主题之间的联系方式和主题的发展.通过主题模型可以使我们组织和总结 ...

  4. forward请求转发,param参数传递以及request.getParameter图文讲解

    <%@ page language="java" contentType="text/html;charset=utf-8" pageEncoding=& ...

  5. Codeforces Round #265 (Div. 2) C. No to Palindromes! 构造不含回文子串的串

    http://codeforces.com/contest/465/problem/C 给定n和m,以及一个字符串s,s不存在长度大于2的回文子串,现在要求输出一个字典比s大的字符串,且串中字母在一定 ...

  6. [ 9.11 ]CF每日一题系列—— 441C暴力模拟

    Description: n * m 的地图,建设k个管道管道只能横竖走,且长度大于等于2,问你任意一种建设方法 Solution: 图里没有障碍,所以先把前k - 1个管道每个分2个长度,最后一个管 ...

  7. python之随机验证码

    一.内置函数 chr ------把数字转换为字母 ord-------把字母转换为数字 n = chr(65) print (n) m = ord("A") print(m) 二 ...

  8. python函数知识

    一.三目运算 也叫三元运算,例如result=x if x<y else y 二.集合(set) 返回主页集合(set):把不同的元素组成一起形成集合,是python基本的数据类型.集合元素(s ...

  9. Android-Java-引用数据类型参数传递内存图

    首先看一个案例: package android.java.oop04; class Person { public String name; public void showName() { Sys ...

  10. Python 学习第三部分函数——第一章函数基础

    函数是python 为了代码最大程度的重用和最小代码冗余而提供的最基本的程序结构.使用它我们可以将复杂的系统分解为可管理的部件. 函数相关语句 def...          创建一个对象并将其赋值给 ...