马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 heidern0612 于 2018-12-21 08:23 编辑
写心得的过程都是自我思考的过程,借鉴了很多论坛前辈和互联网大佬的经验,仓促惶恐间难免漏洞百出,如有错误,恳请指出,不胜感激。
[b]
声明:本帖内容不回复不影响阅读[/b]
这几天看书发现个有趣的例子,琢磨了挺长时间,有点超纲,不扯这个例子,先上3个简单的例子热热身:
例1:不可变对象赋值
最后a和b的值一样吗?
例2:可变对象赋值
最后a和b的值一样吗?
例3:直接赋值
最后a和b的值一样吗?
从以上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[0]=2,修改的是对象[1]第一个元素的值,a和b所指向的还是这个对象,对象本身变化了,a和b变量看到的值也就都变化了。
例3:执行完a[0]=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=[1,2,3]后,再执行a[0]=99,实际上是这个对象第一个元素的值改为99;当然,如果是执行a=[1,2,3]后,再执行a=[99,2,3],就是新建对象[99,2,3]后赋值给变量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一个新元素[4]进去,重新看下区别:
所以,以上可以看出,Copy方法实现的只是简单的一层复制,而DeepCopy实现的则是完全的深度拷贝。
这就是以上二者的区别,实际上切片的复制也只是简单的浅拷贝(level 1),无法实现二次及以上的复制。
如图:
|