在之前的文章 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. 梯度下降法&牛顿法

    梯度下降法 在机器学习任务中,需要最小化损失函数\(L(\theta)\),其中\(\theta\)是要求解的模型参数.梯度下降法是一种迭代方法,用到损失函数的一阶泰勒展开.选取初值\(\theta ...

  2. 同步工具类-----循环栅栏:CyclicBarrier

    import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; impor ...

  3. Html5 Canvas 实现图片合成

    多个图片合成一张 <!doctype html> <html> <head> <meta charset="utf-8"> < ...

  4. input的默认样式去除

    outline:none;-----可去除input=text,的输入框输入时的亮边.

  5. js如何获取键盘高度

    在移动端或混合app开发中,js如何获取键盘高度,直接贴上代码吧 input是一个html input 标签 var timer = { id:null, run:function (callback ...

  6. Unity判断鼠标是否在UI(UGUI)上

    "EventSystem.current.IsPointerOverGameObject()" UI和3D场景同时都需要响应触摸事件,如果同时响应可能就会出现触摸UI的时候影响到了 ...

  7. 树莓3B+_安装vim

    第一步:访问源列表里的每个网址,并读取软件列表,然后保存在本地电脑. sudo apt-get update 第二步:安装vim sudo apt-get install vim 第三步:配置vim ...

  8. PTA基础编程题目集6-3简单求和 (函数题)

    6-3 简单求和 (10 分) 本题要求实现一个函数,求给定的N个整数的和. 函数接口定义: int Sum(int List[],int N) 其中给定整数存放在数组List[]中,正整数N是数组元 ...

  9. vue中组件间的传参

    1.父传子 父组件准备一个数据,通过自定义属性给子组件赋值,进行传递 在子组件中通过 props 属性来接收参数 <body> <div id="app"> ...

  10. 20155328 2016-2017-2 《Java程序设计》 课程总结

    20155328 2016-2017-2 <Java程序设计> 课程总结 目录 一.每周作业链接汇总 二.实验报告链接汇总 三.代码托管链接 四.课堂项目实践 五.课程收获与不足 六.问卷 ...