Python 函数参数引用(传值 or 传址)copy和deepcopy

来源:互联网 发布:mysql教程pdf 编辑:程序博客网 时间:2024/06/02 03:17

博文结构:
1、传值、传址的概念和区别
2、python 参数传递的方式
3、copy和deepcopy 的使用
4、python的数据存储方式

传值、传址的概念和区别:

传值:被调函数局部变量改变不会影响主调函数局部变量
传址:被调函数局部变量改变会影响主调函数局部变量

传值就是传入一个参数的值,传址就是传入一个参数的地址,也就是内存的地址(相当于指针)。他们的区别是如果函数里面对传入的参数重新赋值,函数外的全局变量是否相应改变,用传值传入的参数是不会改变的,用传址传入就会改变

a=1def f(b):    b=2    return bf(a)print a>>2>>1

在这段代码里面,首先声明a的值为1,把a作为参数传入到函数f里面,函数f里面对b重新赋值为2,如果是传值的形式传入a的话,a的值是不会变的,依然为1,如果以传址的形式(但是在python中这个不是程序员能决定的)传入a,a就会变成2。这个就是传值和传址的区别。

python 参数传递的方式

Python是不允许程序员选择采用传值还是传址的。Python参数传递采用的肯定是“传对象引用”的方式。实际上,这种方式相当于传值和传址的一种综合。

如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值——相当于传址。如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用(其实也是对象地址!!!),就不能直接修改原始对象——相当于传值。

所以python的传值和传址是比如根据传入参数的类型来选择的

传值的参数类型:数字,字符串,元组(immutable)

传址的参数类型:列表,字典(mutable)

a=1def f(b):    b=b+1    return bf(a)print a>>2>>1

这段代码里面,因为a是数字类型,所以是传值的方式,a的值并不会变,输出为1。

a=[1]def f(b):    b[0]+=1    return bf(a)print a>>[2]>>[2]

这段代码里面,因为a的类型是列表,所以是传址的形式,a[0]的值会改变,输出为[2]。

copy和deepcopy 的使用

不止是函数里面,函数外面的引用也同样遵循这个规则:

a=1b=aa=2print a,ba=[1]b=aa[0]=2print a,b>>2,1>>[2],[2]

在python中b=a的操作,如果a是数字、字符串、元组,程序执行的操作会新建一个b变量,然后a的值复制给b。

如果a是字典或者列表的话,程序执行的操作并不是新建一个b变量,然后a的值复制给b,而是新建一个b变量,把b的值指向a,也就是相当于在c语言里面的新建一个指向a的指针。所以当a的值发生改变时,b的值会相应改变。

copy:

但是,当我们想新建一个与a的值相等的b变量,同时b的值与a的值没有关联时,要怎么做?这时就用到copy与deepcopy了

import copya=[1,2,3]b=aa.append(4)print a,ba=[1,2,3]b=copy.copy(a)a.append(4)print a,b>>[1,2,3,4],[1,2,3,4]>>[1,2,3,4],[1,2,3]

这里用了copy来让b与a相等,后面如果修改了a的值,b的值并不会改变。看来copy已经可以实现我们上面的提到的需求了,那么deepcopy又有什么用?

deepcopy:

a=[1,[1,2],3]b=copy.copy(a)a[1].append(4)print a,b>>[1,[1,2,4],3],[1,[1,2,4],3]

这样的结果明显不是我们想要的答案。

当列表或字典参数里面的值是列表或字典时,copy并不会复制参数里面的列表或字典,这时就要用到deepcopy了

import copya=[1,[1,2],3]b=copy.deepcopy(a)a[1].append(4)print a,b>>[1,[1,2,4],3],[1,[1,2],3]

下面我将从python的存储机制上解释这个问题。

###python的数据存储方式

Python 存储变量的方法跟其他 OOP 语言不同。它与其说是把值赋给变量,不如说是给变量建立了一个到具体值的 reference。

当在 Python 中 a = something 应该理解为给 something 贴上了一个标签 a。当再赋值给 a 的时候,就好象把 a 这个标签从原来的 something 上拿下来,贴到其他对象上,建立新的 reference。 这就解释了一些 Python 中可能遇到的诡异情况:

>> a = [1, 2, 3]>>> b = a>>> a = [4, 5, 6] //赋新的值给 a>>> a[4, 5, 6]>>> b[1, 2, 3]# a 的值改变后,b 并没有随着 a 变>>> a = [1, 2, 3]>>> b = a>>> a[0], a[1], a[2] = 4, 5, 6 //改变原来 list 中的元素>>> a[4, 5, 6]>>> b[4, 5, 6]# a 的值改变后,b 随着 a 变了

上面两段代码中,a 的值都发生了变化。区别在于,第一段代码中是直接赋给了 a 新的值(从 [1, 2, 3] 变为 [4, 5, 6]);而第二段则是把 list 中每个元素分别改变。

而对 b 的影响则是不同的,一个没有让 b 的值发生改变,另一个变了。怎么用上边的道理来解释这个诡异的不同呢?

首次把 [1, 2, 3] 看成一个物品。a = [1, 2, 3] 就相当于给这个物品上贴上 a 这个标签。而 b = a 就是给这个物品又贴上了一个 b 的标签。
这里写图片描述
第一种情况:

a = [4, 5, 6] 就相当于把 a 标签从 [1 ,2, 3] 上撕下来,贴到了 [4, 5, 6] 上。

在这个过程中,[1, 2, 3] 这个物品并没有消失。 b 自始至终都好好的贴在 [1, 2, 3] 上,既然这个 reference 也没有改变过。 b 的值自然不变。

这里写图片描述
第二种情况:

a[0], a[1], a[2] = 4, 5, 6 则是直接改变了 [1, 2, 3] 这个物品本身。把它内部的每一部分都重新改装了一下。内部改装完毕后,[1, 2, 3] 本身变成了 [4, 5, 6]。

而在此过程当中,a 和 b 都没有动,他们还贴在那个物品上。因此自然 a b 的值都变成了 [4, 5, 6]。

搞明白这个之后就要问了,对于一个复杂对象的浅copy,在copy的时候到底发生了什么?
再看一段代码:

>>> import copy>>> origin = [1, 2, [3, 4]]#origin 里边有三个元素:1, 2,[3, 4]>>> cop1 = copy.copy(origin)>>> cop2 = copy.deepcopy(origin)>>> cop1 == cop2True>>> cop1 is cop2False #cop1 和 cop2 看上去相同,但已不再是同一个object>>> origin[2][0] = "hey!" >>> origin[1, 2, ['hey!', 4]]>>> cop1[1, 2, ['hey!', 4]]>>> cop2[1, 2, [3, 4]]#把origin内的子list [3, 4] 改掉了一个元素,观察 cop1 和 cop2

学过Docker的人应该对镜像这个概念不陌生,我们可以把镜像的概念套用在copy上面。

概念图如下:
这里写图片描述

copy对于一个复杂对象的子对象并不会完全复制,什么是复杂对象的子对象呢?就比如序列里的嵌套序列,字典里的嵌套序列等都是复杂对象的子对象。对于子对象,python会把它当作一个公共镜像存储起来,所有对他的复制都被当成一个引用,所以说当其中一个引用将镜像改变了之后另一个引用使用镜像的时候镜像已经被改变了。

所以说看这里的origin[2],也就是 [3, 4] 这个 list。根据 shallow copy 的定义,在 cop1[2] 指向的是同一个 list [3, 4]。那么,如果这里我们改变了这个 list,就会导致 origin 和 cop1 同时改变。这就是为什么上边 origin[2][0] = “hey!” 之后,cop1 也随之变成了 [1, 2, [‘hey!’, 4]]。

而deepcopy概念图如下:
这里写图片描述

deepcopy的时候会将复杂对象的每一层复制一个单独的个体出来。
这时候的 origin[2] 和 cop2[2] 虽然值都等于 [3, 4],但已经不是同一个 list了。即我们寻常意义上的复制。

参考文章:

1、 http://iaman.actor/blog/2016/04/17/copy-in-python


2、http://www.cnblogs.com/CheeseZH/p/5165283.html
原创粉丝点击