python传参是什么意思_Python传参传什么?
最近老收到关于Python荐书的问题的邀请,以我的资历,实在不敢为大家推荐什么书籍,只能为大家分享一点点心得。
本文向大家推荐一本读过后至今仍不时翻阅的书:《编写高质量代码:改善Python程序的91个建议》kindle里的截图
这本书为Python工程师写出更好的Python代码提出了91个建议,这些建议通俗易懂、切实可行,在实践中效果立竿见影。书中的代码量较少,而且书的篇幅也比较小,所像我这样购买一份电子版的就好了。
这篇文章中与大家分享书中一节:《建议31:记住函数传参既不是传值也不是传引用》,是这本书中的第31个建议。这一节解释了Python函数中传参传的是什么的困惑,说明了Python函数中的传参,既不是传值,也不是传引用,并用简短的实例生动地说明了是不是、为什么不是。
以下是全文大部分内容转载(转载仅为分享和推荐,侵删):
建议31:记住函数传参既不是传值也不是传引用
Python中的函数参数到底是传值还是传引用呢?这是许多人在学习过程中会纠结的一个问题,很多论坛也有这样的讨论。总结来说基本有3个观点:传引用;传值;可变对象传引用,不可变对象传值。这3个观点到底哪个正确呢?我们逐一讨论。
1)传引用
先来看一个非常简单的例子(请不要因为例子太简单而不以为然,小故事往往蕴含大道理,它照样能说明问题)。
示例一:
>>> def inc(n):
... print(id(n))
... n = n + 1
... print(id(n))
...
>>>
>>> n = 3
>>> print(id(n))
140729534747520
>>> inc(n)
140729534747520
140729534747552
>>> print(n)
3
按照传引用的概念,上面的例子期望的输出应该是4,并且inc()函数里面执行操作n=n+1的前后n的id值应该是不变的。可是事实是不是这样的呢?非也,从输出结果来看n的值还是不变,但id(n)的值在函数体前后却不一致。显然,传引用这个说法是不恰当的。
2)传值
来看一个示例。示例二:
>>> def change_list(orginator_list):
... print('orginator list is: ', orginator_list)
... new_list = orginator_list
... new_list.append('i am new')
... print('new list is: ', new_list)
... return new_list
...
>>> o_list = ['a', 'b', 'c']
>>> new_list = change_list(o_list)
orginator list is: ['a', 'b', 'c']
new list is: ['a', 'b', 'c', 'i am new']
>>> print(new_list, o_list)
['a', 'b', 'c', 'i am new'] ['a', 'b', 'c', 'i am new']
>>>
传值通俗来讲就是这个意思:你在内存中有一个位置,我也有一个位置,我把我的值复制给你,以后你做什么就跟我没关系了,我是我,你是你,咱俩井水不犯河水。可是上面的程序输出根本就不是这么一回事,显然change_list()函数没有遵守约定,调用该函数之后orginator_list也发生了改变,这明显侵犯了orginator_list的权利。这么看来传值这个说法也不合适。
3)可变对象传引用,不可变对象传值
这个说法最靠谱,很多人也是这么理解的,但这个说法到底是否准确呢?再来看一个示例。示例三:
>>> def change_me(org_list):
... print(id(org_list))
... new_list = org_list
... print(id(new_list))
... if len(new_list) > 5:
... new_list = ['a', 'b', 'c']
... for i, e in enumerate(new_list):
... if isinstance(e, list):
... new_list[i] = '***'
... print(new_list)
... print(id(new_list))
...
>>> test1 = [1, ['a', 1, 3], [2, 1], 6]
>>> change_me(test1)
2564672584904
2564672584904
[1, '***', '***', 6]
2564672584904
>>> print(test1) # 函数内部对参数的作用确实反映到了原list上
[1, '***', '***', 6]
>>>
>>> test2 = [1, 2, 3, 4, 5, 6, [1]]
>>> change_me(test2)
2564672639496
2564672639496
['a', 'b', 'c']
2564672660616
>>> print(test2) # 函数内部对参数的作用没有反映到原list上
[1, 2, 3, 4, 5, 6, [1]]
>>>
传入参数org_list为列表,属于可变对象,按照可变对象传引用的理解,new_list和org_list指向同一块内存,因此两者的id值输出一致,任何对new_list所执行的内容的操作会直接反应到org_list,也就是说修改new_list会导致org_list的直接修改,对吧?来观察测试例子。
对于test1、new_list和org_list的表现和我们理解的传引用确实一致,最后test1被修改为[1,'','',6],但对于输入test2、new_list和org_list的id输出在进行列表相关的操作前是一致的,但操作之后new_list的id值却变为35250664,整个test2在调用函数change_me()后却没有发生任何改变,可是按照传引用的理解期望输出应该是['a','b','c'],似乎可变对象传引用这个说法也不恰当。那么Python函数中参数传递的机制到底是怎么样的?要明白这个概念,首先要理解:Python中的赋值与我们所理解的C/C++/go等语言中的赋值的意思并不一样。
Go语言中的赋值
我们观察如下程序(Go语言):
package main
import "fmt"
func main() {
var a int
var b int
a = 5
fmt.Println("a的地址是:", &a)
b = a
fmt.Println("b的地址是:", &b)
b = 7
fmt.Println("重新赋值后的b的地址是:", &b)
}
运行程序:
$ go run memory.go
a的地址是: 0xc00000a0c0
b的地址是: 0xc00000a0c8
重新赋值后的b的地址是: 0xc00000a0c8
同样的程序我们观察下Python中的输出:
>>> a = 5
>>> id(5)
140729534747584
>>> b = a
>>> id(b)
140729534747584
>>> b = 7
>>> id(b)
140729534747648
可以观察到,在Go语言中,a、b的地址一开始就不同,当为b再次赋值时,也没有改变b的地址。在Python中,一开始a、b的地址是一致的,当为b再次赋值时,b的地址发生改变。
从输出可以看出,在Python语言中,b=a赋值后b的id()输出和a一样,但b=7操作后b指向另外一块空间。可以简单理解为,b=a传递的是对象的引用,其过程类似于贴“标签”,5和7是实实在在的内存空间,执行a=5相当于申请一块内存空间代表对象5并在上面贴上标签a,这样a和5便绑定在一起了。而b=a相当于对标签a创建了一个别名,因此它们实际都指向5。但b=7操作之后标签b重新贴到7所代表的对象上去了,而此时5仅有标签a。
Python函数传参的真相
理解了上述背景,再回头来看看前面的例子就很好理解了。
对于示例一,n=n+1,由于n为数字,是不可变对象,n+1会重新申请一块内存,其值为n+1,并在函数体中创建局部变量n指向它。当调用完函数inc(n)之后,函数体中的局部变量在函数外不并不可见,此时的n代表函数体外的命名空间所对应的n,值还是3。
而在示例三中,当org_list的长度大于5的时候,new_list=['a','b','c']操作重新创建了一块内存并将new_list指向它。当传入参数为test2=[1,2,3,4,5,6,[1]]的时候,函数的执行并没有改变该列表的值。
因此,对于Python函数参数是传值还是传引用这个问题的答案是:都不是。正确的叫法应该是传对象(call by object)或者说传对象的引用(call by object reference)。函数参数在传递的过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,调用者和被调用者之间共享这个对象,而对于不可变对象,由于并不能真正被修改,因此,修改往往是通过生成一个新对象然后赋值来实现的。
后记:
原文中其它语言中的示例用的是C/C++,转载时换成了Go。在Go语言中, 一切传递都是值传递,要操作原对象可以通过指针操作。在Python语言中,没有指针的设计,许多Python学习者又是非科班出身且Python是其第一语言,对内存布局缺乏了解,所以在函数参数传递上有一定的困惑(甚至不知道自己应当在此处困惑)。有的学习者知道不应当把可变对象作为函数默认参数、也知道在函数内部操作可变对象有很多坑,但具体为什么可变对象要这样小心翼翼,却不得而知了。学习了《第31个建议》,就会变得豁然开朗。
python传参是什么意思_Python传参传什么?相关推荐
- python函数的传递方式有哪些_Python全栈工程师(函数的传参)
ParisGabriel 感谢 大家的支持 每天坚持 一天一篇 点个订阅吧 灰常感谢 当个死粉也阔以 Python人工智能从入门到精通 "\n"Linux 换行符 &quo ...
- python批量上传_python批量上传
环境 python 3.6 你换成其他3x的版本也没关系 flask 项目很小,主要是演示一下使用flask接收页面上传的文件的方法.项目包含一个批量示范页面. 演示页面文件,为了方便演示.我把htm ...
- Android:页面跳转传参方式一,页面跳转传参方式二
一.页面跳转传参方式一 跳转的过程如何传参:也就是页面之间如何传递参数,有点像函数调用如何传参,页面跳转也要传参. 复制一个工程 你要跳转,(现在的代码如下),intent 既然能设置跳转到哪个页面 ...
- Python实现GCS bucket断点续传功能,分块上传文件
Python实现GCS bucket断点续传功能,分块上传文件 环境:Python 3.6 我有一个关于使用断点续传到Google Cloud Storage的上传速度的问题.我已经编写了一个Pyth ...
- django-路由传参-视图捕获URL的参数-位置传参-关键词传参
图例中的url是旧版本的方法,它对应了新版的 re_path 位置传参 路由中写的是正则式 被括号括起来的内容,会被作为参数,传递给视图 视图应该新增形参来接收数据 关键字参数 如果给分组起了名字 视 ...
- python request 库传送formdata_Python Requests库 form-data 上传文件操作
请求数据示例: ------WebKitFormBoundaryKLoWgrA4O40MayHM Content-Disposition: form-data; name="id" ...
- MySQL在脚本中执行传参数_ZZW_shell脚本中的调用MYSQL传参及注意的问题
[oracle@ip9140 db_pcc]$ cat zzw_cc.sh #!/bin/bash z_user='pcc_csuser22' z_pass='pcc_csuser22' z_db=' ...
- python中的点表示什么_Python里面这些点,新手看完之后完全不知道这些点
原标题:Python里面这些点,新手看完之后完全不知道这些点 Python虽然语法简单,通俗易懂,但是再简单它也是一门语言,就像一棵大树,总有一些树枝是弯弯绕绕的,让新手看完之后一脸懵逼,今天我们就来 ...
- python表示空类型的关键字_Python 为什么没有 void 关键字?
原标题:Python 为什么没有 void 关键字? void 是编程语言中最常见的关键字之一,从字面上理解,它是"空的.空集.空白"的意思,最常用于表示函数的一种返回值类型. 维 ...
最新文章
- 死锁产生原因-竞争临时资源
- tcp reno 介绍
- 【caffe】windows下vs2013+opencv3.2.0+opencv_contrib(包含dnn)+cmake3.8编译与配置
- Linux 进程通信 -- 信号
- 爬虫入门二(urllib,urllib2)
- reportviewer动态数据源
- 高性能nginx HTTP服务器 配置实例(转自我的收藏)
- 学生信息管理信息系统--添加用户
- 初识CPS方法的连续动态建模
- 【行车路径规划】百度地图API,行车路径规划 起始点+途经点
- 2021年7月最新iOS面试题总结(答案篇)
- window10怎么设置共享计算机,Win10系统网络共享功能怎么用?Windows10网络共享功能使用方法...
- phx.gen.html 生成器
- hostapd wpa_supplicant madwifi详细分析(七)——hostapd整体梳理
- 连接HC-05与HC-06
- 计算机四级网络工程师合格,计算机四级网络工程师通过率有多少
- 导入MVVMLight出现错误 ViewModelLocator does not exist in the namespace clr-namespace:WpfApp1.ViewModel
- 不必再造轮子了, 这款代码生成器(一键生成)真的很强
- sap清账使用反记账_SAPFI反记账功能
- 重庆市计算机一级理论题,重庆市计算机一级题库加答案