heidern0612 发表于 2018-12-15 19:26:03

【赋值“=”的真正意义--值传递和引用传递、深浅拷贝】

本帖最后由 heidern0612 于 2018-12-21 08:23 编辑

写心得的过程都是自我思考的过程,借鉴了很多论坛前辈和互联网大佬的经验,仓促惶恐间难免漏洞百出,如有错误,恳请指出,不胜感激。


声明:本帖内容不回复不影响阅读

这几天看书发现个有趣的例子,琢磨了挺长时间,有点超纲,不扯这个例子,先上3个简单的例子热热身:

例1:不可变对象赋值
a = 1
b = a
b = a+1

最后a和b的值一样吗?
**** Hidden Message *****


例2:可变对象赋值
a=
b=a
a=2

最后a和b的值一样吗?
**** Hidden Message *****


例3:直接赋值
a=
b=
a=2

最后a和b的值一样吗?
**** Hidden Message *****



从以上3个例子我们可以看出,大多数时候,我可能只是想给a、b初始化相同的值,才用了b=a,在使用的过程中,我并不希望a修改后对b造成影响,或者修改b后,对a造成影响。

但从例1和2来看,b=a,有可能互不影响,有可能会相互影响,要视a的具体值的情况。

我们应当如何理解这种情况,以至不会因为理解上的模糊不清,影响我们实际编程呢?

例1: b=a,实际是将a指向的对象的引用赋值给b,这样a和b就指向了同一个对象。再执行a+1,相当于把a的对象+1,也就是100+1=101,对象变了,b自然也就变了。

例2: 执行a=2,修改的是对象第一个元素的值,a和b所指向的还是这个对象,对象本身变化了,a和b变量看到的值也就都变化了。

例3:执行完a=2后,a和b本身指向发生了变化,变成了指向不同的对象,自然修改a不会影响b。

从以上分析我们可以知道,b=a后,a、b是否会相互影响,关键在于修改的是不是对象本身的内容。

如果修改的是对象本身的内容,则两者会相互影响;如果是创建新对象后直接赋值给上述某个变量,则两者不会相互影响。




下面是我这几天看到的一个例子:

def pattern(obj):
    print("原值为:",obj)
    obj += obj
   
print("******值传递例子******")
string1 = "我的值不会改变"
pattern(string1)#函数调用
print(f"函数调用后: {string1}")

print("\n\n")

print("******引用传递例子******")
list1 = ["我的值会改变"]
pattern(list1)#函数调用
print(f"函数调用后: {list1}")

看的云里雾里不要紧,先引入下值传递和引用传递的概念:

值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值。

引用传递:也称地址传递,在方法调用时,实际上是把参数的引用(传的是地址,而不是参数的值)传递给方法中对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。


由上面函数的例子可以看出,python中对一个函数可以传递参数,但是如何分辨是值传递还是引用传递,不是程序员手动控制的,而是python根据你传入的数据对象,自动识别的。

当你传入的参数对象是可变对象,如列表,字典,这个时候就是引用传递,如果参数在函数体内被修改,那么源对象也会被修改。

当你传入的参数对象是不可变的对象:数字,元组,字符串时,就是值传递。源对象是不会改变的。

简单点就一句话:可变对象为引用传递,不可变对象为值传递。



实际上Python参数传递采用的肯定是“传对象引用”的方式,这种方式相当于传值和传引用的一种综合。

如果函数收到的是一个可变对象(比如字典或者列表)的引用,就能修改对象的原始值——相当于通过“传引用”来传递对象;

如果函数收到的是一个不可变对象(比如数字、字符或者元组)的引用,就不能直接修改原始对象——相当于通过“传值'来传递对象。

最后附上提到的两个概念:

1.可变对象:
标准定义:如果对象的值是可以修改的,则称该对象为可变对象
包含:自定义类型、列表(list)、字典(dict)、可变集合(set)
特征:执行类似a=后,再执行a=99,实际上是这个对象第一个元素的值改为99;当然,如果是执行a=后,再执行a=,就是新建对象后赋值给变量a


2.不变对象:
标准定义:如果对象的值不可以修改,则称为不可变对象
包含:数字类型(int、long、float、complex、bool)、字符串类型(str、unicode)、元组(tuple)、不可变集合(frozenset)
特征:执行类似a=3后,再执行a=4,实际上不是将3这个对象所在的内存的值改为4,而是在内存中新建一个对象4,再赋值给变量a




------------------------------------------------------------------------------------------------------------------------------------------------

既然Python只允许引用传递,那有没有办法可以让两个变量不再指向同一内存地址呢?

答案是有,这就涉及到浅拷贝(copy)和深拷贝(deepcopy)概念。


还是先举一个栗子:




例①:不使用copy的复制,简单理解为复制level 0级,实际上这个根本称不上复制,最多算是重命名。



我简单的赋值,两者所指向的内存地址是一样的,a列表append个值,b也跟着变了。


这并不是我想要的结果,于是我想到了老师介绍的copy方法。


例②:使用copy以及copy附带的深拷贝,简单理解Copy为level1,DeepCopy理解为level2,或将浅拷贝理解为一层复制也可以。



从图中我们可以看出,a和b简单的level 0 赋值拷贝已经不能满足我们的要求了,我import了copy模块,这里copy后内存地址果然变了,deepcopy似乎也达到了相同的效果。

从内存地址上看,好像Copy和DeepCopy没什么区别,都是复制了一份相同的原值,改变原值并不会影响相应Copy和DeepCopy的值。


那么Copy和DeepCopy有什么区别呢?

例③:Copy和DeepCopy的区别

我们简单弄个二级列表,重新用Copy和DeepCopy复制下a列表。


从图中清晰明了可以看出,二级列表内包含的元素1列表Copy和DeepCopy的内存值不同了。

我们append一个新元素进去,重新看下区别:



所以,以上可以看出,Copy方法实现的只是简单的一层复制,而DeepCopy实现的则是完全的深度拷贝。

这就是以上二者的区别,实际上切片的复制也只是简单的浅拷贝(level 1),无法实现二次及以上的复制。

如图:




xytelent 发表于 2018-12-23 16:48:43

312312321

萌王 发表于 2019-2-25 11:31:17

小白,表示看不懂,先收藏,等我下次回头来看

Swarm 发表于 2019-3-2 15:25:43

回复看隐藏,不然研究不出来{:5_109:}

我刀锋偏冷 发表于 2019-3-8 17:16:23

回复看影藏

天天动听 发表于 2019-3-12 09:31:53

?

88888 发表于 2019-3-24 15:57:23

不一样

My_A 发表于 2019-3-24 19:04:43

666

2397439593 发表于 2020-3-23 16:53:56

一样

小小小掰掰 发表于 2021-4-1 15:29:44

值是一样的
页: [1]
查看完整版本: 【赋值“=”的真正意义--值传递和引用传递、深浅拷贝】