你真的知道Python中的賦值與拷貝嗎?
Python中的賦值
Python是一種動態型別程式語言,與C/C++等靜態型別程式語言不同,Python中的變數沒有固定的型別,變數也無需提前宣告分配記憶體空間,因此我們可以給任何變數賦值任何符合Python規範的型別值,舉個例子:
互動式Python程式碼:>>> var = 88 >>> var = 'hello world'
在C語言中,變數使用前需要提前宣告並明確其型別(或宣告和賦值可同一條語句執行),以便為其分配記憶體空間,舉個例子:
C程式碼:int a; a = 100;
但在C語言中一般不可直接將其他型別值賦值給不同型別的變數(可強制型別轉換的除外),例如如下程式碼就會報錯:
C程式碼:int a; a = "hello world";
在Python中將字面量或變數對另一個變數進行賦值可以理解為:給記憶體中的變數實體貼上一個標籤。這個標籤就是變數識別符號,舉個例子:
互動式Python程式碼:>>> a = 100 >>> b = a >>> a is b True
上述程式碼表明變數
a
與變數b
是同一個物件,識別符號a和識別符號b都是記憶體中變數實體100的一個標籤。在C語言中,因為每個變數都有自己的記憶體空間,變數賦值是將值複製到指定的記憶體中,因此進行賦值的兩個變數只是值相同,但並不表示同一個物件。舉個例子:
C程式碼:#include <stdio.h> int a = 100; int b; b = a; if (&a == &b) { printf("yse\n"); } else { printf("no\n"); } // 輸出:no
Python中函式的實參和形參之間以及返回值也是進行賦值操作的,舉個例子:
互動式Python程式碼:>>> a = 3 >>> b = 5 >>> def func(m, n): print(m is a) print(n is b) r = m + n print('id(r):', id(r)) return r >>> c = func(a, b) True True id(r): 10914624 >>> id(c) 10914624
根據上述程式碼可知,Python函式呼叫時形參會被實參進行賦值操作,即在執行
c = func(a, b)
時其實相當於在函式內部執行了m = a
和n = b
。那麼就會出現一個問題,當實參為可變物件時,在函式內部就存在改變實參的風險,舉個例子:
互動式Python程式碼:>>> a = [1, 2] >>> def func(List): List.append(a[0] + a[1]) return List >>> c = func(a) >>> print(c) [1, 2, 3] >>> print(a) [1, 2, 3] >>> id(a) == id(c) True
上述程式碼中
a
為列表型別(可變物件),在函式內部List
新增了一個元素並返回。a
,List
和函式返回後的c
表示同一個物件,這是我們之前討論過的。現在我們討論的重點是在函式內部改變了函式的實參,當然在有些情況下,我們正是需要藉助這種特性完成某種功能,這種情況我們暫且不討論。但在實際的工作中,更多的情況是我們不想函式更改實參,針對這種情況我們應該怎樣規避實參被更改的風險呢?有三種方法:儘量使用不可變型別作實參
當實參為不可變物件時,函式內部形參的操作不會影響實參,舉個例子:
互動式Python程式碼:>>> a = 55 >>> id(a) 10916224 >>> def func(value): print('id(value):', id(value), 'value: ', value) print(value is a) value = 99 print('id(value):', id(value), 'value:', value) print(value is a) >>> func(a) id(value): 10916224 value: 55 True id(value): 10917632 value: 99 False >>> print('id(a):', id(a), 'a:', a) id(a): 10916224 a: 55
上述程式碼中
a
為int型別(不可變型別),作為實參賦值給形參value
,函式在執行value = 99
之前value
與a
表示同一個物件,在執行之後value
與a
表示不同的物件,其實這很好理解就像我們前面討論的,在執行賦值語句之後value
識別符號被“貼”到另一個記憶體變數實體99上了。使用拷貝
- 以上兩種都不想用?那就寫程式碼的時候多注意吧
下面我就來討論今天的第二個重點:拷貝。
Python中的拷貝
Python中的拷貝分為淺拷貝和深拷貝
淺拷貝
Python中的預設拷貝都是淺拷貝,這裡我們就拿列表型別舉個例子,先看看列表有哪些拷貝方法,此處參考Python語言及其應用:
互動式Python程式碼:>>> a = [1, 2, 3] >>> b = a.copy() >>> a [1, 2, 3] >>> b [1, 2, 3] >>> b is a False >>> c = list(a) >>> c [1, 2, 3] >>> c is a False >>> d = a[:] >>> d is a False
上述程式碼中顯示出三種列表拷貝方法:
- 使用列表物件的
.copy()
方法 - 使用
list()
轉換函式 - 使用列表分片
[:]
我們也可看到拷貝出來的物件與被拷貝物件不再是同一個物件,這也是拷貝的根本目的。當使用可變物件的拷貝進行函式傳參可以避免函式內部修改實參,舉個例子:
互動式Python程式碼:>>> a = [1, 2, 3] >>> def func(List): List.append(100) List[0] = 'hello' return List >>> b = func(a.copy()) >>> b ['hello', 2, 3, 100] >>> a [1, 2, 3]
好像一切都很完美,達到了我們的目的。於是我們編寫了下面這樣的程式碼:
互動式Python程式碼:>>> a = [1, 'hello', 'world', [88, 99], 22] >>> def func(List): List.append('shawn') List[0] = 'simon' List[3][0] = 100 return List >>> b = func(a.copy()) >>> b ['simon', 'hello', 'world', [100, 99], 22, 'shawn'] >>> a [1, 'hello', 'world', [100, 99], 22]
有瑕疵啊,這執行結果和我們想象的不一樣,我們想象著變數
a
索引為3的元素值仍為[88, 99]
,但輸出並不是這樣,問題出在了哪裡?原因在於我們前面所說的Python中的預設拷貝均為淺拷貝,如果我們想要輸出達到我們的預期,則需要深拷貝。- 使用列表物件的
深拷貝
深拷貝就是在每個層次都對可變物件進行拷貝,淺拷貝只對最外層可變物件進行拷貝。
我們先來看看賦值的情況即b = a
,如下圖所示:
示意圖a
與b
指向同一個物件,我們通過程式碼驗證其正確性:
互動式Python程式碼:>>> a = [1, 2, [3, 4]] >>> b = a >>> id(b) == id(a) True >>> id(a[0]) == id(b[0]) True >>> id(a[1]) == id(b[1]) True >>> id(a[2]) == id(b[2]) True >>> id(a[2][0]) == id(b[2][0]) True >>> id(a[2][1]) == id(b[2][1]) True
再來看看淺拷貝的情況,先上圖:
上圖中為b = a[:]
示意圖,b
為a
的淺拷貝,淺拷貝會將最外層可變物件進行拷貝,下面進行程式碼驗證:
互動式Python程式碼:>>> a = [1, 2, [3, 4]] >>> b = a[:] >>> id(b) == id(a) False >>> id(a[0]) == id(b[0]) True >>> id(a[1]) == id(b[1]) True >>> id(a[2]) == id(b[2]) True >>> id(a[2][0]) == id(b[2][0]) True >>> id(a[2][1]) == id(b[2][1]) True
最後來看看深拷貝,深拷貝我們需要用到copy庫的deepcopy,示意圖如下:
接下來進行程式碼驗證:
互動式Python程式碼:>>> from copy import deepcopy >>> >>> a = [1, 2, [3, 4]] >>> b = deepcopy(a) >>> id(a) == id(b) False >>> id(a[0]) == id(b[0]) True >>> id(a[1]) == id(b[1]) True >>> id(a[2]) == id(b[2]) False >>> id(a[2][0]) == id(b[2][0]) True >>> id(a[2][1]) == id(b[2][1]) True
因此當實參為可變物件且其中含有多層巢狀的可變物件時,如果我們不想因為我們的粗心讓函式更改實參,那麼我們就應該使用深拷貝,看程式碼:
互動式Python程式碼:>>> a = [1, 2, [3, 4]] >>> def func(List): List.append('hello world') List[0] = 'shawn' List[2][0] = 'simon' List[2][1] = 199 return List >>> from copy import deepcopy >>> b = func(deepcopy(a)) >>> a [1, 2, [3, 4]] >>> b ['shawn', 2, ['simon', 199], 'hello world']
Note: Python中的深拷貝和淺拷貝只是針對可變物件而言,而對於不可變物件無所謂深拷貝還是淺拷貝,因為對於不可變物件兩種拷貝和變數之間賦值沒什麼兩樣。
互動式Python程式碼:>>> from copy import deepcopy >>> >>> a = 'hello world' >>> b = deepcopy(a) >>> c = a >>> id(a) == id(b) == id(c) True
總結
Python中變數之間的賦值,相當於給變數多貼了一個“標籤”,或者說給變數起了個別名
深拷貝、淺拷貝是針對可變物件說的,不可變物件無所謂什麼拷貝
對於可變物件,深拷貝對每層可變物件都進行拷貝,而淺拷貝只對最外層進行拷貝
當函式實參為可變物件且我們不想函式改變實參本身,應儘量使用深拷貝,尤其在實參中巢狀了可變物件時
相關文章
- 賦值、淺拷貝與深拷貝賦值
- 你真的理解Python中的賦值、傳參嗎?Python賦值
- 【Python】直接賦值,深拷貝和淺拷貝Python賦值
- Python - 物件賦值、淺拷貝、深拷貝的區別Python物件賦值
- JavaScript 之物件拷貝與賦值JavaScript物件賦值
- 淺析賦值、淺拷貝、深拷貝的區別賦值
- 龍叔python-直接賦值,深拷貝,淺拷貝的簡單解析Python賦值
- ES6 變數宣告與賦值:值傳遞、淺拷貝與深拷貝詳解變數賦值
- js資料型別賦值,淺拷貝,深拷貝JS資料型別賦值
- Python淺拷貝與深拷貝Python
- python深拷貝與淺拷貝Python
- JavaScript中的淺拷貝與深拷貝JavaScript
- 5張圖徹底理解Python中的淺拷貝與深拷貝Python
- 理解JS中的淺拷貝與深拷貝JS
- Java中的深淺拷貝問題,你清楚嗎?Java
- 你真的知道JS嗎JS
- 圖解 Python 淺拷貝與深拷貝圖解Python
- 你真的知道Python的字串怎麼用嗎?Python字串
- 你真的知道Python的字串是什麼嗎?Python字串
- 給妹子講python-S01E11賦值與物件拷貝機制分析Python賦值物件
- C++之Big Three:拷貝構造、拷貝賦值、解構函式探究C++賦值函式
- 你真的知道跨域嗎跨域
- 【進階4-1期】詳細解析賦值、淺拷貝和深拷貝的區別賦值
- 物件的深拷貝與淺拷貝物件
- 你真的知道js的原型鏈嗎??JS原型
- JavaScript的記憶體空間、賦值和深淺拷貝JavaScript記憶體賦值
- python 指標拷貝,淺拷貝和深拷貝Python指標
- 淺拷貝與深拷貝的實現
- 【JavaScript】物件的淺拷貝與深拷貝JavaScript物件
- Python3之淺談----深拷貝與淺拷貝Python
- python學習筆記–深拷貝與淺拷貝的區別Python筆記
- 在js中如何區分深拷貝與淺拷貝?JS
- JS深拷貝與淺拷貝JS
- 你真的理解js的賦值語句麼JS賦值
- VUE 中 的深拷貝和淺拷貝Vue
- iOS 圖文並茂的帶你瞭解深拷貝與淺拷貝iOS
- 你真的知道 == 和 equals 的區別嗎?
- JDBC API的那些事,你真的知道嗎?JDBCAPI