在之前的文章 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. mongodb学习一(使用mongoResposity)

    最近公司做一个项目用到了mongodb,下面来介绍一下MongoRepository接口. 大家可以类比Hibernate的jpa,MongoRepository是一个springdata提供的一个有 ...

  2. CentOS6安装各种大数据软件 第二章:Linux各个软件启动命令

    相关文章链接 CentOS6安装各种大数据软件 第一章:各个软件版本介绍 CentOS6安装各种大数据软件 第二章:Linux各个软件启动命令 CentOS6安装各种大数据软件 第三章:Linux基础 ...

  3. 面试被问到SPI总结

    SPI驱动框架 枚举过程 drivers/spi/spi.c: spi_register_board_info /* 对于每一个spi_master,调用spi_match_master_to_boa ...

  4. C语言中赋值表达式的返回值是什么?

    我们或多或少都有过,或者见过将赋值表达式参与运算的情况.这通常会伴随着一些意想不到的问题.今天我就见到了一段奇怪的代码: #include<stdio.h> int main() { ; ...

  5. scala (8) 模糊匹配

    object MatchDemo { /** * 定义偏函数用PartialFunction来表示 * PartialFunction[T1,T2]要求传入一个参数T1,T2代表返回的类型. * 偏函 ...

  6. php安装redis拓展

    1. 查看是否安装redis库 查看是否安装redis库了.可以通过下面2种方式查看. phpinfo()是否能输出redis的加载信息 在命令行执行`php -m` 输出gd 2. 安装redis库 ...

  7. Oracle Data Provider for .NET Support for Microsoft .NET Core

    Oracle Data Provider for .NET Support for Microsoft .NET Core的官方地址,记录下来,按照官方描述,会在2017年底左右发布,暂时还没有看到相 ...

  8. 20155321实验二 Java面向对象程序设计

    实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模式 实验步骤 单元测试 三种代码 伪代码:使用自然语言来显示设 ...

  9. 20155323 2016-2017-2 《Java程序设计》第10周学习总结

    20155323 2016-2017-2 <Java程序设计>第10周学习总结 教材学习内容总结 网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据. 狭义的网络编程范畴就是程序 ...

  10. noone is not in the sudoers file ubuntu

      Login as root or su to get root prompt type visudo an editor will open find a line says root ALL=( ...