python中的引用传递,可变对象,不可变对象,list注意点
python中的引用传递
首先必须理解的是,python中一切的传递都是引用(地址),无论是赋值还是函数调用,不存在值传递。
可变对象和不可变对象
python变量保存的是对象的引用,这个引用指向堆内存里的对象,在堆中分配的对象分为两类,一类是可变对象,一类是不可变对象。不可变对象的内容不可改变,保证了数据的不可修改(安全,防止出错),同时可以使得在多线程读取的时候不需要加锁。
不可变对象(变量指向的内存的中的值不能够被改变)
当更改该对象时,由于所指向的内存中的值不可改变,所以会把原来的值复制到新的空间,然后变量指向这个新的地址。
python中数值类型(int和float),布尔型bool,字符串str,元组tuple都是不可变对象。
a = 1
print id(a) # 40133000L,整数1放在了地址为40133000L的内存中,a变量指向这个地址。
a += 1
print id(a) # 40132976L,整数int不可改变,开辟新空间存放加1后的int,a指向这个新空间。
可变对象(变量指向的内存的中的值能够被改变)
当更改该对象时,所指向的内存中的值直接改变,没有发生复制行为。
python中列表list,字典dict,集合set都是可变对象。包括自定义的类对象也是可变对象。
a = [1,2,3]
print id(a) # 44186120L。
a += [4,5] # 相当于调用了a.extend([4,5])
print id(a) # 44186120L,列表list可改变,直接改变指向的内存中的值,没开辟新空间。
a = a + [7,8] # 直接+和+=并不等价,使用+来操作list时,得到的是新的list,不指向原空间。
print id(a) # 44210632L
可变对象和不可变对象
() is () # 返回True,因为tuple是不可变对象(不可改变,怎么定义都一样)
'' is '' # 返回True,因为str是不可变对象
None is None # 返回True,None也是不可变的
[] is [] # 返回False,因为是可变对象(可能改变,定义出来的两个必然要不一样)
{} is {} # 返回False,因为是可变对象
[] == [] # 返回True,注意==和is的不同,==只比较内容,is比较地址(id)
class Student:
pass
Student() is Student() # 返回False,自定义类型也是可变对象,两次定义的对象地址是不同的
id(Student()) == id(Student()) # 返回True,这里比较神奇,是因为创建一个Student对象,id()后返回地址但是进行了对象销毁,第二次又重新创建,两次占用了同一个地址
不可变对象的编译时驻留(类似java的常量池)
int的驻留:-5到256之间的整数都会进行驻留,再次定义的变量地址不变,为什么是-5到256呢,这是解释器决定的,依赖于具体实现。
str的驻留:只包含字母,数字,下划线的字符串会驻留;长度为0或1的会驻留;
a = -5
b = -5
a is b # True,-5到256之间的整数,驻留(直觉上这部分数据会频繁调用,驻留可以节省资源)
a = 256
b = 256
a is b # True,-5到256之间的整数,驻留
a = -6
b = -6
a is b # False,非-5到256之间的整数,不驻留
a = 257
b = 257
a is b # False,非-5到256之间的整数,不驻留
a = 'hello_world'
b = 'hello'+'_'+'world'
a is b # True,只包含字母,数字,下划线的字符串会驻留
a = 'hello_world!'
b = 'hello_world!'
a is b # False,包含了特殊字符!, 不驻留
'hello_world' is '_'.join(['hello', 'world']) # False,因为驻留是编译阶段发生的,join在解释阶段才产生结果,未进行驻留
a, b = 'hello_world!', 'hello_world!'
a is b # True 编译器的优化,在同一行赋值字符串时,只创建一个对象,指给两个引用。(ps:不适用3.7.x版本,3.7.x中会返回False)
关于驻留的陷阱
跟驻留没有直接关系(雾?),是在命令行运行和py文件直接运行有一些差异。先看之前的小例子。
a = 257
b = 257
a is b # False,非-5到256之间的整数,不驻留。
事实上,在命令行运行得到的才是False(我做的小实验一般都在交互式命令行上运行)
如果把这三行放到py文件里,再直接运行,得到的是True,因为py文件是一次性编译的,而交互式命令行按一行为单位(严格说是命令结束时的全部,因为会有for while这种)编译
或者在交互式中把这三行定义为函数,再调用函数,返回也是True
def func():
a = 257
b = 257
return a is b
func() # 返回True
这是由python的代码块机制导致的,在同一代码块中相同值的赋值会指向同一个对象。函数体,类对象体,py文件,模块都可以看作一个代码块。
在交互式命令行上,一行看作一个代码块(严格说是命令结束时的全部,因为会有for while这种),所以,这里所谓“代码块的优化”,就是前面提到的,同行赋值的优化,只在一行(代码块)上优化。
到具体直接运行py文件,又有了更大范围的代码块的优化,所以连着两行相同赋值的对象,会指向同一个对象。
引用传递后的改变
a = [1,2,3]
b = a
b[0] = 2 # 由于list是可变对象,改变b时候会导致a的改变,a和b都是[2,2,3]
s = 'abc'
s2 = s
s2 += 'd' # 由于str是不可变对象,s2是新建的对象,s2的修改不会影响s。s为'abc',s2为'abcd'。
list注意点
a = [1,2,3]
b = a
a is b # True,因为按引用传递,a和b存的地址(引用)是一样的,改变b相当于改变a。
b = a[:]
a is b # False,想使用list的值却不想修改原list时可以使用切片[:]拷贝一份到新空间。
a = [1,2,3]
id(a) # 140376329323528
a = [1,2,3]
id(a) # 140376359286920,两次定义相同的list,但是其地址并不相同,会创造新对象
a = [1,2,3]
id(a) # 140376329323528
a[:] = [1,2,3]
id(a) # 140376329323528,因为a[:]切片创建的是新空间,对新空间赋值不影响旧空间a,所以a的地址跟原来一致。
a =[ [0]*2 ]* 2 # 以这种方式创建一个二维list,此时a为[[0,0],[0,0]]。
a[0] is a[1] # True,这种创建方法的机制是复制list,所以2个list其实是同一个list。
a[0][0] = 1 # 改变第一个list时第二个list也改变,此时a为[[1,0],[1,0]]。
a[0] += [1] # 改变第一个list时第二个list也改变,此时a为[[1,0,1],[1,0,1]]。+=相当于extend,对list进行原地修改。
a[0] = a[0] + [1] # 改变第一个list时,第二个list不改变,此时a为[[1,0,1,1],[1,0,1]]。因为不是原地改变,而是创建了新list,然后给原来的引用赋了新值。
a[0] = [1,2] # a[0]指向创建的新list[1,2]。此时a[1]不变,a为[[1,2],[1,0,1]]。同样是给a[0]赋值了新的list[1,2],不会影响到a[1]。
a = [[0]*2 for _ in range(2)] # 相对正确的创建方式,这样创建的二维list,改变a[0]并不会影响a[1]
a[0] is a[1] # False
a = [ []*1000 ] # 同理,这么定义返回的是[],并不能得到含有1000个空list的list(直觉误区)
a = [ [] for _ in range(1000) ] # 正确的定义方式
python中的引用传递,可变对象,不可变对象,list注意点的更多相关文章
- 非常易于理解‘类'与'对象’ 间 属性 引用关系,暨《Python 中的引用和类属性的初步理解》读后感
关键字:名称,名称空间,引用,指针,指针类型的指针(即指向指针的指针) 我读完后的理解总结: 1. 我们知道,python中的变量的赋值操作,变量其实就是一个名称name,赋值就是将name引用到一个 ...
- python中的引用
作为一个python初学者,今天被一个python列表和词典引用的问题折磨了很久,但其实了解了缘由也很简单,记录在此备忘. 首先背书python中的引用对象问题: 1. python不允许程序员选择采 ...
- 理解Java中的引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习 ...
- VB6中的引用传递 与 VB.NET中的引用传递的区别
首先注意一点,在VB6中缺省参数传递的方式是:引用传递,而在VB.NET中缺省参数传递的方式是:值传递. 然后我们看下面VB6中的引用传递与VB.NET中的引用传递的对比. VB6中的引用传递 Pri ...
- Python中的引用的使用注意
关于Python中的引用的一些使用注意 在python中,在创建一个对象并给它赋予一个变量时,这个赋予的变量仅仅是一个引用它所代表的对象.也就是说新创建的对象只是指向计算机中储存那个对象的内存. 比如 ...
- (转载)理解Java中的引用传递和值传递
关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天 ...
- Java中的引用传递和值传递
Java中的引用传递和值传递 关于Java的引用传递和值传递,在听了老师讲解后,还是没有弄清楚是怎么一回事,于是查了资料,所以在这里与大家分享,有不对的地方,欢迎大家留言. java中是没有指针的,j ...
- Java中没有引用传递只有值传递(在函数中)
◆传参的问题 引用类型(在函数调用中)的传参问题,是一个相当扯的问题.有些书上说是传值,有些书上说是传引用.搞得Java程序员都快成神经分裂了.所以,我们最后来谈一下“引用类型参数传递”的问题. 如下 ...
- python中的值传递和引用传递
Python中的变量是没有类型的,我们可以把它看做一个(*void)类型的指针,变量是可以指向任何对象的,而对象才是有类型的. Python中的对象有可变对象(number,string,tuple等 ...
随机推荐
- Linux命令之乐--nmap
Nmap是一款非常强大的实用工具,可用于:检测活在网络上的主机(主机发现)检测主机上开放的端口(端口发现或枚举)检测到相应的端口(服务发现)的软件和版本检测操作系统,硬件地址,以及软件版本检测脆弱性的 ...
- C语言switch语句
C语言虽然没有限制 if else 能够处理的分支数量,但当分支过多时,用 if else 处理会不太方便,而且容易出现 if else 配对出错的情况.例如,输入一个整数,输出该整数对应的星期几的英 ...
- 88、android 插件开发教程(转载)
http://blog.csdn.net/qq435757399/article/details/46521085 http://blog.csdn.net/t12x3456/article/deta ...
- 关于Win7 x64下过TP保护(应用层)(转)
非常感谢大家那么支持我上一篇教程.Win10 快出了,所以我打算尽快把应用层的部分说完. 调试对象:DXF调试工具:CE.OD.PCHunter.Windbg调试先言:TP的应用层保护做得比较多,包括 ...
- webpack配置(一)
这里再配置的时候走了些弯路,现在,把配置前的准备工作做好很重要: 首先,安装node.js,当然,npm也就有了: 其次,安装xampp,主要是为了配置Apache: 安装好后,xampp---htd ...
- 延迟加载JavaScript
上代码: 这段代码放到HTML文档的</body>标签之前(靠近HTML文档底部).外部脚本的名称为defer.js. <script type="text/javascr ...
- MFC中控件的TAB顺序
本文来自: http://hi.baidu.com/qingcaichongch/item/47f7ae14de8cbef6ddeeca42 在MFC中添加控件后,按Ctrl+d可以改变控件TAB顺序 ...
- html 标签转译反转译
如果要在 html 标签内原样插入带 html 标签的文字,通常都会被自动解析掉,比如: document.getElementsByTagName('div')[0].innerHTML=" ...
- 170407、java基于nio工作方式的socket通信
客户端代码: /** * */ package com.bobohe.nio; import java.io.BufferedReader; import java.io.IOException; i ...
- Requset和Response中的乱码问题
在我们的日常开发中,乱码问题,还是比较经常遇到的,有时候是浏览器端提交的数据到后台乱码了,有时候是后台响应的数据到前台浏览器端展现出现乱码了.下面我们将通过几个简单的例子来说明乱码的由来和解决方式. ...