最近老收到关于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传参传什么?相关推荐

  1. python函数的传递方式有哪些_Python全栈工程师(函数的传参)

    ParisGabriel 感谢 大家的支持 每天坚持 一天一篇 点个订阅吧  灰常感谢    当个死粉也阔以 Python人工智能从入门到精通 "\n"Linux 换行符 &quo ...

  2. python批量上传_python批量上传

    环境 python 3.6 你换成其他3x的版本也没关系 flask 项目很小,主要是演示一下使用flask接收页面上传的文件的方法.项目包含一个批量示范页面. 演示页面文件,为了方便演示.我把htm ...

  3. Android:页面跳转传参方式一,页面跳转传参方式二

     一.页面跳转传参方式一 跳转的过程如何传参:也就是页面之间如何传递参数,有点像函数调用如何传参,页面跳转也要传参. 复制一个工程 你要跳转,(现在的代码如下),intent 既然能设置跳转到哪个页面 ...

  4. Python实现GCS bucket断点续传功能,分块上传文件

    Python实现GCS bucket断点续传功能,分块上传文件 环境:Python 3.6 我有一个关于使用断点续传到Google Cloud Storage的上传速度的问题.我已经编写了一个Pyth ...

  5. django-路由传参-视图捕获URL的参数-位置传参-关键词传参

    图例中的url是旧版本的方法,它对应了新版的 re_path 位置传参 路由中写的是正则式 被括号括起来的内容,会被作为参数,传递给视图 视图应该新增形参来接收数据 关键字参数 如果给分组起了名字 视 ...

  6. python request 库传送formdata_Python Requests库 form-data 上传文件操作

    请求数据示例: ------WebKitFormBoundaryKLoWgrA4O40MayHM Content-Disposition: form-data; name="id" ...

  7. MySQL在脚本中执行传参数_ZZW_shell脚本中的调用MYSQL传参及注意的问题

    [oracle@ip9140 db_pcc]$ cat zzw_cc.sh #!/bin/bash z_user='pcc_csuser22' z_pass='pcc_csuser22' z_db=' ...

  8. python中的点表示什么_Python里面这些点,新手看完之后完全不知道这些点

    原标题:Python里面这些点,新手看完之后完全不知道这些点 Python虽然语法简单,通俗易懂,但是再简单它也是一门语言,就像一棵大树,总有一些树枝是弯弯绕绕的,让新手看完之后一脸懵逼,今天我们就来 ...

  9. python表示空类型的关键字_Python 为什么没有 void 关键字?

    原标题:Python 为什么没有 void 关键字? void 是编程语言中最常见的关键字之一,从字面上理解,它是"空的.空集.空白"的意思,最常用于表示函数的一种返回值类型. 维 ...

最新文章

  1. 死锁产生原因-竞争临时资源
  2. tcp reno 介绍
  3. 【caffe】windows下vs2013+opencv3.2.0+opencv_contrib(包含dnn)+cmake3.8编译与配置
  4. Linux 进程通信 -- 信号
  5. 爬虫入门二(urllib,urllib2)
  6. reportviewer动态数据源
  7. 高性能nginx HTTP服务器 配置实例(转自我的收藏)
  8. 学生信息管理信息系统--添加用户
  9. 初识CPS方法的连续动态建模
  10. 【行车路径规划】百度地图API,行车路径规划 起始点+途经点
  11. 2021年7月最新iOS面试题总结(答案篇)
  12. window10怎么设置共享计算机,Win10系统网络共享功能怎么用?Windows10网络共享功能使用方法...
  13. phx.gen.html 生成器
  14. hostapd wpa_supplicant madwifi详细分析(七)——hostapd整体梳理
  15. 连接HC-05与HC-06
  16. 计算机四级网络工程师合格,计算机四级网络工程师通过率有多少
  17. 导入MVVMLight出现错误 ViewModelLocator does not exist in the namespace clr-namespace:WpfApp1.ViewModel
  18. 不必再造轮子了, 这款代码生成器(一键生成)真的很强
  19. sap清账使用反记账_SAPFI反记账功能
  20. 重庆市计算机一级理论题,重庆市计算机一级题库加答案

热门文章

  1. Go 和 Golang 有什么关系?
  2. 华为eNSP中交换机设备无法正常启动
  3. 超宽带 (UWB) 解释 (以及为什么它在 iPhone 11)
  4. 牛客网 Rabbit的字符串
  5. 【PHP】PHP服务端支付宝支付及回调
  6. BUUCTF-一叶障目 解析
  7. Sublime Text2安装_注册_使用_技巧
  8. 花百万年薪从阿里挖了个 P8 程序员!难道是“水货”?
  9. Mondrian + JPivot环境配置和演示
  10. 中兴机顶盒子,烽火机顶盒子的交叉编译环境搭建