Python参数传递,原来既不是传值也不是传引用

面试的时候,有没有被问到Python传参是传引用还是传值这种问题?有没有听到过Python传参既不是传值也不是传引用这种说法?一个小小的参数默认值也可能让代码出现难以查找的bug?

如果你也遇到过上面的问题,不妨我们来探究下Python函数传递的种种。

万物皆对象

Python中有一个非常重要的概念——万物皆对象,无论是一个数字、字符串,还是数组、字典,在Python中都会以一个对象的形式存在。

1a = 123

对于上面这行代码,在Python看来就是创建一个PyObject对象,值为123,然后定义一个指针a,a指向这个PyObject对象。

可变对象和不可变对象

Python中的对象分为两种类型,可变对象和不可变对象,不可变对象指tuple、str、int等类型的对象,可变对象指的是dict、list、自定义对象等类型的对象,我们用一段代码说明他们的区别。

1a = [1, 2, 3]
2print(id(a))  # 2587116690248
3a += [4]
4print(id(a)) # 2587116690248
5
6b = 1
7print(id(b)) # 2006430784
8b += 1
9print(id(b)) # 2006430816

上面代码中我们分别定义了一个可变对象和一个不可变对象,并且对他们进行修改,打印修改前后的对象标识可以发现,对可变对象进行修改,变量对其引用不会发生变化,对不可变对象进行修改,变量引用发生了变化。

上图是一个可变对象,当修改对象时,例如删除数组中的一个元素,实际上把其中一个元素从对象中移除,对象本身的标识是不发生变化的。

改变一个不可变对象时,例如给一个int型加2,语法上看上去是直接修改了i这个对象,但是如前面所说,i只是一个指向对象73的一个变量,Python会将这个变量指向的对象加2后,生成一个新的对象,然后再让i指向这个新的对象。

参数传递时的表现

了解了对象的原理后,我们就可以来尝试理解一下参数传递时他们的不同表现了。

 1a = [1, 2, 3]
 2print(id(a))  # 1437494204232
 3def mutable(a):
 4    print(id(a))  # 1437494204232
 5    a += [4]
 6    print(id(a))  # 1437494204232
 7mutable(a)
 8
 9b = 1
10print(id(b))  # 2006430784
11def immutable(b):
12    print(id(b))  # 2006430784
13    b += 1
14    print(id(b))  # 2006430816
15immutable(b)

通过上面的代码可以看出,修改传进的可变参数时,会对外部对象产生影响,修改不可变参数时则不会影响。

概括地说,Python参数传递时,既不是传对象也不是传引用,之所以会有上述的区别,跟Python的对象机制有关,参数传递只是给对象绑定了一个新的变量(实际上是传递C中的指针)。

参数传递时的坑

理解了参数传递的逻辑,我们需要注意一下这种逻辑可能引发的问题。

1def test(b=[]):
2    b += [1]
3    print(b)
4
5test()  # [1]
6test()  # [1, 1]
7test()  # [1, 1, 1]

上面的代码的输出,按照可变对象传参的逻辑,应该每次调用都输出[1]才对,而实际输出看上去好像默认参数好像只生效了一次。原因在于Python的函数也是对象(万物皆对象),这个对象只初始化一次,加上参数又是不可变对象,所以每次调用实际上都修改的是一个对象。

解决这个问题,推荐再参数传递可变对象时,默认值设置为None,在函数内部对None进行判断后再赋予默认值。

1def test(b=None):
2    b = b or []
3    b += [1]
4    print(b)
5
6test()  # [1]
7test()  # [1]
8test()  # [1]

再看一段代码。

1i = 1
2def test(a=i):
3    print(a)
4
5i = 2
6test()  # 1

由于参数默认值是在函数定义时而不是函数执行时确定的,所以这段代码test方法的参数默认值时1而不是2。

原文发布于微信公众号 - Python私房菜(python-fans)

原文发表时间:2018-04-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏和蔼的张星的图像处理专栏

423. 有效的括号序列利用堆栈

给定一个字符串所表示的括号序列,包含以下字符: '(', ')', '{', '}', '[' and ']', 判定是否是有效的括号序列。

1146
来自专栏子勰随笔

Java参数引用传递引发的惨案(又一次Java的String的“非对象”特性的踩坑经历)

2726
来自专栏Play & Scala 技术分享

Scala基础 - 函数和方法的区别

2285
来自专栏Coco的专栏

一道面试题引发的对javascript类型转换的思考

对于一个好奇的切图仔来说,忍不住动手尝试了一下,看到题目首先想到的是会用到高阶函数以及 Array.prototype.reduce()。

2944
来自专栏Petrichor的专栏

python: list 操作

1234
来自专栏小樱的经验随笔

【python进阶】详解元类及其应用1

前言 元类在python中是很重要的一部分,我将分两次去讲解元类及其应用,此篇为详解元类及其应用第一篇,下面开始今天的说明~~~ 1. 类也是对象 在⼤多数编程...

2985
来自专栏mathor

1小时入门c++面向对象编程

这篇文章只适用于有 C 或 C++ 基础的人看,没有基础建议先去了解一下基础知识,我会结合之前我上课老师讲的内容,以及我自己的理解,有的放矢的讲,而且绝对不会掺...

841
来自专栏javathings

Java 函数调用时值传递还是引用传递?

值传递:函数调用时,传递的参数不是实参本身,而是把参数复制一份,传递到函数中,传递的是一份拷贝。如果参数是基本类型/值类型,那么就是把这个类型拷贝一份传到函数中...

7320
来自专栏java学习

Java每日一练(2017/9/2)

本期题目: (单选题)1、如果int x=20, y=5,则语句System.out.println(x+y+""+(x+y)+y); 的输出结果是() A...

3497
来自专栏函数式编程语言及工具

Scalaz(5)- typeclass:my typeclass scalaz style-demo

  我们在上一篇讨论中介绍了一些基本的由scalaz提供的typeclass。这些基本typeclass主要的作用是通过操作符来保证类型安全,也就是在前期编译时...

2089

扫码关注云+社区

领取腾讯云代金券