目录

1 Python函数参数的值传递机制

2 Python函数参数的引用传递

3 两个结论

4. 拓展

4.1 可变类型和不可变类型

4.2 python传参时可变类型和不可变类型的区别

参考


Python中,函数参数由实参传递给形参的过程,是由参数传递机制来控制的。通过学习《Python函数值传递和引用传递》一节我们知道,根据实际参数的类型不同,函数参数的传递方式分为值传递和引用传递(又称为地址传递),本节将对这两种传递机制做深度剖析。

1 Python函数参数的值传递机制

所谓值传递,实际上就是将实际参数值的副本(复制品)传入函数,而参数本身不会受到任何影响。

值传递的方式,类似于《西游记》里的孙悟空,它复制一个假孙悟空,假孙悟空具有的能力和真孙悟空相同,可除妖或被砍头。但不管这个假孙悟空遇到什么事,真孙悟空都不会受到任何影响。与此类似,传入函数的是实际参数值的复制品,不管在函数中对这个复制品如何操作,实际参数值本身不会受到任何影响。

下面程序演示了函数参数进行值传递的效果:

def swap(a , b) :# 下面代码实现a、b变量的值交换a, b = b, aprint("swap函数里,a的值是", \a, ";b的值是", b)
a = 6
b = 9
swap(a , b)
print("交换结束后,变量a的值是", \a , ";变量b的值是", b)

运行上面程序,将看到如下运行结果:

swap函数里,a的值是 9 ;b的值是 6
交换结束后,变量a的值是 6 ;变量b的值是 9

从上面的运行结果来看,在 swap() 函数里,a 和 b 的值分别是 9、6,交换结束后,变量 a 和 b 的值依然是 6、9。从这个运行结果可以看出,程序中实际定义的变量 a 和 b,并不是 swap() 函数里的 a 和 b 。
正如前面所讲的,swap() 函数里的 a 和 b 只是主程序中变量 a 和 b 的复制品。下面通过示意图来说明上面程序的执行过程。
上面程序开始定义了 a、b 两个局部变量,这两个变量在内存中的存储示意图如图 1 所示。


图 1 主栈区中 a、b 变量存储示意图

当程序执行 swap() 函数时,系统进入 swap() 函数,并将主程序中的 a、b 变量作为参数值传入 swap() 函数,但传入 swap() 函数的只是 a、b 的副本,而不是 a、b 本身。进入 swap() 函数后,系统中产生了 4 个变量,这 4 个变量在内存中的存储示意图如图 2 所示。

图 2 主栈区的变量作为参数值传入 swap() 函数后存储示意图

当在主程序中调用 swap() 函数时,系统分别为主程序和 swap() 函数分配两块栈区,用于保存它们的局部变量。将主程序中的 a、b 变量作为参数值传入 swap() 函数,实际上是在 swap() 函数栈区中重新产生了两个变量 a、b,并将主程序栈区中 a、b 变量的值分别赋值给 swap() 函数栈区中的 a、b 参数(就是对 swap() 函数的 a、b 两个变量进行初始化)。此时,系统存在两个 a 变量、两个 b 变量,只是存在于不同的栈区中而己。
程序在 swap() 函数中交换 a、b 两个变量的值,实际上是对图 2 中灰色区域的 a、b 变量进行交换。交换结束后,输出 swap() 函数中 a、b 变量的值,可以看到 a 的值为 9,b 的值为 6,此时在内存中的存储示意图如图 3 所示。

图 3 swap() 函数中 a、b 交换之后的存储示意图

对比图 3 与图 1,可以看到两个示意图中主程序栈区中 a、b 的值并未有任何改变,程序改变的只是 swap() 函数栈区中 a、b 的值。这就是值传递的实质:当系统开始执行函数时,系统对形参执行初始化,就是把实参变量的值赋给函数的形参变量,在函数中操作的并不是实际的实参变量。

2 Python函数参数的引用传递

如果实际参数的数据类型是可变对象(列表、字典),则函数参数的传递方式将采用引用传递方式。需要注意的是,引用传递方式的底层实现,采用的依然还是值传递的方式。

下面程序示范了引用传递参数的效果:

def swap(dw):# 下面代码实现dw的a、b两个元素的值交换dw['a'], dw['b'] = dw['b'], dw['a']print("swap函数里,a元素的值是",\dw['a'], ";b元素的值是", dw['b'])
dw = {'a': 6, 'b': 9}
swap(dw)
print("交换结束后,a元素的值是",\dw['a'], ";b元素的值是", dw['b'])

运行上面程序,将看到如下运行结果:

swap函数里,a元素的值是 9 ;b元素的值是 6
交换结束后,a元素的值是 9 ;b元素的值是 6

从上面的运行结果来看,在 swap() 函数里,dw 字典的 a、b 两个元素的值被交换成功。不仅如此,当 swap() 函数执行结束后,主程序中 dw 字典的 a、b 两个元素的值也被交换了。这很容易造成一种错觉,即在调用 swap() 函数时,传入 swap() 函数的就是 dw 字典本身,而不是它的复制品。但这只是一种错觉,下面还是结合示意图来说明程序的执行过程。
程序开始创建了一个字典对象,并定义了一个 dw 引用变量(其实就是一个指针)指向字典对象,这意味着此时内存中有两个东西:对象本身和指向该对象的引用变量。此时在系统内存中的存储示意图如图 4 所示:


图 4 主程序创建了字典对象后存储示意图

接下来主程序开始调用 swap() 函数,在调用 swap() 函数时,dw 变量作为参数传入 swap() 函数,这里依然采用值传递方式:把主程序中 dw 变量的值赋给 swap() 函数的 dw 形参,从而完成 swap() 函数的 dw 参数的初始化。值得指出的是,主程序中的 dw 是一个引用变量(也就是一个指针),它保存了字典对象的地址值,当把 dw 的值赋给 swap() 函数的 dw 参数后,就是让 swap() 函数的 dw 参数也保存这个地址值,即也会引用到同一个字典对象。图 5 显示了 dw 字典传入 swap() 函数后的存储示意图。


图 5 dw 字典传入 swap() 函数后存储示意图

从图 5 来看,这种参数传递方式是不折不扣的值传递方式,系统一样复制了dw 的副本传入 swap() 函数。但由于 dw 只是一个引用变量,因此系统复制的是 dw 变量,并未复制字典本身。

当程序在 swap() 函数中操作 dw 参数时,由于 dw 只是一个引用变量,故实际操作的还是字典对象。此时,不管是操作主程序中的 dw 变量,还是操作 swap() 函数里的 dw 参数,其实操作的都是它们共同引用的字典对象,它们引用的是同一个字典对象。因此,当在 swap() 函数中交换 dw 参数所引用字典对象的 a、b 两个元素的值后,可以看到在主程序中 dw 变量所引用字典对象的 a、b 两个元素的值也被交换了。

为了更好地证明主程序中的 dw 和 swap() 函数中的 dw 是两个变量,在 swap() 函数的最后一行增加如下代码:

#把dw 直接赋值为None,让它不再指向任何对象
dw = None

运行上面代码,结果是 swap() 函数中的 dw 变量不再指向任何对象,程序其他地方没有任何改变。主程序调用 swap() 函数后,再次访问 dw 变量的 a、b 两个元素,依然可以输出 9、6。可见,主程序中的 dw 变量没有受到任何影响。实际上,当在 swap() 函数中增加“dw =None”代码后,在内存中的存储示意图如图 6 所示。


图 6 将 swap() 函数中的 dw 赋值为 None 后存储示意图

从图 6 来看,把 swap() 函数中的 dw 赋值为 None 后,在 swap() 函数中失去了对字典对象的引用,不可再访问该字典对象。但主程序中的 dw 变量不受任何影响,依然可以引用该字典对象,所以依然可以输出字典对象的 a、b 元素的值。

3 两个结论

  1. 不管什么类型的参数,在 Python 函数中对参数直接使用“=”符号赋值是没用的,直接使用“=”符号赋值并不能改变参数。
  2. 如果需要让函数修改某些数据,则可以通过把这些数据包装成列表、字典等可变对象,然后把列表、字典等可变对象作为参数传入函数,在函数中通过列表、字典的方法修改它们,这样才能改变这些数据。

4. 拓展

要搞清楚python的函数调用时传值还是传引用,这还得从python的变量内存模型说起,作为一个C/C++程序员,对于变量的理解就是CPU会为每个变量分配独立的内存空间,在变量生存周期结束时内存空间被收回。

但python却使用了另一种完全不同的机制,对于python而言,一切皆对象,python为每个对象分配内存空间,但是并非为每个变量分配内存空间,因为在python中,变量更像是一个标签,就像在现实生活中,一个人可以有多种身份标签,比如:XX的父亲,XX的儿子,XX的工程师,X地志愿者等等,但对应的实体都是同一个人,只占同一份资源。

4.1 可变类型和不可变类型

在python中将类型分为了可变类型和不可变类型,分别有:

可变类型:列表,字典

不可变类型:int、float、string、tuple

我们可以这样简单地来理解可变类型和不可变类型:在修改该类型变量时是否产生新对象,如果是在原对象上进行修改,为可变对象,如果是产生新的对象,则是不可变对象。

那么怎么判断是否产生新的对象呢?我们可以用python内建id()函数来判断,这个函数返回对象在内存中的位置,如果内存位置有变动,表明变量指向的对象已经被改变。

4.2 python传参时可变类型和不可变类型的区别

事实上,对于python的函数传递而言,我们不能简单地用传值或者传址来定义参数传递,我们从上一部分中可变类型和不可变类型的角度来分析:

  • 在参数传递时,实参将标签复制给了形参,这个时候形参和实参都是指向同一个对象。
  • 在函数内修改形参,
    • 对于不可变类型变量而言:因为不可变类型变量特性,修改变量需要新创建一个对象,形参的标签转而指向新对象,而实参没有变
    • 对于可变类型变量而言,因为可变类型变量特性,直接在原对象上修改,因为此时形参和实参都是指向同一个对象,所以,实参指向的对象自然就被修改了。
      到这里,应该就不难理解为什么在"python的参数传递"部分,test1.py和test2.py执行完两种完全不同的结果了(test1.py传入不可变类型int,实参未被函数修改。而test2.py传入可变类型list,实参被修改)。

参考

  • python函数调用时参数传递方式

Python函数参数的值传递机制相关推荐

  1. python函数参数的引用传递_Python初学者笔记(八):函数参数的值传递和引用传递...

    函数参数的值传递和引用传递 像我们最熟悉的 C 语言,再写函数参数的同时需要规定函数参数到底是值传递还是引用传递.引用传递导致该参数在函数内的修改出了函数之外也会生效,值传递所有的修改都不会体现到函数 ...

  2. 图解C/C++中函数参数的值传递、指针传递与引用传递

    因为一直对这几种函数参数的传递方式理解的不是很透彻,花了一段时间仔细捋清了他们之间的区别.这个问题也是编程初级阶段会经常遇到的问题,也是有可能在面试中遇到的基本问题,在此进行了简单的总结一下,一是加深 ...

  3. 类对象作为函数参数进行值传递

    这个是今天踩到的坑,记录一下. 首先先给出几个概念: 1)类对象在创建时会调用构造函数,在释放是会调用析构函数: 2)函数中的局部变量在函数返回后,会由于弹栈从而内存被释放: 3)如果函数参数是采用值 ...

  4. 函数参数的值传递和地址传递

    利用一个小例子说明,见代码. #include<stdio.h> void F1(int *pp) {pp++;*pp = 9; }void F2(int **pp) {(*pp)++; ...

  5. python参数传递方法_深入理解python中函数传递参数是值传递还是引用传递

    python 的 深入理解python中函数传递参数是值传递还是引用传递 目前网络上大部分博客的结论都是这样的: Python不允许程序员选择采用传值还是传 引用.Python参数传递采用的肯定是&q ...

  6. python是值传递还是引用传递_Python里参数是值传递还是引用传递?

    Python就是把一些参数从一个函数传递到另一个函数,从而使其执行相应的任务.但是你有没有想过,参数传递的底层是如何工作的,原理又是怎样的呢? 实际工作中,很多人会遇到这样的场景:写完了代码,一测试, ...

  7. python函数参数引用传递

    python函数参数引用传递 Python中函数参数是引用传递(注意不是值传递) 对于不可变类型,因变量不能修改,所以运算不会影响到变量自身 而对于可变类型来说,函数体中的运算有可能会更改传入的参数变 ...

  8. python函数参数类型及顺序_python函数参数类型及其顺序

    根据inspect模块官文文档中关于函数参数类型的相关说明,python函数参数共有五种类型,按顺序分别为:POSITIONAL_ONLY.POSITIONAL_OR_KEYWORD.VAR_POSI ...

  9. python函数参数类型及其顺序

    根据inspect模块官文文档中关于函数参数类型的相关说明,python函数参数共有五种类型,按顺序分别为:POSITIONAL_ONLY.POSITIONAL_OR_KEYWORD.VAR_POSI ...

  10. ES6-ES11-第一部分-let、const、解构赋值、模板字符串、简化对象写法、箭头函数、函数参数默认值、rest 参数、扩展运算符、Symbol、迭代器、生成器、Promise、Set、Map

    根据视频进行整理 [https://www.bilibili.com/video/BV1uK411H7on?p=1] 视频资源(百度网盘): 链接:[https://pan.baidu.com/s/1 ...

最新文章

  1. R语言使用R基础安装中的glm函数构建乳腺癌二分类预测逻辑回归模型、分类预测器(分类变量)被自动替换为一组虚拟编码变量、summary函数查看检查模型、使用table函数计算混淆矩阵评估分类模型性能
  2. TCP三次握手Render Tree页面渲染=从输入URL到页面显示的过程?
  3. 惊了!7 行代码优雅地实现 Excel 文件生成下载功能
  4. 【通知】深度学习之模型设计书籍勘误汇总!附赠资源和代码开源问题
  5. Partition--分区总结
  6. 在SD/MMC卡中可读写的FAT文件系统
  7. Linux开启fileinfo扩展
  8. html菜鸟ruby,Ruby 循环
  9. MAVEN安装和配置
  10. python爬虫 正则表达式 re.finditer 元字符 贪婪匹配 惰性匹配
  11. JavaScript实现的List(集合)类
  12. leetcode—8.同向双指针—滑动窗口题型python解答
  13. Java去除富文本编辑器中的格式标签
  14. double转int方法为 去尾法
  15. mysql节点是什么意思_数据库节点是什么意思
  16. 干货!底层视觉研究,我们应该往哪里走?
  17. 车辆管理系统无法连接服务器,智能通道人员车辆管理软件常见问题
  18. C# .NET实现扫描识别图片中的文字
  19. 为什么hashmap的容量必须是2的n次幂
  20. 圣邦微电子2023校招笔试

热门文章

  1. 2007年测试员的工具选择排名
  2. USACO 3.3.1 Riding the Fences 骑马修栅栏(欧拉回路)
  3. 856. Score of Parentheses
  4. java 面试基础总结(二)---多线程
  5. python中的not具体使用及意思
  6. 奢侈的休闲度假时光——带着小样儿去三亚(三)
  7. GridView的多表头(排序)实例方法
  8. 没有记录让grivdview显示
  9. Android中的Can't create handler inside thread that has not called Looper.prepare()异常
  10. jar包add to build path与放入lib下