目錄
1、賦值
?2、賦值的分類——引用賦值、值賦值
1) 不可變對象引用賦值——字符串、數值、元組等
2)可變對象引用賦值——列表、集合、字典
3)可變與不可變對象的引用賦值內部分析
4)在py文件中,和作用域有關,如在同一個函數中的相同值的變量是相等的,即值相等,地址也相等
3、深拷貝與淺拷貝
4、循環——序列和非序列的循環中進行元素的修改
?
1、賦值
# 賦值包含多種賦值方式,一般賦值、元組賦值、序列賦值、解包賦值
a = "long"
b,c = "1",2
d,e,f,g = "long"
h,*i = "long"
print(a)
print(b)
print(c)
print(d)
print(e)
print(f)
print(g)
print(h)
print(i)
long
1
2
l
o
n
g
l
['o', 'n', 'g']
當使用一個
*
前綴變量的時候,表示將序列中對應的元素全部收集到一個列表中(注意,總是一個列表),這個列表名為*
開頭的那個變量名。*
號可以出現在任意位置處,只要賦值的時候能前后對應位置關系即可。注意其中的幾個關鍵字:序列、對應的元素、列表
- 序列意味著可以是列表、元組、字符串等等
- 列表意味著只要收集不報錯,賦值給解包變量的一定是一個列表
- 對應的元素意味著可能收集到0或任意個元素到列表。
不管如何,收集的結果總是列表,只不過可能是空列表或者只有一個元素的列表。
?
兩個注意事項:
- 因為序列解包是根據元素位置來進行賦值的,所以不能出現多個解包變量
- 如果將序列直接賦值給單個解包變量時(即沒有普通變量),這個解包變量必須放在列表或元組中
a,*b,c,*d = L # 錯誤 *a = L # 錯誤 [*a] = L # 正確 (*a) = L # 正確
?2、賦值的分類——引用賦值、值賦值
引用賦值——指的是將內存地址賦值給變量來實現賦值
1) 不可變對象引用賦值——字符串、數值、元組等
a = 1000
b = a
a = 2000
前兩行b=a是將1000的地址賦值給b,即a和b都是指向值1000的內存地址。第三行a=2000是對a重新進行賦值,因為數值是不可改變的對象,因此會先開辟一個內存地址用于存儲2000,然后將a指向2000
不可變對象變量之間不會互相影響,即如果一開始兩個變量指向同一個內存地址,當其中一個變量的值發生了改變的時候,另一個變量不會受到影響
對于不可變對象,修改變量的值意味著在內存中要新創建一個數據對象
a = 10000
b = a
a = 20000>>> a,b
(20000, 10000)
2)可變對象引用賦值——列表、集合、字典
對于可變對象,比如列表,它是在"原處修改"數據對象的(注意加了雙引號)。比如修改列表中的某個元素,列表的地址不會變,還是原來的那個內存對象,所以稱之為"原處修改"。例如:
L1 = [111,222,333]
L2 = L1
L1[1] = 2222>>> L1,L2
([111, 2222, 333], [111, 2222, 333])
L2是通過引用賦值得到的值,值為可變對象列表,當對L1改變一個列表元素時,其列表的地址不會發生改變,因此其L2的值也會發生相應的改變
在L1[1]
賦值的前后,數據對象[111,222,333]
的地址一直都沒有改變,但是這個列表的第二個元素的值已經改變了。因為L1和L2都指向這個列表,所以L1修改第二個元素后,L2的值也相應地到影響。也就是說,L1和L2仍然是同一個列表對象[111,2222,333]
。
結論是:對于可變對象,變量之間是相互影響的。
3)可變與不可變對象的引用賦值內部分析
可變對象和不可變對象的賦值形式雖然一樣,但是修改數據時的過程不一樣。
對于不可變對象,修改數據是直接在堆內存中新創建一個數據對象。如圖:
對于可變對象,修改這個可變對象中的元素時,這個可變對象的地址不會改變,所以是"原處修改"的。但需要注意的是,這個被修改的元素可能是不可變對象,可能是可變對象,如果被修改的元素是不可變對象,就會創建一個新數據對象,并引用這個新數據對象,而原始的那個元素將等待垃圾回收器回收。
>>> L=[333,444,555]
>>> id(L),id(L[1])
(56583832, 55771984)
>>> L[1]=4444
>>> id(L),id(L[1])
(56583832, 55771952)
如圖所示:
4)在py文件中,和作用域有關,如在同一個函數中的相同值的變量是相等的,即值相等,地址也相等
3、深拷貝與淺拷貝
1)深拷貝
完全創建一個新的數據對象,不會受到其他變量的元素值變化的影響
2)淺拷貝,只是拷貝了第一層的元素,若第一層的元素是可變對象,則引用的是可變對象的地址,因此還是會受到其他變量的影響
# 引用賦值——只是得到了地址
print("賦值----------------------")
L = [1,2,[3,4,5]]
L2 = L
print("修改元素前-----------")
print(L)
print(L2)
print("修改元素后-----------")
L[0] = 0
print(L)
print(L2)
賦值----------------------
修改元素前-----------
[1, 2, [3, 4, 5]]
[1, 2, [3, 4, 5]]
修改元素后-----------
[0, 2, [3, 4, 5]]
[0, 2, [3, 4, 5]]?
print("淺拷貝------------------------")print("淺拷貝1------------------------")
# 淺拷貝——只是拷貝了第一層的元素,若為不可變元素,則會重新為元素創建一個新的數據對象,若為可變數據對象,則只是拷貝了地址
L = [1,2,[3,4,5],6]
L1 = L.copy()
print("修改不可變對象的元素前-----------")
print(L)
print(L1)
print("修改不可變對象的元素后-----------")
L[0] = 0
print(L)
print(L1)print("淺拷貝1------------------------")
L = [1,2,[3,4,5],6]
L1 = L.copy()
print("修改可變對象的元素前-----------")
print(L)
print(L1)
print("修改可變對象的元素后-----------")
L[2][0] = 0
print(L)
print(L1)print("淺拷貝2------------------------")
L = [1,2,[3,4,5]]
L3 = L[:]
print("修改元素前-----------")
print(L)
print(L3)
print("修改元素后-----------")
L[0] = 0
L[2][0] = 0
print(L)
print(L3)
?
淺拷貝------------------------
淺拷貝1------------------------
修改不可變對象的元素前-----------
[1, 2, [3, 4, 5], 6]
[1, 2, [3, 4, 5], 6]
修改不可變對象的元素后-----------
[0, 2, [3, 4, 5], 6]
[1, 2, [3, 4, 5], 6]
淺拷貝1------------------------
修改可變對象的元素前-----------
[1, 2, [3, 4, 5], 6]
[1, 2, [3, 4, 5], 6]
修改可變對象的元素后-----------
[1, 2, [0, 4, 5], 6]
[1, 2, [0, 4, 5], 6]
淺拷貝2------------------------
修改元素前-----------
[1, 2, [3, 4, 5]]
[1, 2, [3, 4, 5]]
修改元素后-----------
[0, 2, [0, 4, 5]]
[1, 2, [0, 4, 5]]
from copy import deepcopy
print("深拷貝------------------------")
L = [1,2,[3,4,5]]
L1 = deepcopy(L)
L[0] = 0
L[2][0] = 0
print(L)
print(L1)
深拷貝------------------------
[0, 2, [0, 4, 5]]
[1, 2, [3, 4, 5]]
一般我們使用到的都是淺拷貝
4、循環——序列和非序列的循環中進行元素的修改
https://www.cnblogs.com/f-ck-need-u/p/10129317.html
1)列表進行原地修改時(L+=[val1,val2]),進行后面的迭代時,進行迭代的是修改后的列表,因為for是一個迭代器,使用的是next,即通過索引進行的,因此列表原地修改會導致元素出現奇怪的現象
為了避免這種情況,我們對列表進行修改時,建議生成一個新的列表對象來進行存放
L = ['a','b','c','d','e']## 原處修改列表,新元素f、g也會被迭代
for i in L:if i in "de":L += ["f", "g"]print(i)## 創建新列表,新元素f、g不會被迭代
for i in L:if i in "de":L = L + ["f", "g"]print(i)
這個for迭代器在迭代剛開始的時候,先找到L所指向的迭代對象,即內存中的[1,2,3,4]
。如果迭代過程中如果L變成了一個集合,或另一個列表對象,for的迭代并不會收到影響。但如果是在原處修改這個列表,那么迭代將會收到影響,例如新增元素也會被迭代到。
這里通過列表索引來進行元素的遍歷和修改即可避免上面的情況
2)迭代一個列表,迭代過程中刪除一個列表元素。
L = ['a','b','c','d','e']
for i in L:if i in "bc":L.remove(i)print(i)print(L)
輸出的結果將是:
b
['a', 'c', 'd', 'e']
這個for循環的本意是想刪除b、c元素,但結果卻只刪除了b。通過結果可以發現,c根本就沒有被for迭代。之所以會這樣,是因為迭代到b的時候,滿足if條件,然后刪除了列表中的b元素。正因為刪除操作,使得列表中b后面的元素整體前移一個位置,也就是c元素的索引位置變成了index=1,而index=1的元素已經被for迭代過(即元素b),使得c幸運地逃過了for的迭代。
3)迭代的是字典或者集合時,雖然兩者都是可變序列,但是時無序的,因此在迭代的過程中,是不允許字典或者集合發生改變的,否則會報錯
D = {'a':1,'b':2,'c':3,'d':4,'e':5}for i in D:if i in "bc":del D[i]print(i)print(D)
報錯:
b
Traceback (most recent call last):File "g:/pycode/lists.py", line 12, in <module>for i in D:
RuntimeError: dictionary changed size during iteration
S = {'a','b','c','d','e'}for i in S:if i in "bc":S.remove(i)print(i)print(S)
報錯:
b
Traceback (most recent call last):File "g:/pycode/lists.py", line 4, in <module>for i in L:
RuntimeError: Set changed size during iteration
若想修改字典的話,我們可以復制一份數據對象作為副本,然后將副本進行迭代,將原來的字典或集合作為修改對象?
D = {'a':1,'b':2,'c':3,'d':4,'e':5}for i in D.copy():if i in "bc":D.pop(i)print(i)
print(D)S = {'a','b','c','d','e'}for i in S.copy():if i in "bc":S.remove(i)print(i)
print(S)
?注意:在進行可變對象數據對象的迭代與修改時,我們只需要將迭代對象和修改對象分開就不會出現上述的錯誤。