我们在前面的章节里学习了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. 自定义view防支付成功页面

    package com.loaderman.customviewdemo; import android.content.Context; import android.graphics.Canvas ...

  2. python之注释的分类

    <1> 单行注释 以#开头,#右边的所有东西当做说明,而不是真正要执行的程序,起辅助说明作用 # 我是注释,可以在里写一些功能说明之类的哦 print('hello world') < ...

  3. Git中.gitignore忽略文件(maven项目)

    使用情景: 有些时候,你必须把某些文件放到Git工作目录中,但又不能提交它们 解决方案: 在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略 ...

  4. 【8】ie css hack

    1. "\9"   IE6/IE7/IE8/IE9/IE10都生效 "\0"   IE8/IE9/IE10都生效,是IE8/9/10的hack "\9 ...

  5. robotframework 配置过程中遇到的问题

    现有环境配置:操作系统: Win7 32bitPython 2.7.8Python 3.5.2Pycharm Community Edition 2016.3.2robotframework: 3.0 ...

  6. 匿名内部类 this.val$的问题

    一天偶尔在网上找到一个jar包,反编译后出现了如下的代码: public void defineAnonymousInnerClass(String name)  {    new Thread(na ...

  7. 安装opencv3.3.0碰到的问题及解决方法

    出处:http://osask.cn/front/ask/view/258965 CMakeError.log Compilation failed: source file: '/home/jhro ...

  8. 双系统正确卸载Ubuntu系统

    双系统正确卸载Ubuntu系统  安装系统后由于显卡驱动问题,无法开机,从而只能卸载重装,重装过程如下. 第一步:下载需要的工具包,这里我用的是MBRfix, 可以直接从我分享的网盘链接下载,密码gw ...

  9. centoss7下将命令加开机服务

    https://www.cnblogs.com/hxun/p/11075755.html

  10. mysql-jdbc connector

    mysql-jdbc connector: https://dev.mysql.com/downloads/connector/j/ 目录: /usr/share/java/mysql-connect ...