## 準備知識
一些基本的定義:
* 在Python中對象的賦值其實就是對象的引用。當創建一個對象,把它賦值給另一個變量的時候,python并沒有拷貝這個對象,只是拷貝了這個對象的引用而已。
* 淺拷貝:拷貝了最外圍的對象本身,內部的元素都只是拷貝了一個引用而已。也就是,把對象復制一遍,但是該對象中引用的其他對象我不復制
* 深拷貝:外圍和內部元素都進行了拷貝對象本身,而不是引用。也就是,把對象復制一遍,并且該對象中引用的其他對象也復制。
幾個術語的解釋
* 變量:是一個系統表的元素,擁有指向對象的連接空間
* 對象:被分配的一塊內存,存儲其所代表的值
* 引用:是自動形成的從變量到對象的指針
* 注意:類型(int類型,long類型(python3已去除long類型,只剩下int類型的數據))屬于對象,不是變量
* 不可變對象:一旦創建就不可修改的對象,包括字符串、元組、數字
* 可變對象:可以修改的對象,包括列表、字典。
深淺拷貝的作用
* 減少內存的使用?
* 以后在做數據的清洗、修改或者入庫的時候,對原數據進行復制一份,以防數據修改之后,找不到原數據。
對于不可變對象的深淺拷貝
不可變對象類型(這個不可變對象類型里面不能包含可變對象類型,如元祖里面包含列表就不滿足這個條件),沒有被拷貝的說法,即便是用深拷貝,查看id的話也是一樣的,如果對其重新賦值,也只是新創建一個對象,替換掉舊的而已。一句話就是,不可變類型,不管是深拷貝還是淺拷貝,地址值和拷貝后的值都是一樣的。
## 數字、字符串等不可變數據類型
### 賦值
舉個栗子:
~~~
n1 = 123123
n2 = n1
print(n1,n2)
print(id(n1))
print(id(n2))
輸出結果:
123123 123123
1607915318992
1607915318992
~~~
在以上代碼塊當中,a2與a1所賦的值是一樣的,都是數字123123。因為python有一個重用機制,對于同一個數字,python并不會開辟一塊新的內存空間,而是維護同一塊內存地址,只是將該數字對應的內存地址的引用賦值給變量a1和a2。所以根據輸出結果,a1和a2其實對應的是同一塊內存地址,只是兩個不同的引用罷了。同樣的,對于a2 = a1,其實效果等同于“a1 = 123123; a2 = 123123”,它也就是將a1指向123123的引用賦值給a2。字符串跟數字的原理雷同,如果把123123改成“abcabc”也是一樣的。
**結論:對于通過用 = 號賦值,數字和字符串在內存當中用的都是同一塊地址。**
### 淺拷貝
同樣的栗子:
~~~
import copy # 使用淺拷貝需要導入copy模塊
n1 = 123123
n3 = copy.copy(n1) # 使用copy模塊里的copy()函數就是淺拷貝了
print(n1,n3)
print(id(n1))
print(id(n3))
輸出結果:
123123 123123
2735567515344
2735567515344
~~~
通過使用copy模塊里的copy()函數來進行淺拷貝,把a1拷貝一份賦值給a3,查看輸出結果發現,a1和a3的內存地址還是一樣。
**結論:對于淺拷貝,數字和字符串在內存當中用的也是同一塊地址。**
### 深拷貝
再來一個栗子:
~~~
import copy
n1 = 123123
n4 = copy.deepcopy(n1) # 深拷貝是用copy模塊里的deepcopy()函數
print(n1,n4)
print(id(n1))
print(id(n4))
輸出結果:
123123 123123
2545114525392
2545114525392
~~~
**結論:綜上所述,對于數字和字符串的賦值、淺拷貝、深拷貝在內存當中用的都是同一塊地址。**
原理圖:

## 字典、列表等可變數據類型
### 賦值
再舉個栗子
~~~
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n2 = n1 # 賦值
print(n1,n2)
print(id(n1))
print(id(n2))
n1['k1'] = 'c'
n1['k3'][0] = 'd'
print(n1,n2)
print(id(n1))
print(id(n2))
輸出結果:
~~~
{'k1': 'wu', 'k2': 123, 'k3': \['alex', 678\]} {'k1': 'wu', 'k2': 123, 'k3': \['alex', 678\]}
1867471875528
1867471875528
{'k1': 'c', 'k2': 123, 'k3': \['d', 678\]} {'k1': 'c', 'k2': 123, 'k3': \['d', 678\]}
1867471875528
1867471875528
? 我們的栗子當中用了一個字典n1,字典里面嵌套了一個列表,當我們把n1賦值給n2時,內存地址并沒有發生變化,因為其實它也是只是把n1的引用拿過來賦值給n2而已(我們用了一個字典來舉例,其他類型也是一樣的)。正因為如此,當我們修改字典里面的數據時,n1和n2都會發生改變。

** 結論:對于賦值,字典、列表等其他類型用的內存地址不會變化。**
### 淺拷貝
栗子走起
~~~
import copy
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n3 = copy.copy(n1) # 淺拷貝
print(n1,n3)
print("第一層字典的內存地址:")
print(id(n1))
print(id(n3))
print("第二層嵌套的列表的內存地址:")
print(id(n1["k3"]))
print(id(n3["k3"]))
n1['k1'] = 'tom'
n1['k3'][0] = 'jack'
print('***************')
print(n1,n3)
輸出結果:
{'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]}
第一層字典的內存地址:
1506727325128
1506727325200
第二層嵌套的列表的內存地址:
1506758960840
1506758960840
***************
{'k1': 'tom', 'k2': 123, 'k3': ['jack', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['jack', 678]}
~~~
通過以上結果可以看出,進行淺拷貝時,我們的字典第一層n1和n3指向的內存地址已經改變了,但是對于第二層里的列表并沒有拷貝,它的內存地址還是一樣的。原理如下圖:

**? 結論:所以對于淺拷貝,字典、列表等類型,它們只拷貝第一層地址。**
### 深拷貝
栗子:
~~~
import copy
n1 = {"k1": "wu", "k2": 123, "k3": ["alex", 678]}
n4 = copy.deepcopy(n1) # 深拷貝
print("第一層字典的內存地址:")
print(id(n1))
print(id(n4))
print("第二層嵌套的列表的內存地址:")
print(id(n1["k3"]))
print(id(n4["k3"]))
n1['k1'] = 'tom'
n1['k3'][0] = 'jack'
print(n1,n4)
輸出結果:
{'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]}
第一層字典的內存地址:
1853270748616
1853271588800
第二層嵌套的列表的內存地址:
1853273351880
1853273350600
***************
{'k1': 'tom', 'k2': 123, 'k3': ['jack', 678]} {'k1': 'wu', 'k2': 123, 'k3': ['alex', 678]}
~~~
通過以上結果發現,進行深拷貝時,字典里面的第一層和里面嵌套的地址都已經變了。對于深拷貝,它會拷貝多層,將第二層的列表也拷貝一份,如果還有第三層嵌套,那么第三層的也會拷貝,但是對于里面的最小元素,比如數字和字符串,這里就是“wu”,123,“alex”,678之類的,按照python的機制,它們會共同指向同一個位置,它的內存地址是不會變的。原理如下圖:

?**結論:對于深拷貝,字典、列表等類型,它里面嵌套多少層,就會拷貝多少層出來,但是最底層的數字和字符串地址不變,是一樣的。**
**PS:**
對于元祖來說,如果他里面的元素都是不可變類型的元素,那么不論是賦值,淺拷貝還是深拷貝,他們的id都是一樣的(不僅整個元祖的id是一樣的,里面每一個元素的id都是一樣的)。但是如果元祖里面的元素有可變元素,如列表字典等,那么對于賦值和淺拷貝來說,id仍然還是一樣的(不僅整個元祖的id是一樣的,里面每一個元素的id都是一樣的);對于深拷貝來說,元祖的id和列表字典的id是不一樣的,但是對于最底層的數字,字符串地址還是一樣的。
- Python學習
- Python基礎
- Python初識
- 列表生成式,生成器,可迭代對象,迭代器詳解
- Python面向對象
- Python中的單例模式
- Python變量作用域、LEGB、閉包
- Python異常處理
- Python操作正則
- Python中的賦值與深淺拷貝
- Python自定義CLI三方庫
- Python并發編程
- Python之進程
- Python之線程
- Python之協程
- Python并發編程與IO模型
- Python網絡編程
- Python之socket網絡編程
- Django學習
- 反向解析
- Cookie和Session操作
- 文件上傳
- 緩存的配置和使用
- 信號
- FBV&&CBV&&中間件
- Django補充
- 用戶認證
- 分頁
- 自定義搜索組件
- Celery
- 搭建sentry平臺監控
- DRF學習
- drf概述
- Flask學習
- 項目拆分
- 三方模塊使用
- 爬蟲學習
- Http和Https區別
- 請求相關庫
- 解析相關庫
- 常見面試題
- 面試題
- 面試題解析
- 網絡原理
- 計算機網絡知識簡單介紹
- 詳解TCP三次握手、四次揮手及11種狀態
- 消息隊列和數據庫
- 消息隊列之RabbitMQ
- 數據庫之Redis
- 數據庫之初識MySQL
- 數據庫之MySQL進階
- 數據庫之MySQL補充
- 數據庫之Python操作MySQL
- Kafka常用命令
- Linux學習
- Linux基礎命令
- Git
- Git介紹
- Git基本配置及理論
- Git常用命令
- Docker
- Docker基本使用
- Docker常用命令
- Docker容器數據卷
- Dockerfile
- Docker網絡原理
- docker-compose
- Docker Swarm
- HTML
- CSS
- JS
- VUE