
深入探究Python中變量的拷貝和作用域問(wèn)題
在 python 中賦值語(yǔ)句總是建立對(duì)象的引用值,而不是復(fù)制對(duì)象。因此,python 變量更像是指針,而不是數(shù)據(jù)存儲(chǔ)區(qū)域,
這點(diǎn)和大多數(shù) OO 語(yǔ)言類似吧,比如 C++、java 等 ~
1、先來(lái)看個(gè)問(wèn)題吧:
在Python中,令values=[0,1,2];values[1]=values,為何結(jié)果是[0,[...],2]?
>>> values = [0, 1, 2]
>>> values[1] = values
>>> values
[0, [...], 2]
我預(yù)想應(yīng)當(dāng)是
[0, [0, 1, 2], 2]
但結(jié)果卻為何要賦值無(wú)限次?
可以說(shuō) Python 沒(méi)有賦值,只有引用。你這樣相當(dāng)于創(chuàng)建了一個(gè)引用自身的結(jié)構(gòu),所以導(dǎo)致了無(wú)限循環(huán)。為了理解這個(gè)問(wèn)題,有個(gè)基本概念需要搞清楚。
Python 沒(méi)有「變量」,我們平時(shí)所說(shuō)的變量其實(shí)只是「標(biāo)簽」,是引用。
執(zhí)行
values = [0, 1, 2]
的時(shí)候,Python 做的事情是首先創(chuàng)建一個(gè)列表對(duì)象 [0, 1, 2],然后給它貼上名為 values 的標(biāo)簽。如果隨后又執(zhí)行
values = [3, 4, 5]
的話,Python 做的事情是創(chuàng)建另一個(gè)列表對(duì)象 [3, 4, 5],然后把剛才那張名為 values 的標(biāo)簽從前面的 [0, 1, 2] 對(duì)象上撕下來(lái),重新貼到 [3, 4, 5] 這個(gè)對(duì)象上。
至始至終,并沒(méi)有一個(gè)叫做 values 的列表對(duì)象容器存在,Python 也沒(méi)有把任何對(duì)象的值復(fù)制進(jìn) values 去。過(guò)程如圖所示:
執(zhí)行
的時(shí)候,Python 做的事情則是把 values 這個(gè)標(biāo)簽所引用的列表對(duì)象的第二個(gè)元素指向 values 所引用的列表對(duì)象本身。執(zhí)行完畢后,values 標(biāo)簽還是指向原來(lái)那個(gè)對(duì)象,只不過(guò)那個(gè)對(duì)象的結(jié)構(gòu)發(fā)生了變化,從之前的列表 [0, 1, 2] 變成了 [0, ?, 2],而這個(gè) ? 則是指向那個(gè)對(duì)象本身的一個(gè)引用。如圖所示:
要達(dá)到你所需要的效果,即得到 [0, [0, 1, 2], 2] 這個(gè)對(duì)象,你不能直接將 values[1] 指向 values 引用的對(duì)象本身,而是需要吧 [0, 1, 2] 這個(gè)對(duì)象「復(fù)制」一遍,得到一個(gè)新對(duì)象,再將 values[1] 指向這個(gè)復(fù)制后的對(duì)象。Python 里面復(fù)制對(duì)象的操作因?qū)ο箢愋投悾瑥?fù)制列表 values 的操作是
values[:] #生成對(duì)象的拷貝或者是復(fù)制序列,不再是引用和共享變量,但此法只能頂層復(fù)制
所以你需要執(zhí)行
Python 做的事情是,先 dereference 得到 values 所指向的對(duì)象 [0, 1, 2],然后執(zhí)行 [0, 1, 2][:] 復(fù)制操作得到一個(gè)新的對(duì)象,內(nèi)容也是 [0, 1, 2],然后將 values 所指向的列表對(duì)象的第二個(gè)元素指向這個(gè)復(fù)制二來(lái)的列表對(duì)象,最終 values 指向的對(duì)象是 [0, [0, 1, 2], 2]。過(guò)程如圖所示:
往更深處說(shuō),values[:] 復(fù)制操作是所謂的「淺復(fù)制」(shallow copy),當(dāng)列表對(duì)象有嵌套的時(shí)候也會(huì)產(chǎn)生出乎意料的錯(cuò)誤,比如
問(wèn):此時(shí) a 和 b 分別是多少?
正確答案是 a 為 [8, [1, 9], 3],b 為 [0, [1, 9], 3]。發(fā)現(xiàn)沒(méi)?b 的第二個(gè)元素也被改變了。想想是為什么?不明白的話看下圖
正確的復(fù)制嵌套元素的方法是進(jìn)行「深復(fù)制」(deep copy),方法是
2、引用 VS 拷貝:
(1)沒(méi)有限制條件的分片表達(dá)式(L[:])能夠復(fù)制序列,但此法只能淺層復(fù)制。
(2)字典 copy 方法,D.copy() 能夠復(fù)制字典,但此法只能淺層復(fù)制
(3)有些內(nèi)置函數(shù),例如 list,能夠生成拷貝 list(L)
(4)copy 標(biāo)準(zhǔn)庫(kù)模塊能夠生成完整拷貝:deepcopy 本質(zhì)上是遞歸 copy
(5)對(duì)于不可變對(duì)象和可變對(duì)象來(lái)說(shuō),淺復(fù)制都是復(fù)制的引用,只是因?yàn)閺?fù)制不變對(duì)象和復(fù)制不變對(duì)象的引用是等效的(因?yàn)閷?duì)象不可變,當(dāng)改變時(shí)會(huì)新建對(duì)象重新賦值)。所以看起來(lái)淺復(fù)制只復(fù)制不可變對(duì)象(整數(shù),實(shí)數(shù),字符串等),對(duì)于可變對(duì)象,淺復(fù)制其實(shí)是創(chuàng)建了一個(gè)對(duì)于該對(duì)象的引用,也就是說(shuō)只是給同一個(gè)對(duì)象貼上了另一個(gè)標(biāo)簽而已。
L = [1, 2, 3]
D = {'a':1, 'b':2}
A = L[:]
B = D.copy()
print "L, D"
print L, D
print "A, B"
print A, B
print "--------------------"
A[1] = 'NI'
B['c'] = 'spam'
print "L, D"
print L, D
print "A, B"
print A, B
L, D
[1, 2, 3] {'a': 1, 'b': 2}
A, B
[1, 2, 3] {'a': 1, 'b': 2}
--------------------
L, D
[1, 2, 3] {'a': 1, 'b': 2}
A, B
[1, 'NI', 3] {'a': 1, 'c': 'spam', 'b': 2}
3、增強(qiáng)賦值以及共享引用:
x = x + y,x 出現(xiàn)兩次,必須執(zhí)行兩次,性能不好,合并必須新建對(duì)象 x,然后復(fù)制兩個(gè)列表合并
屬于復(fù)制/拷貝
x += y,x 只出現(xiàn)一次,也只會(huì)計(jì)算一次,性能好,不生成新對(duì)象,只在內(nèi)存塊末尾增加元素。
當(dāng) x、y 為list時(shí), += 會(huì)自動(dòng)調(diào)用 extend 方法進(jìn)行合并運(yùn)算,in-place change。
屬于共享引用
L = [1, 2]
M = L
L = L + [3, 4]
print L, M
print "-------------------"
L = [1, 2]
M = L
L += [3, 4]
print L, M
[1, 2, 3, 4] [1, 2]
-------------------
[1, 2, 3, 4] [1, 2, 3, 4]
4、python 從2.x 到3.x,語(yǔ)句變函數(shù)引發(fā)的變量作用域問(wèn)題
先看段代碼:
def test():
a = False
exec ("a = True")
print ("a = ", a)
test()
b = False
exec ("b = True")
print ("b = ", b)
在 python 2.x 和 3.x 下 你會(huì)發(fā)現(xiàn)他們的結(jié)果不一樣:
2.x:
a = True
b = True
3.x:
a = False
b = True
這是為什么呢?
因?yàn)?3.x 中 exec 由語(yǔ)句變成函數(shù)了,而在函數(shù)中變量默認(rèn)都是局部的,也就是說(shuō)
你所見到的兩個(gè) a,是兩個(gè)不同的變量,分別處于不同的命名空間中,而不會(huì)沖突。
具體參考 《learning python》P331-P332
知道原因了,我們可以這么改改:
def test():
a = False
ldict = locals()
exec("a=True",globals(),ldict)
a = ldict['a']
print(a)
test()
b = False
exec("b = True", globals())
print("b = ", b)
這個(gè)問(wèn)題在 stackoverflow 上已經(jīng)有人問(wèn)了,而且 python 官方也有人報(bào)了 bug。。。
數(shù)據(jù)分析咨詢請(qǐng)掃描二維碼
若不方便掃碼,搜微信號(hào):CDAshujufenxi
SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎(chǔ)用法到實(shí)戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無(wú)論 ...
2025-09-18MySQL 大表拆分與關(guān)聯(lián)查詢效率:打破 “拆分必慢” 的認(rèn)知誤區(qū) 在 MySQL 數(shù)據(jù)庫(kù)管理中,“大表” 始終是性能優(yōu)化繞不開的話題。 ...
2025-09-18CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫(kù)表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-18DSGE 模型中的 Et:理性預(yù)期算子的內(nèi)涵、作用與應(yīng)用解析 動(dòng)態(tài)隨機(jī)一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開始提取前,需先判斷 TIF 文件的類型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價(jià)值的專業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫(kù)表、Excel 表、 ...
2025-09-17Excel 導(dǎo)入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實(shí)戰(zhàn)應(yīng)用 在用 Python(如 pandas 庫(kù))處理 Excel 數(shù)據(jù)時(shí),“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗(yàn)與 t 檢驗(yàn):差異、適用場(chǎng)景與實(shí)踐應(yīng)用 在數(shù)據(jù)分析與統(tǒng)計(jì)學(xué)領(lǐng)域,假設(shè)檢驗(yàn)是驗(yàn)證研究假設(shè)、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專業(yè)操盤手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計(jì)劃中 rows 數(shù)量的準(zhǔn)確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計(jì)劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對(duì)象的 text 與 content:區(qū)別、場(chǎng)景與實(shí)踐指南 在 Python 進(jìn)行 HTTP 網(wǎng)絡(luò)請(qǐng)求開發(fā)時(shí)(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價(jià)值的核心操盤手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫(kù)表)是企業(yè)最基礎(chǔ)、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請(qǐng)求工具對(duì)比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請(qǐng)求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長(zhǎng)浮點(diǎn)數(shù)據(jù)的科學(xué)計(jì)數(shù)法問(wèn)題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長(zhǎng)浮點(diǎn)數(shù)據(jù)時(shí)的科學(xué)計(jì)數(shù)法問(wèn)題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務(wù)數(shù)據(jù)分析步驟的落地者與價(jià)值優(yōu)化者 業(yè)務(wù)數(shù)據(jù)分析是企業(yè)解決日常運(yùn)營(yíng)問(wèn)題、提升執(zhí)行效率的核心手段,其價(jià)值 ...
2025-09-12用 SQL 驗(yàn)證業(yè)務(wù)邏輯:從規(guī)則拆解到數(shù)據(jù)把關(guān)的實(shí)戰(zhàn)指南 在業(yè)務(wù)系統(tǒng)落地過(guò)程中,“業(yè)務(wù)邏輯” 是連接 “需求設(shè)計(jì)” 與 “用戶體驗(yàn) ...
2025-09-11塔吉特百貨孕婦營(yíng)銷案例:數(shù)據(jù)驅(qū)動(dòng)下的精準(zhǔn)零售革命與啟示 在零售行業(yè) “流量紅利見頂” 的當(dāng)下,精準(zhǔn)營(yíng)銷成為企業(yè)突圍的核心方 ...
2025-09-11CDA 數(shù)據(jù)分析師與戰(zhàn)略 / 業(yè)務(wù)數(shù)據(jù)分析:概念辨析與協(xié)同價(jià)值 在數(shù)據(jù)驅(qū)動(dòng)決策的體系中,“戰(zhàn)略數(shù)據(jù)分析”“業(yè)務(wù)數(shù)據(jù)分析” 是企業(yè) ...
2025-09-11Excel 數(shù)據(jù)聚類分析:從操作實(shí)踐到業(yè)務(wù)價(jià)值挖掘 在數(shù)據(jù)分析場(chǎng)景中,聚類分析作為 “無(wú)監(jiān)督分組” 的核心工具,能從雜亂數(shù)據(jù)中挖 ...
2025-09-10統(tǒng)計(jì)模型的核心目的:從數(shù)據(jù)解讀到?jīng)Q策支撐的價(jià)值導(dǎo)向 統(tǒng)計(jì)模型作為數(shù)據(jù)分析的核心工具,并非簡(jiǎn)單的 “公式堆砌”,而是圍繞特定 ...
2025-09-10