Python核心技术与实战——十三|Python中参数传递机制

  我们在前面的章节里学习了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 = 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的值是不受影响的,这种方式就是值传递。

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

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

我们只是在函数中交换了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指向的值。

原文地址:https://www.cnblogs.com/yinsedeyinse/p/11811466.html

时间: 2024-07-30 14:24:15

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

Python核心技术与实战

课程目录:第00课.开篇词丨从工程的角度深入理解Python.rar第01课.如何逐步突破,成为Python高手?.rar第02课.Jupyter Notebook为什么是现代Python的必学技术?.rar第03课.列表和元组,到底用哪一个?.rar第04课.字典.集合,你真的了解吗?.rar第05课.深入浅出字符串.rar第06课.Python “黑箱”:输入与输出.rar第07课.修炼基本功:条件与循环.rar第08课.异常处理:如何提高程序的稳定性?.rar第09课.不可或缺的自定义函数

Python核心技术与实战——十五|Python协程

我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多进程模型,这就是解决并发问题的经典模型之一.在最初的互联网世界中,多线程/多进程就在服务器并发中起到举足轻重的作用. 但是随着互联网的发展,慢慢很多场合都会遇到C10K瓶颈,也就是同时连接到服务器的客户达到1W,于是,很多代码就跑崩溃,因为进程的上下文切换占用了大量的资源,线程也顶不住如此巨大的压力

Python核心技术与实战——二一|巧用上下文管理器和with语句精简代码

我们在Python中对于with的语句应该是不陌生的,特别是在文件的输入输出操作中,那在具体的使用过程中,是有什么引伸的含义呢?与之密切相关的上下文管理器(context manager)又是什么呢? 什么是上下文管理器 在任何一种编程语言里,文件的输入输出.数据库的建立连接和断开等操作,都是很常见的资源管理操作.但是资源是有限的,在写程序的时候,我们必须保证这些资源在使用后得到释放,不然就容易造成资源泄漏,轻者系统处理缓慢,重则系统崩溃. 我们看一个例子: for i in range(100

Python核心技术与实战——七|自定义函数

我们前面用的代码都是比较简单的脚本,而实际工作中是没有人把整个一个功能从头写到尾按顺序堆到一块的.一个规范的值得借鉴的Python程序,除非代码量很少(10行20行左右)应该由多个函数组成,这样的代码才更加的模块化.规范化. 函数的基础知识这里就不详细说明了,这里讲一些其他的内容! 一.多态 我们先看一个这样的函数 def fun(a,b): return a+b print(fun(1,2)) print(fun('1','2')) 运行后会发现效果是不一样的.Python不用考虑输入数据的类

Python 核心技术与实战 --01 列表与元祖

2019-12-21 本文章主要讲述Python的列表和元组. 高级内容: 列表与元组存储方式的差异 了解 dir() 函数的概念:    dir() 函数不带参数时,返回当前范围内的变量.方法和定义的类型列表:带参数时,返回参数的属性.方法列表. l = [1,3,3,4,8] print(dir(l)) ['__add__', '__class__', '__contains__', \ '__delattr__', '__delitem__', '__dir__', \ '__doc__'

Python 核心技术与实战 --02 字典和集合

什么是字典?字典是一系列由键(key) 和 值(value) 配对组成的元素集合,在python3.7+ , 字典被确定为有序(注意在3.6 中,字典有序是一个implementation detail, 在3.7 才正式成为语言特性),而在3.6 无法100% 保证有序性,而在3.6 之前是无序的,其长度大小可变,元素可以任意地删减和改变. 相比于列表和元组,字典的性能更优,特别是相对于查找/添加/删除的操作,字典都能在常数时间复杂度内完成. 而集合和字典基本相同,唯一的区别,就是集合没有键和

Python核心技术与实战——八|匿名函数

今天我们来学习一下匿名函数.在学习了上一节的自定义函数后,是时候了解一下匿名函数了.他们往往非常简短,就一行,而且有个关键字:lambda.这就是弥明函数. 一.匿名函数基础 匿名函数的基本格式是这样的: lambda argument1,argument2,argument3,...,argumentN:expression lambda后紧跟的是参数,冒号后是表达式.举个例子来说明用法 >>> fun = lambda x:x**2 >>> fun(4) 16 这里

车万翔《基于深度学习的自然语言处理》中英文PDF+涂铭《Python自然语言处理实战核心技术与算法》PDF及代码

自然语言处理是人工智能领域的一个重要的研究方向,是计算机科学与语言学的交叉学科.随着互联网的快速发展,网络文本尤其是用户生成的文本呈爆炸性增长,为自然语言处理带来了巨大的应用需求.但是由于自然语言具有歧义性.动态性和非规范性,同时语言理解通常需要丰富的知识和一定的推理能力,为自然语言处理带来了极大的挑战. 近年来快速发展的深度学习技术为解决自然语言处理问题的解决提供了一种可能的思路,已成为有效推动自然语言处理技术发展的变革力量. 推荐将深度学习理论运用至NLP中的资料<基于深度学习的自然语言处理

python中的*和**参数传递机制

python的参数传递机制具有值传递(int.float等值数据类型)和引用传递(以字典.列表等非值对象数据类型为代表)两种基本机制以及方便的关键字传递特性(直接使用函数的形参名指定实参的传递目标,如函数定义为def f(a,b,c),那么在调用时可以采用f(b=1,c=2,a=3)的指定形参目标的传递方式,而不必拘泥于c语言之类的形参和实参按位置对应) 除此之外,python中还允许包裹方式的参数传递,这未不确定参数个数和参数类型的函数调用提供了基础: def f(*a,**b) 包裹参数传递