本博客主要学习python参数传递机制,了解python的参数是如何传递的。学过其它语言如(C,C++),都知道参数传递有值传递和引用传递,python的参数传递属于哪种?

一、值传递和引用传递

值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(被调函数新开辟内存空间存放的是实参的副本值

引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。(被调函数新开辟内存空间存放的是实参的地址

比如,我们来看下面的一段 C++ 代码:

#include <iostream>
using namespace std;// 交换 2 个变量的值
void swap(int x, int y) {int temp;
temp = x; // 交换 x 和 y 的值x = y;y = temp;return;
}
int main () {int a = 1;int b = 2;cout << "Before swap, value of a :" << a << endl;cout << "Before swap, value of b :" << b << endl;swap(a, b); cout << "After swap, value of a :" << a << endl;cout << "After swap, value of b :" << b << endl;return 0;
}Before swap, value of a :1
Before swap, value of b :2
After swap, value of a :1
After swap, value of b :2

这里的 swap() 函数,把 a 和 b 的值拷贝给了 x 和 y,然后再交换 x 和 y 的值。这样一来,x 和 y 的值发生了改变,但是 a 和 b 不受其影响,所以值不变。这种方式,就是我们所说的值传递。

所谓引用传递,通常是指把参数的引用传给新的变量,这样,原变量和新变量就会指向同一块内存地址。如果改变了其中任何一个变量的值,那么另外一个变量也会相应地随之改变。

上述例子中的 swap() 函数,如果改成下面的形式,声明引用类型的参数变量:

void swap(int& x, int& y) {int temp;temp = x; // 交换 x 和 y 的值x = y;y = temp;return;
}

那么输出的便是另一个结果:

Before swap, value of a :1
Before swap, value of b :2
After swap, value of a :2
After swap, value of b :1

原变量 a 和 b 的值被交换了,因为引用传递使得 a 和 x,b 和 y 一模一样,对 x 和 y 的任何改变必然导致了 a 和 b 的相应改变。

这是 C/C++ 语言中的特点。那么 Python 中,参数传递到底是如何进行的呢?它们到底属于值传递、引用传递,还是其他呢?在回答这个问题之前,先来了解一下,Python 变量和赋值的基本原理。

二、Python 变量及其赋值

a = 1
b = a
a = a + 1

首先将 1 赋值于 a,即 a 指向了 1 这个对象,如下面的流程图所示:
接着 b = a 则表示,让变量 b 也同时指向 1 这个对象。这里要注意,Python 里的对象可以被多个变量所指向或引用。
最后执行 a = a + 1。需要注意的是,Python 的数据类型,例如整型(int)、字符串(string)等等,是不可变的。所以,a = a + 1,并不是让 a 的值增加 1,而是表示重新创建了一个新的值为 2 的对象,并让 a 指向它。但是 b 仍然不变,仍然指向 1 这个对象。

因此,最后的结果是,a 的值变成了 2,而 b 的值不变仍然是 1。
通过这个例子你可以看到,这里的 a 和 b,开始只是两个指向同一个对象的变量而已,或者你也可以把它们想象成同一个对象的两个名字。简单的赋值 b = a,并不表示重新创建了新对象,只是让同一个对象被多个变量指向或引用。

同时,指向同一个对象,也并不意味着两个变量就被绑定到了一起。如果你给其中一个变量重新赋值,并不会影响其他变量的值。

l1 = [1, 2, 3]
l2 = l1
l1.append(4)
l1
[1, 2, 3, 4]
l2
[1, 2, 3, 4]

同样的,我们首先让列表 l1 和 l2 同时指向了 [1, 2, 3] 这个对象。
由于列表是可变的,所以 l1.append(4) 不会创建新的列表,只是在原列表的末尾插入了元素 4,变成 [1, 2, 3, 4]。由于 l1 和 l2 同时指向这个列表,所以列表的变化会同时反映在 l1 和 l2 这两个变量上,那么,l1 和 l2 的值就同时变为了 [1, 2, 3, 4]。

另外,需要注意的是,Python 里的变量可以被删除,但是对象无法被删除。比如下面的代码:

l = [1, 2, 3]
del l

del l 删除了 l 这个变量,从此以后你无法访问 l,但是对象 [1, 2, 3] 仍然存在。Python 程序运行时,其自带的垃圾回收系统会跟踪每个对象的引用。如果 [1, 2, 3] 除了 l 外,还在其他地方被引用,那就不会被回收,反之则会被回收。

由此可见,在 Python 中:

Python中的变量是没有类型的,我们可以把它看做一个(*void)类型的指针,变量是可以指向任何对象的,而对象才是有类型的。

Python中的对象有不可变对象(number,string,tuple等)和可变对象之分(list,dict等)。

  1. 变量的赋值,只是表示让变量指向了某个对象,并不表示拷贝对象给变量;而一个对象,可以被多个变量所指向。
  2. 可变对象(列表,字典,集合等等)的改变,会影响所有指向该对象的变量。
  3. 对于不可变对象(字符串,整型,元组等等),所有指向该对象的变量的值总是一样的,也不会改变。但是通过某些操作(+= 等等)更新不可变对象的值时,会返回一个新的对象。
  4. 变量可以被删除,但是对象无法被删除。

python中主要是区分可变对象与不可变对象。前面学习也提到好多次,如参数变量作用域里。https://blog.csdn.net/weixin_44052055/article/details/108385472

三、Python函数的参数传递

首先引用 Python 官方文档中的一段说明:

Remember that arguments are passed by assignment in Python. Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per Se.”

准确地说,Python 的参数传递是赋值传递 (pass by assignment),或者叫作对象的引用传递(pass by object reference)。Python 里所有的数据类型都是对象,所以参数传递时,只是让新变量与原变量指向相同的对象而已,并不存在值传递或是引用传递一说。

def my_func1(b):b = 2a = 1
my_func1(a)
a
1

这里的参数传递,使变量 a 和 b 同时指向了 1 这个对象。但当我们执行到 b = 2 时,系统会重新创建一个值为 2 的新对象,并让 b 指向它;而 a 仍然指向 1 这个对象。所以,a 的值不变,仍然为 1。

那么对于上述例子的情况,是不是就没有办法改变 a 的值了呢?

答案当然是否定的,我们只需稍作改变,让函数返回新变量,赋给 a。这样,a 就指向了一个新的值为 2 的对象,a 的值也因此变为 2。

def my_func2(b):b = 2return ba = 1
a = my_func2(a)
a
2

不过,当可变对象当作参数传入函数里的时候,改变可变对象的值,就会影响所有指向它的变量。比如下面的例子:

def my_func3(l2):l2.append(4)l1 = [1, 2, 3]
my_func3(l1)
l1
[1, 2, 3, 4]

这里 l1 和 l2 先是同时指向值为 [1, 2, 3] 的列表。不过,由于列表可变,执行 append() 函数,对其末尾加入新元素 4 时,变量 l1 和 l2 的值也都随之改变了。

但是,下面这个例子,看似都是给列表增加了一个新元素,却得到了明显不同的结果。

def my_func4(l2):l2 = l2 + [4]l1 = [1, 2, 3]
my_func4(l1)
l1
[1, 2, 3]

注意,这里 l2 = l2 + [4],表示创建了一个“末尾加入元素 4“的新列表,并让 l2 指向这个新的对象。这个过程与 l1 无关,因此 l1 的值不变。当然,同样的,如果要改变 l1 的值,我们就得让上述函数返回一个新列表,再赋予 l1 即可:

def my_func5(l2):l2 = l2 + [4]return l2l1 = [1, 2, 3]
l1 = my_func5(l1)
l1
[1, 2, 3, 4]

尤其要记住的是,改变变量和重新赋值的区别:

  1. my_func3() 中单纯地改变了对象的值,因此函数返回后,所有指向该对象的变量都会被改变;
  2. 但 my_func4() 中则创建了新的对象,并赋值给一个本地变量,因此原变量仍然不变。

至于 my_func3() 和 my_func5() 的用法,两者虽然写法不同,但实现的功能一致。不过,在实际工作应用中,我们往往倾向于类似 my_func5() 的写法,添加返回语句。这样更简洁明了,不易出错。

下面的代码中, l1、l2 和 l3 都指向同一个对象吗?

l1 = [1, 2, 3]
l2 = [1, 2, 3]
l3 = l2

列表是可变对象,每创建一个列表,都会重新分配内存,因此 l1 和 l2 并不是同一个对象,由于 l3 = l2 表明 l3 指向 l2 的对象。

def func(d):d['a'] = 10d['b'] = 20d = {'a': 1, 'b': 2}
func(d)
print(d)

字典的d[‘a’] = 10此类操作相当于list的append函数,不会创建一个新的字典,而是在原字典中添加or改变key和value。函数中的局部变量d和函数外的全局变量d都被改成{‘a’:10,‘b’:20}后,局部变量d又指向了新创建的字典对象{‘a’:1,‘b’:2}。打印输出的是全局变量d,所以是{‘a’:10,‘b’:20}。

总结

和其他语言不同的是,Python 中参数的传递既不是值传递,也不是引用传递,而是赋值传递,或者是叫对象的引用传递。需要注意的是,这里的赋值或对象的引用传递,不是指向一个具体的内存地址,而是指向一个具体的对象

  1. 如果对象是可变的,当其改变时,所有指向这个对象的变量都会改变。
  2. 如果对象不可变,简单的赋值只能改变其中一个变量的值,其余变量则不受影响。

如果你想通过一个函数来改变某个变量的值,通常有两种方法。

  1. 第一种是直接将可变数据类型(比如列表,字典,集合)当作参数传入,直接在其上修改;
  2. 第二种则是创建一个新变量,来保存修改后的值,然后将其返回给原变量

在实际工作中,我们更倾向于使用后者,因为其表达清晰明了,不易出错。

参考:
《Python核心技术与实践》
《Python中值传递和引用传递》https://www.cnblogs.com/xiongxueqi/p/9129708.html
《python中的参数传递是值传递还是引用传递?》https://www.cnblogs.com/spring-haru/p/9320493.html

Python学习:Python中参数传递,是值传递,引用传递or其它相关推荐

  1. Python学习---Python安装与基础1205

    1.0. 安装 1.1.1. 下载 官网下载地址:https://www.python.org/downloads/release/python-352/ 1.1.2. 配置环境变量 因为在安装的时候 ...

  2. Python学习 Python基本图形绘制

    Python学习 Python基本图形绘制 1.深入理解Python语言 2.Python蟒蛇绘制 3.turtle库的使用 4.代码调用方法分析 5.学以致用 1.深入理解Python语言 计算机技 ...

  3. java中是值传递引用传递_Java是按值传递而不是按引用传递

    java中是值传递引用传递 One of the biggest confusion in Java programming language is whether java is Pass by V ...

  4. Java 中参数传递是传值还是引用?

    2019独角兽企业重金招聘Python工程师标准>>> 明确概念 值传递:指的是将变量的一个备份传过去,当该备份被修改后,不会影响原来变量的值: 引用传递:指的是将该变量的地址传过去 ...

  5. 关于C#值类型,引用类型,值传递,引用传递

    说到参数传递,必须得弄清值类型和引用类型: (为了容易表达,我暂且命名存放在堆中的内容为堆中对象,存放在栈上的内容为栈中对象.) 值类型存放在栈中,直接访问.如果有:int a=0;int b=a;就 ...

  6. C专家编程--指针和数组(三) 值传递 指针传递 引用传递

    可以先看下这两篇文章 http://blog.csdn.net/yusiguyuan/article/details/12357381 http://blog.csdn.net/yusiguyuan/ ...

  7. Python:值传递,引用传递?不存在的,是赋值传递

    Python的变量及其赋值 c/c++/c#/java 里面都有值传递,引用传递的概念,在Python中是如何的? 例 a = 1 b = a print(id(a),id(b)) #14072334 ...

  8. Python学习(中一)

    4. 深入 Python 流程控制 除了 Python学习(上)介绍的 while 语句,Python 还从其它语言借鉴了一些流程控制功能,并有所改变. 4.1. if 语句 也许最有名的是 if 语 ...

  9. Python进阶:值传递,引用传递?不存在的,是赋值传递

    Python的变量及其赋值 c/c++/c#/java 里面都有值传递,引用传递的概念,在Python中是如何的? 例 a = 1 b = a print(id(a),id(b)) #14072334 ...

  10. python学习—python中的引用本质

    python引用概念 python中可以使用 id函数查看引用的是否为同一个内存空间,如果返回值相同,说明引用相同.在python中,值是靠引用来传递的. python中所有的变量都是在堆中开辟内存, ...

最新文章

  1. Python基于MASK信息抽取ROI子图并构建基于迁移学习(densenet)的图像分类器实战(原始影像和mask文件都是二维的情况)
  2. 中心化(又叫零均值化)和标准化(又叫归一化)
  3. happen-before原则的理解
  4. html ul左侧浮动,UL里的LI元素左浮动层一行显示时使其居中的方法
  5. BFS迷宫问题模型(具体模拟过程见《啊哈算法》)
  6. LeetCode 2197. 替换数组中的非互质数(栈)
  7. ensp1.3.00.100用哪个virtualbox_virtualBox使用 Ubuntu18.0.4
  8. 吴恩达机器学习笔记(二) —— Logistic回归
  9. 性能报告——使用AOP与DYNAMICProxy的orm性能测试
  10. Datawhale 人工智能培养方案
  11. php 遍历数组 只显示1个,求高手指点,foreach遍历三维数组时只能遍历出第一个子级,这是为什么呢?...
  12. 4.1 Spark SQL概述
  13. python运算符_零基础学习 Python 之运算符
  14. 机器学习之决策树(数据分析师学习必备)——糖潮丽子的博客
  15. LAMP环境的搭建与部署
  16. 简单制作百度注册页面
  17. 关于De-pCode在线工具失效的问题
  18. helm部署mysql_使用 helm 安装 MySQL (包括持久化存储)
  19. 混战多年,K12在线教育的故事讲到哪了?
  20. 现60岁,一次性缴纳60万养老金,一个月领取4000元,你愿意吗

热门文章

  1. libvirt理解总结
  2. oracle rac跨数据中心,Oracle RAC在社保数据中心的应用
  3. 云计算学习路线思维导图
  4. Python学习:学员管理系统
  5. 7-2 英文单词排序 (25 分)
  6. clickhouse.except.ClickHouseUnknownException异常
  7. 易通酒店信息管理系统 v2.5 绿色
  8. 女孩学计算机科学与技术专业好吗,女生适合学计算机科学与技术专业吗
  9. Ae效果控件快速参考:颜色校正
  10. 航海王燃烧意志服务器维护是什么,航海王燃烧意志12月6日更新了什么 航海王燃烧意志更新维护公告...