Python坑系列:可变对象与不可变对象
在之前的文章 http://www.cnblogs.com/bitpeng/p/4748148.html 中,大家看到了ret.append(path) 和ret.append(path[:])的巨大差别。这和Python的对象机制有关。现在谈谈这个问题!
我们知道,Python有可变对象和不可变对象,他们的表现行为也迥然不同。先来几个简单的问题:
def foo1(arg):
arg = 5
print(arg) x = 1
foo(x) # 输出5
print(x) # 输出1
def foo2(arg):
arg.append(3) x = [1, 2]
print(x) # 输出[1, 2]
foo(x)
print(x) # 输出[1, 2, 3]
def foo3(arg):
arg = [3] x = [1, 2]
print(x) # 输出[1, 2]
foo(x)
print(x) # 输出[1, 2]
一、Python参数问题
1.关于Python默认参数问题,我之前一篇博文有过描述。请参考这里:http://www.cnblogs.com/bitpeng/p/4747765.html
2.关于Python的参数传递问题。原来接触过C/C++ 的朋友,肯定想过,Python函数调用时,到底是值传递,还是引用传递。看到网上说的最多的是:对于不可变对象,是值传递;对于可变对象,是引用传递。可是我个人感觉,这个描述不是很准确,因为Python函数调用时,不管是可变对象,还是不可变对象,参数引用的都是实参。但是,既然为什么都是引用,结果却表现不同,这就和Python的对象有关。
在Python中,任何东西都是对象。
Python使用对象模型来储存数据,任何类型的值都是一个对象。所有的python对象都有3个特征:身份、类型和值。
身份:每一个对象都有自己的唯一的标识,可以使用内建函数id()来得到它。这个值可以被认为是该对象的内存地址。
类型:对象的类型决定了该对象可以保存的什么类型的值,可以进行什么操作,以及遵循什么样的规则。type()函数来查看python 对象的类型。
值:对象表示的数据项。
运算符is
、is not
就是通过id()的返回值(即身份)来判定的,也就是看它们是不是同一个对象的“标签”。
这里有个很形象的例子:http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables
根据这里,我们可以知道,Python是通过名字来访问对象。这和其他语言有很大的不同,比如在C中,你定义了变量(注意是定义不是声明),那么编译后就一定会给变量分配内存,以后对该变量的读写就是通过该内存地址进行的。而Python中,只会给对象分配内存,然后再通过名称来访问该对象而已。所以,这就是为什么Python名称可以赋值给任何类型的原因(并不是真的赋值). 参数传递也一样,只是用形参名称来访问实参所表示的对象。跟所谓的值传递、引用传递没有任何关系。所以:在foo2()中,append()执行列表方法,当然也会影响实参;而赋值操作,只是将x绑定到另一个列表对象。这样,原来的实参列表还是原来的,没有变化,现在foo2()和foo3()应该能懂了吧。
关于Python参数调用的结论:
- Python函数不会替换调用参数所引用的对象。
- 对一个参数名重新赋值不会起任何作用。
- Python函数可以修改参数,如果这个参数是可变的。
- 在Python中没有什么是被隐式复制的。
- 在Python中函数调用时,不存在什么所谓的值传递和引用传递,只是通过名称(形参)来访问对象(实参所代表的对象), 这和Python对象机制是一致的!
二、可变对象,不可变对象复制行为
>>> a = 1
>>> b = 1
>>> a is b
True
>>> import copy
>>> c = copy.deepcopy(a)
>>> c
1
>>> c is a
True
>>> s = "abc"
>>> c = copy.deepcopy(s)
>>> c
'abc'
>>> c is s
True
>>> a = "abc"; b = a; c = a[:]; d = copy.deepcopy(a)
>>> a,b,c,d
('abc', 'abc', 'abc', 'abc')
>>> a is b ;c is a; d is a
True
True
True
看到了吗?对于字符、字符串、数值型,不管是赋值,切片,还是深度复制,他们都是同一个对象。但是对于元组呢?表现有所不同
>>> a = (1,2,[3,4]); b = a; c = a[:];d = copy.deepcopy(a)
>>> b is a; c is a; d is a
True
True
False
>>> a[2][0] = 0;a,b,c,d
((1, 2, [0, 4]), (1, 2, [0, 4]), (1, 2, [0, 4]), (1, 2, [3, 4]))
显然,对于元组并且包含可变元素时,切片和深度复制表现时不一样的。
结论:
1、赋值:简单地拷贝对象的引用,两个对象的id相同。
2、浅拷贝:创建一个新的组合对象,这个新对象与原对象共享内存中的子对象。
3、深拷贝:创建一个新的组合对象,同时递归地拷贝所有子对象,新的组合对象与原对象没有任何关联。虽然实际上会共享不可变的子对象,但不影响它们的相互独立性。
浅拷贝和深拷贝的不同仅仅是对组合对象来说,所谓的组合对象就是包含了其它对象的对象,如列表,类实例。而对于数字、字符串以及其它“原子”类型,没有拷贝一说,产生的都是原对象的引用。
可能有些朋友会有疑问,元组时不可变的,为什么还可以给a[2][0]赋值呢。
>>> a = (1,2,[3,4])
>>> a[2] = [1,2,3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> a[2][:] = [1,2,3]
>>> a
(1, 2, [1, 2, 3])
这里我们这样认为,元组可以包含可变对象,只要元组的每个元素的id没有变化即可。所以a[2] = [1,2,3]是尝试把列表第3个元素引用其他的列表,id肯定变了;所以不支持。但是a[2][:] = [1,2,3]是原地赋值,虽然列表本身变了,但是列表本身的id号没变,所以支持。
结论:对于不可变对象如元组:仅仅代表,他的每一个元素的id号是不可变的。如果元组本身包含可变元素,那么还是可以改变他的值的!
三、Python与二维数组
之前做算法题,需要用二维数组,所以很当然的想到了二维列表。
需求:初始化一个8行8列的数组,每个元素初始化为0.
当时,我想当然的是这样做的:
>>> a = [[0] * 8 ]* 8; a
[[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]]
当时还想着,看,多完美!!!
结果程序总是死循环,不能正常退出。刚开始,结果对程序逻辑进行一次次的检查后,问题还是无解!于是开始调试。最后发现了诡异的问题。
问:a[i][j] = 1 后,程序会发送什么, 其中0 <= i, j < 8.
>>> a = [[0] * 8 ]* 8; a
[[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]]
>>> a[0][0] = 1;a
[[1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0]]
呵呵,发现了吗?给a[0][0]赋值后,结果很多其他的元素也都变了。现在看这个问题其实很简单,因为列表执行乘法,相当于是浅复制。所以a中看似有8个列表,实际上引用的都是同一个。ps:这个问题,实际上书上讲过,我忘了!结果写程序时,出了这样的问题.
>>> for i in a:
... print id(a)
...
140383550710200
140383550710200
140383550710200
140383550710200
140383550710200
140383550710200
140383550710200
140383550710200
也许有人会问,那为什么a = [[0] * 8 ]* 8; a[0][0] = 1后, 只有每一列第一个元素变为1,而其他的不变呢?这个问题也简单:因为0是不可变对象。实际上,a = [[0] * 8 ]* 8执行完毕后,a的所有元素id都是相同的。但是,执行a[0][0] = 1后,a[0][0] 的id号已经变了。
>>> for i in a[0]:print id(i)
...
32276848
32276848
32276848
32276848
32276848
32276848
32276848
32276848
>>> a[0][0] = 1
>>> for i in a[0]:print id(i)
...
32276824
32276848
32276848
32276848
32276848
32276848
32276848
32276848
结论:需要二维数组时,老老实实的用列表推导。
>>> a = [[0 for i in range(8)] for i in range(8)]
>>> a
[[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]]
>>> a[0][0] = 1
>>> a
[[1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0]]
现在已经一切正常了。
Python坑系列:可变对象与不可变对象的更多相关文章
- python 中的可变对象与不可变对象
近日辞职待工,没有实际的项目与大家分享.暂写写在实际运用python中遇到的关于可变对象和不可变对象的坑. 首先我们需要明确一个概念,在python中一且皆对象.我们一般定义一个变量a=0,其实质a是 ...
- 【Python】可变对象和不可变对象
Python在heap中分配的对象分成两类:可变对象和不可变对象.所谓可变对象是指,对象的内容是可变的,例如list.而不可变的对象则相反,表示其内容不可变. 不可变对象:int,string,flo ...
- Python 可变对象和不可变对象
具体可以看这里:http://thomaschen2011.iteye.com/blog/1441254 不可变对象:int,string,float,tuple 可变对象 :list,dicti ...
- Python中的可变对象和不可变对象
Python中的可变对象和不可变对象 什么是可变/不可变对象 不可变对象,该对象所指向的内存中的值不能被改变.当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一 ...
- python,可变对象,不可变对象,深拷贝,浅拷贝。
学习整理,若有问题,欢迎指正. python 可变对象,不可变对象 可变对象 该对象所指定的内存地址上面的值可以被改变,变量被改变后,其所指向的内存地址上面的值,直接被改变,没有发生复制行为,也没有发 ...
- python可变对象与不可变对象的差别
一.可变对象和不可对象 Python在heap中分配的对象分成两类:可变对象和不可对象.所谓可变对象是指,对象的内容可变,而不可变对象是指内容不可变. 不可变对象:int.string.float ...
- Python入门之python可变对象与不可变对象
本文分为如下几个部分 概念 地址问题 作为函数参数 可变参数在类中使用 函数默认参数 类的实现上的差异 概念 可变对象与不可变对象的区别在于对象本身是否可变. python内置的一些类型中 可变对象: ...
- python可变对象与不可变对象
可变/不可变对象定义 不可变对象 该对象所指向的内存中的值不能被改变.当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址. 可 ...
- Python的可变对象与不可变对象。
参考:http://thomaschen2011.iteye.com/blog/1441254 Python基础:Python可变对象和不可变对象 http://blog.jobbo ...
随机推荐
- javascript 中x++和++x的不同
x++和++x都是给x加一,但是前者是完成赋值之后再递增x,后者相反. 例如:如果x是5,y=x++会将y设置为5,x设置为6:而y=++x会将x和y都设置为6.
- PHP-----PHP程序设计基础教程----第一章PHP开篇
本章内容知识体系如下所示: 一.PHP基础知识 1.1 Web技术 服务器与客户端 服务器:能够提供某种服务的电脑 客户端:想使用服务器所提供服务的电脑 服务器也是一台电脑,必须安装操作系统.否则就是 ...
- 关于JQuery的异步注册
在采用JQuery进行表单异步提交时,前台传入的是json数据格式,后台controller用map接收,再传回前台进行结果判断时,if-else接收结果()里面,尽量不要出现“=”,不然判断语句失效 ...
- 前端基础-jQuery的动画效果
阅读目录 隐藏 显示 切换 下拉 上卷 显示 一.jQuery中隐藏元素的hide方法 让页面上的元素不可见,一般可以通过设置css的display为none属性.但是通过css直接修改是静态的布局, ...
- Cloudera环境搭建
在开发阶段,可以单机搭建环境安装Flume和Solr,在两个工程的官网下载相关文件. 还有另一种更便捷的方式,就是使用Cloudera提供的镜像,包括了已经配置好的各种大数据服务环境的docker镜像 ...
- win下python环境搭建以及安装pip、django
1. 安装python并配置 下载安装python,这里我下载的是python2.7,听说2.7比较好用 地址:https://www.python.org/downloads/source/ 记住你 ...
- www.pantom.top
新建小站 https://www.pantom.top
- CVE-2018-8174 EXP 0day python
usage: CVE-2018-8174.py [-h] -u URL -o OUTPUT [-i IP] [-p PORT] Exploit for CVE-2018-8174 optional a ...
- java 8 更优雅的判断是否为空
public static int getLength(String text) { // Java 8 return Optional.ofNullable(text).map(String::le ...
- SDR软件无线电知识要点(三)EVM
SDR软件无线电知识要点(三)EVM 信号质量如何评估 Noise Figure (NF) or sensitivity and Error Vector Magnitude (EVM) provid ...