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

Posted 银色的音色

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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指向的值。

 

 

以上是关于Python核心技术与实战——十三|Python中参数传递机制的主要内容,如果未能解决你的问题,请参考以下文章

精通Python网络爬虫 核心技术框架与项目实战

Python核心技术与实战

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

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

Python自然语言处理实战核心技术与算法,Python自然语言处理,PyTorch深度学习实战下载

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