在之前的文章 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 对象的类型。

:对象表示的数据项。

运算符isis 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坑系列:可变对象与不可变对象的更多相关文章

  1. python 中的可变对象与不可变对象

    近日辞职待工,没有实际的项目与大家分享.暂写写在实际运用python中遇到的关于可变对象和不可变对象的坑. 首先我们需要明确一个概念,在python中一且皆对象.我们一般定义一个变量a=0,其实质a是 ...

  2. 【Python】可变对象和不可变对象

    Python在heap中分配的对象分成两类:可变对象和不可变对象.所谓可变对象是指,对象的内容是可变的,例如list.而不可变的对象则相反,表示其内容不可变. 不可变对象:int,string,flo ...

  3. Python 可变对象和不可变对象

    具体可以看这里:http://thomaschen2011.iteye.com/blog/1441254 不可变对象:int,string,float,tuple 可变对象   :list,dicti ...

  4. Python中的可变对象和不可变对象

    Python中的可变对象和不可变对象 什么是可变/不可变对象 不可变对象,该对象所指向的内存中的值不能被改变.当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一 ...

  5. python,可变对象,不可变对象,深拷贝,浅拷贝。

    学习整理,若有问题,欢迎指正. python 可变对象,不可变对象 可变对象 该对象所指定的内存地址上面的值可以被改变,变量被改变后,其所指向的内存地址上面的值,直接被改变,没有发生复制行为,也没有发 ...

  6. python可变对象与不可变对象的差别

    一.可变对象和不可对象 Python在heap中分配的对象分成两类:可变对象和不可对象.所谓可变对象是指,对象的内容可变,而不可变对象是指内容不可变.   不可变对象:int.string.float ...

  7. Python入门之python可变对象与不可变对象

    本文分为如下几个部分 概念 地址问题 作为函数参数 可变参数在类中使用 函数默认参数 类的实现上的差异 概念 可变对象与不可变对象的区别在于对象本身是否可变. python内置的一些类型中 可变对象: ...

  8. python可变对象与不可变对象

    可变/不可变对象定义 不可变对象 该对象所指向的内存中的值不能被改变.当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址. 可 ...

  9. Python的可变对象与不可变对象。

    参考:http://thomaschen2011.iteye.com/blog/1441254          Python基础:Python可变对象和不可变对象 http://blog.jobbo ...

随机推荐

  1. javascript 中x++和++x的不同

    x++和++x都是给x加一,但是前者是完成赋值之后再递增x,后者相反. 例如:如果x是5,y=x++会将y设置为5,x设置为6:而y=++x会将x和y都设置为6.

  2. PHP-----PHP程序设计基础教程----第一章PHP开篇

    本章内容知识体系如下所示: 一.PHP基础知识 1.1 Web技术 服务器与客户端 服务器:能够提供某种服务的电脑 客户端:想使用服务器所提供服务的电脑 服务器也是一台电脑,必须安装操作系统.否则就是 ...

  3. 关于JQuery的异步注册

    在采用JQuery进行表单异步提交时,前台传入的是json数据格式,后台controller用map接收,再传回前台进行结果判断时,if-else接收结果()里面,尽量不要出现“=”,不然判断语句失效 ...

  4. 前端基础-jQuery的动画效果

    阅读目录 隐藏 显示 切换 下拉 上卷 显示 一.jQuery中隐藏元素的hide方法 让页面上的元素不可见,一般可以通过设置css的display为none属性.但是通过css直接修改是静态的布局, ...

  5. Cloudera环境搭建

    在开发阶段,可以单机搭建环境安装Flume和Solr,在两个工程的官网下载相关文件. 还有另一种更便捷的方式,就是使用Cloudera提供的镜像,包括了已经配置好的各种大数据服务环境的docker镜像 ...

  6. win下python环境搭建以及安装pip、django

    1. 安装python并配置 下载安装python,这里我下载的是python2.7,听说2.7比较好用 地址:https://www.python.org/downloads/source/ 记住你 ...

  7. www.pantom.top

    新建小站 https://www.pantom.top

  8. 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 ...

  9. java 8 更优雅的判断是否为空

    public static int getLength(String text) { // Java 8 return Optional.ofNullable(text).map(String::le ...

  10. SDR软件无线电知识要点(三)EVM

    SDR软件无线电知识要点(三)EVM 信号质量如何评估 Noise Figure (NF) or sensitivity and Error Vector Magnitude (EVM) provid ...