我们在前面的章节里学习了Python的函数基础以及应用,那么现在想一想:传参,也就是把一些参数从一个函数传递到另一个函数,从而使其执行相应的任务,这个过程的底层是如何工作的,原理又是怎样的呢?
  在实际过程中,我们写完了代码测试时候发现结果和预期值不一样,在一次次debug后发现是传参过程中数据结构发生了改变,导致程序出错。比富我们把一个列表作为实参传递给另一个函数,但是我们并不希望列表再函数运行结束后发生变化。但往往事与愿违,由于某些额外的操作改变了他的值,那就导致后续程序一系列错误的发生。因此,了解Python中参数的传递机制,具有十分重要的意义,所以我们今天就来总结下,Python中是怎么传递参数的。
值传递和引用传递
  我们这里借用一段C++的程序来讲明(在C或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 = ;
int b = ;
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 ;
}
Before swap, value of a :
Before swap, value of b :
After swap, value of a :
After swap, value of b :

在上面的例子中,我们通过调用swap()函数,把a和b的值拷贝给x和y,然后交换x和y的值,这时,x和y的值发生了变化,但a和b的值是不受影响的,这种方式就是值传递。

而引用传递,就是把参数的引用传递给变量,这样,原变量和新变量是指向同一块内存地址,如果改变了其中任何变量的值,那么另外一个变量也会相应改变。我们把上面的代码稍作修改

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

那么他输出就是另外的结果了

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

我们只是在函数中交换了x和y的值,但是因为引用传递使得a和x,b和y分别指向的是同一块地址,那么x和y的改变也会导致a和b的改变。

上面是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,str是不可变的,所以,a =a +1并不是让a的值加上1,而是重新创建了一个新的值为2的对象,并让a指向它,但b仍然不变,依旧指向1这个对象。也就是说结果a值变成2,而b值仍为1。

  通过上面的例子可以发现,a和b开始是指向了同一个对象的两个变量,b=a这个赋值语句并不表示重新创建了新对象,而是让同一个对象被多个变量指向或引用。

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

  下面我们在看一个列表的例子:

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

同样,我们让列表l1和l2同时指向[1,2,3,4]这个对象

由于列表是可变的,l1.append(5)并不会创建新的列表,只是在原列表的末尾插入了新的元素,由于l1和l2同时指向这个列表,所以列表的变化会同时反映在l1和l2两个变量上,那么l1和l2的值就同时改变。

另外要注意的是:Python中只有变量可以被删除,而对象是无法被删除的,

l = [1,2,3,4]
del l

上面的代码只是删除了l这个变量,删除以后l是无法被访问的,但是[1,2,3,4]这个对象还是存在的,只有靠Python的垃圾回收机制发现对象没有被引用的时候才会被回收。

划重点::

由此可见,在Python中:

  1.变量的赋值,只是表示让变量指向某个对象,并不表示拷贝对象给变量;而一个对象可以被多个变量所指向

  2.可变对象(列表,字典,集合等)的改变会影响所有指向改对象的变量。

  3.对于不可变对象(str,int,tuple等),所有指向该对象的值总是一样的,也不会改变,但通过某些操作会返回一个新的对象。

  4.变量可以删除,但是对象不能被删除。

Python参数传递

  从上面的讲解中,可以总结一下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 fun1(b):
b = 2
return b a = 1
a = fun1(a)
print(a)

猜猜打印出来的值是多少?没错,就是2,那我们这样修改一下

def fun2(b):
b = 2 a = 1
a = fun2(a)
print(a)

这里,变量a和b是同时指向1这个对象的,但当我们执行到b=2时,系统会重新创建一个新的对象2并让b指向他,而a始终指向1,所以b值变化,a值不变。但是如果函数有返回值(修改前的代码段),函数返回了新的对象并赋值给a,那么a就指向了新的对象2。

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

def fun3(l):
l.append('a') l1 = [1,2,3]
fun3(l1)
print(l1)

这里,l1并没有被重新赋值,函数也没有返回值,但是由于列表可变,执行append函数以后后l和l1的值都发生了变化。但是如果只是把l进行赋值而不是改变原来的对象,呢么结果是不同的

def fun4(l):
l + ['a'] l1 = [1,2,3]
fun4(l1)
print(l1)

l1被传递给fun函数以后,l指向[1,2,3],在添加了‘a'以后l变化,但fun是没有返回值的,所以l1并不会变化。同样,如果fun加上return的值,l1也是会变的:

def fun5(l):
l + ['a']
return l l1 = [1,2,3]
fun5(l1)
print(l1)

这里我们要记住的是:改变变量和重新赋值的区别:

1.在fun3()中,只是单纯的改变了对象的值,因此函数返回时,所有指向这个对象的变量值都会改变

2在fun4()中,函数创建了新的对象,并把他赋值给一个本地变量,因此原变量是不会变的

3.fun3()和fun5()的用法虽然写法不太一样,但实现的功能一致。在实际应用中我们更倾向于fun5的写法,添加了返回语句,这样更加简洁明了,不易出错。


总结

  总之,Python中的参数既不是值传递,也不是引用传递,而是赋值传递或叫对象的引用传递。要注意的是,这里的赋值或对象的引用传递,并不是指向一个具体的内存地址,而是指向一个具体的对象。

  如果对象是可变的,当其改变是,所有指向这个对象的变量都会改变

  如果对象是不可变的,简单的赋值只能改变其中一个变量的值,其余的变量则不受影响。

  所以,在以后的工作中如果想通过一个函数来改变某个变量的值,通常有两个方法,一种是直接将可变的数据类型(列表、字典、集合)当做参数传入,直接在上面做修改,还有一种方法就是创建一个新的变量来保存修改后的值,然后把他返回给原变量。实际工作中,我们更倾向于后者。


思考题

  1.下面代码中的l1,l2和l3是指向同一个对象么?

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

  2.下面的代码输出是什么?

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

答案:

  1.l2和l3指向的是同一个变量,l1并不是,l1所指向的是另一块内存地址,我们的可以通过id来获得或者is来比较

  2.输出的为{"a":10,"b":20},因为字典是可变的,在函数中我们改变了字典的key指向的值。

Python核心技术与实战——十三|Python中参数传递机制的更多相关文章

  1. Python核心技术与实战——十七|Python并发编程之Futures

    不论是哪一种语言,并发编程都是一项非常重要的技巧.比如我们上一章用的爬虫,就被广泛用在工业的各个领域.我们每天在各个网站.App上获取的新闻信息,很大一部分都是通过并发编程版本的爬虫获得的. 正确并合 ...

  2. Python核心技术与实战——四|Python黑箱:输入与输出

    抽象的看,Python程序可以被看成一个黑箱:通过输入流将数据送达,经过处理后在输入,也就是说具备了一个图灵机运作的必要条件. 输入输出基础 最简单的输入是来自键盘的操作 name = input(' ...

  3. Python核心技术与实战——十九|一起看看Python全局解释器锁GIL

    我们在前面的几节课里讲了Python的并发编程的特性,也了解了多线程编程.事实上,Python的多线程有一个非常重要的话题——GIL(Global Interpreter Lock).我们今天就来讲一 ...

  4. Python核心技术与实战 笔记

    基础篇 Jupyter Notebook 优点 整合所有的资源 交互性编程体验 零成本重现结果 实践站点 Jupyter 官方 Google Research 提供的 Colab 环境 安装 运行 列 ...

  5. Python核心技术与实战——六|异常处理

    和其他语言一样,Python中的异常处理是很重要的机制和代码规范. 一.错误与异常 通常来说程序中的错误分为两种,一种是语法错误,另一种是异常.首先要了解错误和异常的区别和联系. 语法错误比较容易理解 ...

  6. Python核心技术与实战——十二|Python的比较与拷贝

    我们在前面已经接触到了很多Python对象比较的例子,例如这样的 a = b = a == b 或者是将一个对象进行拷贝 l1 = [,,,,] l2 = l1 l3 = list(l1) 那么现在试 ...

  7. Python核心技术与实战——十一|程序的模块化

    我们现在已经总结了Python的基本招式和套路,现在可以写一些不那么简单的系统性工程或代码量较大的应用程序.这时候,一个简单的.py文件就会显得过于臃肿,无法承担一个重量级软件开发的重任.这就需要这一 ...

  8. Python核心技术与实战——九|面向对象

    在搞清了各种数据类型.赋值判断.循环以后如果是从C++.Java语言入手的,就会有一个深坑要过:OOP(object oriented programming):公私有保护.多重继承.多态派生.纯函数 ...

  9. Python核心技术与实战——十四|Python中装饰器的使用

    我在以前的帖子里讲了装饰器的用法,这里我们来具体讲一讲Python中的装饰器,这里,我们从前面讲的函数,闭包为切入点,引出装饰器的概念.表达和基本使用方法.其次,我们结合一些实际工程中的例子,以便能再 ...

随机推荐

  1. Jmeter之乱码 (一)

    Jmeter历史版本下载: http://archive.apache.org/dist/jmeter/binaries/ Jmeter3.0接口测试脚本POST请求主体中的中文无法正确显示,现象如下 ...

  2. 使用ssh-agent管理私钥

    使用ssh-agent的好处: 如果有多台远程服务器与多个私钥文件,ssh-gent将会尝试使用不同的私钥文件建立连接,直至成功 假如有 A.B.C 三台服务器,A是控制节点,A可以直接登录B,但是无 ...

  3. CSS3实用指南 初读笔记

    1.7.1  浏览器前缀 当一个浏览器实现了一个新的属性.值或者选择器,而这个特性还不是处于候选推荐标准状态的时候,在属性前面会添加一个前缀以便于它的渲染引擎识别. CSS属性的浏览器前缀:    前 ...

  4. getApplication()和getApplicationContext()区别

    二者使用结果相同,我们写个代码分别打印二者返回结果,发现两个方法获取的是同一个对象. public class MainActivity extends Activity { @Override pr ...

  5. PHP 按照时区获取当前时间

    /**  * 时间格式化  * @param string $dateformat 时间格式  * @param int $timestamp 时间戳  * @param int $timeoffse ...

  6. AttributeError: 'dict' object has no attribute 'status_code'

    前端AJAX请求数据,提示错误:“AttributeError: 'dict' object has no attribute 'status_code'”. 原因:是提示返回对象dict没有“sta ...

  7. Luogu P2915 [USACO08NOV]奶牛混合起来

    题外话: 是非常颓废的博主 写题解也不在于能不能通过啦,主要是缓解颓废 首先看到这个题,肯定是可以暴力搜索的: 不得不说这道题还是很善良的,一波大暴力dfs,居然有70pts: #include< ...

  8. Python自学笔记之计算机基础

    osi七层协议应用层-表示层-会话层-传输层-网络层-数据链路层-物理层 无线网协议 ethernet 物理层:网线,光纤 数据链路层:arp协议 mac地址,广播 在广播域内传播 网络层:ip地址标 ...

  9. python中的元类介绍

    类也是对象 在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段,在python中也是成立的. class ObjectCreator: pass my_object = ObjectCre ...

  10. python中self与__init__怎么解释能让小白弄懂?

    python中self与__init__怎么解释能让小白弄懂? 这个问题其实没那么简单. 只说一下自己的理解. python 里所有的 object 都有三个属性, 标识(identity), 类型( ...