python雖然沒有指針的概念,但是對(duì)象、引用、地址這些內(nèi)容還是存在的,尤其是像列表對(duì)象、某類型對(duì)象的引用上,搞清楚對(duì)象變量的復(fù)制和賦值很有必要,不然容易出現(xiàn)“莫名其妙”的錯(cuò)誤。
認(rèn)清對(duì)象、引用、地址(直接以“=”賦值變量)
以列表為例
python中給一個(gè)變量a
賦值列表實(shí)際上是創(chuàng)建了一個(gè)列表對(duì)象,并將該列表的地址賦值給a
,而變量a此時(shí)就成為了這個(gè)列表對(duì)象的引用。當(dāng)用a
給變量b
賦值時(shí),其實(shí)只是把這個(gè)列表對(duì)象的地址賦值給了b,即a
和b
都成為了該對(duì)象的引用,因此直接對(duì)a
或b
進(jìn)行修改,都將使該對(duì)象發(fā)生變化,下面給出一段代碼示例:
a = ["abc","bcd","cde",1,2,3] #創(chuàng)建了一個(gè)對(duì)象并用變量a引用它
b = a #將a賦值給b,讓b和a都同時(shí)作為列表對(duì)象的引用
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
b[0] = 0 #通過引用b將列表第一個(gè)元素賦值為0
a.remove(3) #通過引用a將列表中第一個(gè)值為3的元素刪除
print("修改后:")
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
a = ["a","new","list"] #創(chuàng)建一個(gè)新的對(duì)象賦值給a,即讓變量a成為新列表對(duì)象的引用
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
運(yùn)行結(jié)果為:
a= ['abc', 'bcd', 'cde', 1, 2, 3] b= ['abc', 'bcd', 'cde', 1, 2, 3]
a: <class 'list'> 2072617426752 b: <class 'list'> 2072617426752
修改后:
a= [0, 'bcd', 'cde', 1, 2] b= [0, 'bcd', 'cde', 1, 2]
a: <class 'list'> 2072617426752 b: <class 'list'> 2072617426752
a= ['a', 'new', 'list'] b= [0, 'bcd', 'cde', 1, 2]
a: <class 'list'> 2072617436224 b: <class 'list'> 2072617426752
可以看到,一開始a和b共同代表了創(chuàng)建的列表["abc","bcd","cde",1,2,3]
,兩個(gè)變量輸出的內(nèi)容、類型、地址都是完全一樣的,用兩個(gè)變量均可以對(duì)該列表進(jìn)行修改,即a
和b
會(huì)互相影響。而后面給a
另外賦值一個(gè)新的列表對(duì)象后,a
和b
就分別引用不同的對(duì)象了,兩者在之后就不會(huì)互相影響了。
以自定義類為例
對(duì)于python中的類,包括自定義的類,所創(chuàng)建的對(duì)象也是類似的,給變量賦值僅僅是將對(duì)象地址給到變量,讓變量成為該對(duì)象的引用,看下面的一段示例代碼和結(jié)果就很好理解了:
class AClass:
def __init__(self):
self.alist = [1,2,3,"ab","cd"]
self.aString = "abcdef"
a = AClass()
b = a
print("a:",a)
print("a.alist:",a.alist)
print("a.aString:",a.aString)
print("b:",b)
print("b.alist:",b.alist)
print("b.aString:",b.aString)
b.aString = 0
b.alist = 0
print("修改后:")
print("a:",a)
print("a.alist:",a.alist)
print("a.aString:",a.aString)
運(yùn)行結(jié)果為:
a: <__main__.AClass object at 0x000002708399D4F0>
a.alist: [1, 2, 3, 'ab', 'cd']
a.aString: abcdef
b: <__main__.AClass object at 0x000002708399D4F0>
b.alist: [1, 2, 3, 'ab', 'cd']
b.aString: abcdef
修改后:
a: <__main__.AClass object at 0x000002708399D4F0>
a.alist: 0
a.aString: 0
可以看到,程序僅創(chuàng)建了一個(gè)自定義類的對(duì)象(地址為0x000002708399D4F0
),變量a
和b
均是該對(duì)象的引用,所以通過b
對(duì)其屬性更改后,通過a
輸出內(nèi)容便是更改后的內(nèi)容。
綜上,編程過程中一定要注意,尤其在很多時(shí)候我們可能需要臨時(shí)使用并更改對(duì)象中的某些值但并不希望真的修改它以至于影響下次使用它,尤其針對(duì)某些其他語言轉(zhuǎn)過來的人,可能習(xí)慣于直接用變量賦值,然后用新變量當(dāng)臨時(shí)變量來進(jìn)行使用和修改,殊不知這樣會(huì)直接影響原始對(duì)象的數(shù)據(jù),造成一些未知的錯(cuò)誤
淺拷貝(copy()函數(shù)的使用)
為了解決上面說的問題,有時(shí)候我們確實(shí)需要臨時(shí)改變一下對(duì)象中的內(nèi)容用于計(jì)算、統(tǒng)計(jì)等功能,但同時(shí)希望保持原有對(duì)象的數(shù)據(jù)不變,以便后面的流程中再次使用,所以就需要用到對(duì)象的復(fù)制,而不是上面所說的變量的賦值。
python中對(duì)象的復(fù)制可以用標(biāo)準(zhǔn)庫copy
中的copy()
函數(shù),使用前需要導(dǎo)入該庫,示例如下:
import copy
a = copy.copy(b)
值得注意的是,淺拷貝僅拷貝對(duì)象本身的內(nèi)容并創(chuàng)建新對(duì)象,但是,對(duì)象中的對(duì)象即子對(duì)象仍然只是復(fù)制了地址而不是新創(chuàng)建同內(nèi)容的子對(duì)象,下面會(huì)詳細(xì)解釋這種情況:
無嵌套的情況
先說無嵌套的情況,這種情況比較簡(jiǎn)單,沿用上面以列表為例的代碼,僅作一處修改及將b=a
改為b=copy.copy()
,代碼如下:
print("testing copy")
a = ["abc","bcd","cde",1,2,3] #創(chuàng)建了一個(gè)對(duì)象并用變量a引用它
b = copy.copy(a) #將a引用的對(duì)象淺拷貝給b
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
b[0] = 0 #通過引用b將列表第一個(gè)元素賦值為0
a.remove(3) #通過引用a將列表中第一個(gè)值為3的元素刪除
print("修改后:")
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
a = ["a","new","list"] #創(chuàng)建一個(gè)新的對(duì)象賦值給a,即讓變量a成為新列表對(duì)象的引用
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
運(yùn)行結(jié)果為:
a= ['abc', 'bcd', 'cde', 1, 2, 3] b= ['abc', 'bcd', 'cde', 1, 2, 3]
a: <class 'list'> 2370431356672 b: <class 'list'> 2370431370624
修改后:
a= ['abc', 'bcd', 'cde', 1, 2] b= [0, 'bcd', 'cde', 1, 2, 3]
a: <class 'list'> 2370431356672 b: <class 'list'> 2370431370624
a= ['a', 'new', 'list'] b= [0, 'bcd', 'cde', 1, 2, 3]
a: <class 'list'> 2370431369344 b: <class 'list'> 2370431370624
從結(jié)果可以看出,在復(fù)制(淺拷貝)之后,a
和b
雖然他們的內(nèi)容是一樣的,但是分別引用的兩個(gè)不同的對(duì)象,各自的操作也互不影響。
有嵌套的情況
相對(duì)于無嵌套的情況,有嵌套的情況指的就是對(duì)象中還包含有其他對(duì)象,例如,一個(gè)列表中,其中的元素也有列表;或者一個(gè)類的對(duì)象中還包含有別的對(duì)象,例如上面以自定義類為例的代碼中,AClass類的對(duì)象中,其成員屬性alist也是一個(gè)對(duì)象
在這種情況下,淺拷貝會(huì)出現(xiàn)什么情況呢?通過下面的代碼示例就很容易理解了。
以列表為例
# 創(chuàng)建一個(gè)列表,列表中第2個(gè)和最后一個(gè)元素分別是一個(gè)字符串列表和整數(shù)列表
a = ["abc",["aa","bb","cc"],"cde",[1,2,3]]
b = copy.copy(a) #將a引用的對(duì)象淺拷貝給b
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
print("id of a[0]:",id(a[0]),"id of a[1]:",id(a[1]))
print("id of b[0]:",id(b[0]),"id of b[1]:",id(b[1]))
b[0] = 0 #通過引用b將列表第一個(gè)元素(字符串"abc")修改為0
a.pop(3) #通過引用a將列表中索引為3的元素(即[1,2,3]整數(shù)列表)刪除
b[1][0] = "new" #通過引用b將列表第二個(gè)元素所代表的字符串列表的第一個(gè)字符串"aa"修改為"new"
print("修改后:")
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
print("id of a[0]:",id(a[0]),"id of a[1]:",id(a[1]))
print("id of b[0]:",id(b[0]),"id of b[1]:",id(b[1]))
運(yùn)行結(jié)果為:
a= ['abc', ['aa', 'bb', 'cc'], 'cde', [1, 2, 3]] b= ['abc', ['aa', 'bb', 'cc'], 'cde', [1, 2, 3]]
a: <class 'list'> 1928029320000 b: <class 'list'> 1928028905664
id of a[0]: 1927980678448 id of a[1]: 1928029321024
id of b[0]: 1927980678448 id of b[1]: 1928029321024
修改后:
a= ['abc', ['new', 'bb', 'cc'], 'cde'] b= [0, ['new', 'bb', 'cc'], 'cde', [1, 2, 3]]
a: <class 'list'> 1928029320000 b: <class 'list'> 1928028905664
id of a[0]: 1927980678448 id of a[1]: 1928029321024
id of b[0]: 140728955460320 id of b[1]: 1928029321024
從結(jié)果可以看出,在淺拷貝后,a
和b
的id是不一樣的,意味著他們分別引用的是兩個(gè)對(duì)象。通過后面的操作也可以看出,將a
引用的對(duì)象最后一個(gè)元素刪除,將b
引用的對(duì)象的第一個(gè)元素修改為0,這兩個(gè)操作互不影響。但同時(shí)也可以發(fā)現(xiàn),a[0]
和b[0]
,a[1]
和b[1]
的id是一樣的,這意味著a[1]
和b[1]
(注意這倆是子列表對(duì)象,而不是字符串或整數(shù))引用的是同一個(gè)列表對(duì)象。所以:
1)當(dāng)執(zhí)行b[0]=0
時(shí),實(shí)際上是創(chuàng)建了一個(gè)新的值為0的Int對(duì)象(這就是所謂的“python中一切皆為對(duì)象”)并將其地址賦值給b[0],所以在這之后,我們發(fā)現(xiàn)b[0]
的id已經(jīng)發(fā)生了變化,跟a[0]
的id不再一樣了。
2)當(dāng)執(zhí)行b[1][0]="new"
時(shí),將b[1]
所指向的子列表對(duì)象的第一個(gè)元素修改為"new"(即創(chuàng)建新對(duì)象并將地址賦值給b[1][0]
即b[1]
的第一個(gè)元素),而由于b[1]
和a[1]
是共同指向或引用地址為1928029321024
的列表對(duì)象,所以a
和b
中的第二個(gè)元素(字符串子列表)共同發(fā)生了變化。
以自定義類為例
跟前面完全相同的類定義:
class AClass:
def __init__(self):
self.alist = [1,2,3,"ab","cd"]
self.aString = "abcdef"
執(zhí)行的示例代碼基本與上述以自定義類為例的示例代碼相同,僅修改兩處(見注釋):
a = AClass()
b = copy.copy(a)#修改等號(hào)賦值為淺拷貝
print("a:",a)
print("a.alist:",a.alist,id(a.alist))
print("a.aString:",a.aString)
print("b:",b)
print("b.alist:",b.alist,id(b.alist))
print("b.aString:",b.aString)
b.aString = 0
b.alist[0] = "a new"#僅修改alist屬性的第一個(gè)元素
print("修改后:")
print("a:",a)
print("a.alist:",a.alist,id(a.alist))
print("a.aString:",a.aString)
print("b:",b)
print("b.alist:",b.alist,id(b.alist))
print("b.aString:",b.aString)
運(yùn)行結(jié)果為:
a: <__main__.AClass object at 0x000001C0E77AC520>
a.alist: [1, 2, 3, 'ab', 'cd'] 1928028953216
a.aString: abcdef
b: <__main__.AClass object at 0x000001C0E77AC4C0>
b.alist: [1, 2, 3, 'ab', 'cd'] 1928028953216
b.aString: abcdef
修改后:
a: <__main__.AClass object at 0x000001C0E77AC520>
a.alist: ['a new', 2, 3, 'ab', 'cd'] 1928028953216
a.aString: abcdef
b: <__main__.AClass object at 0x000001C0E77AC4C0>
b.alist: ['a new', 2, 3, 'ab', 'cd'] 1928028953216
b.aString: 0
可以看到,運(yùn)用淺拷貝復(fù)制后,b
和a
內(nèi)容一樣,但是id是不一樣的,而a.alist
和b.alist
指向的列表id是一樣的,所以在后面的修改中對(duì)b.alist[0]
的修改,對(duì)a
和b
同時(shí)生效了,b.aString=0
則是創(chuàng)建了值為0的新對(duì)象賦值給了b.aString
,就只對(duì)b
生效了。
深拷貝(deepcopy()函數(shù)的使用)
如果想完全復(fù)制一個(gè)全新的對(duì)象及其所有的子對(duì)象,則需要用深拷貝函數(shù)deepcopy()
來完成,跟copy()
函數(shù)一樣,都來自copy
庫,導(dǎo)入和使用示例為:
import copy
a = copy.deepcopy(b)
沿用上面有嵌套的列表示例和自定義類示例,僅修改copy()
函數(shù)為deepcopy()
函數(shù),代碼如下:
import copy
print("以列表為例:")
a = ["abc",["aa","bb","cc"],"cde",[1,2,3]]
b = copy.deepcopy(a) #將a引用的對(duì)象淺拷貝給b
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
print("id of a[0]:",id(a[0]),"id of a[1]:",id(a[1]))
print("id of b[0]:",id(b[0]),"id of b[1]:",id(b[1]))
b[0] = 0 #通過引用b將列表第一個(gè)元素(字符串"abc")修改為0
a.pop(3) #通過引用a將列表中索引為3的元素(即[1,2,3]整數(shù)列表)刪除
b[1][0] = "new" #通過引用b將列表第二個(gè)元素所代表的字符串列表的第一個(gè)字符串"aa"修改為"new"
print("修改后:")
print("a=",a,"b=",b) #輸出a,b列表中的內(nèi)容
print("a:",type(a),id(a),"b:",type(b),id(b))#輸出a,b的類型和地址
print("id of a[0]:",id(a[0]),"id of a[1]:",id(a[1]))
print("id of b[0]:",id(b[0]),"id of b[1]:",id(b[1]))
print("以自定義類為例:")
class AClass:
def __init__(self):
self.alist = [1,2,3,"ab","cd"]
self.aString = "abcdef"
a = AClass()
b = copy.deepcopy(a)
print("a:",a)
print("a.alist:",a.alist,id(a.alist))
print("a.aString:",a.aString)
print("b:",b)
print("b.alist:",b.alist,id(b.alist))
print("b.aString:",b.aString)
b.aString = 0
b.alist[0] = "a new"
print("修改后:")
print("a:",a)
print("a.alist:",a.alist,id(a.alist))
print("a.aString:",a.aString)
print("b:",b)
print("b.alist:",b.alist,id(b.alist))
print("b.aString:",b.aString)
運(yùn)行結(jié)果如下:文章來源:http://www.zghlxwxcb.cn/news/detail-761722.html
以列表為例:
a= ['abc', ['aa', 'bb', 'cc'], 'cde', [1, 2, 3]] b= ['abc', ['aa', 'bb', 'cc'], 'cde', [1, 2, 3]]
a: <class 'list'> 2194904591232 b: <class 'list'> 2194905204032
id of a[0]: 2194856443120 id of a[1]: 2194904652928
id of b[0]: 2194856443120 id of b[1]: 2194904632960
修改后:
a= ['abc', ['aa', 'bb', 'cc'], 'cde'] b= [0, ['new', 'bb', 'cc'], 'cde', [1, 2, 3]]
a: <class 'list'> 2194904591232 b: <class 'list'> 2194905204032
id of a[0]: 2194856443120 id of a[1]: 2194904652928
id of b[0]: 140728955460320 id of b[1]: 2194904632960
以自定義類為例:
a: <__main__.AClass object at 0x000001FF0A82D580>
a.alist: [1, 2, 3, 'ab', 'cd'] 2194904589504
a.aString: abcdef
b: <__main__.AClass object at 0x000001FF0A82D670>
b.alist: [1, 2, 3, 'ab', 'cd'] 2194904591232
b.aString: abcdef
修改后:
a: <__main__.AClass object at 0x000001FF0A82D580>
a.alist: [1, 2, 3, 'ab', 'cd'] 2194904589504
a.aString: abcdef
b: <__main__.AClass object at 0x000001FF0A82D670>
b.alist: ['a new', 2, 3, 'ab', 'cd'] 2194904591232
b.aString: 0
結(jié)果就不做過多解釋了,一句話,深拷貝后得到的對(duì)象,跟之前的對(duì)象內(nèi)容完全一樣,但是完全沒有任何關(guān)聯(lián),任何更改都不影響原來的對(duì)象。當(dāng)然,這種做法簡(jiǎn)單粗暴,如果對(duì)象占用資源較多,深拷貝會(huì)增加資源的消耗,資深人員可以根據(jù)需要靈活使用深拷貝淺拷貝。文章來源地址http://www.zghlxwxcb.cn/news/detail-761722.html
到了這里,關(guān)于詳解python列表等對(duì)象的賦值和復(fù)制(淺拷貝copy()及深拷貝deepcopy()的使用區(qū)別與示例)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!