" />

欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

帶你從內(nèi)存的角度看Python中的變量

 更新時(shí)間:2022年01月11日 15:42:18   作者:菜雞劉  
這篇文章主要為大家介紹了從內(nèi)存的角度看Python中的變量,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

1、前言

由于筆者并未系統(tǒng)地學(xué)習(xí)過Python,對(duì)Python某些底層的實(shí)現(xiàn)細(xì)節(jié)一概不清楚,以至于在實(shí)際使用的時(shí)候會(huì)寫出一些奇奇怪怪的Bug(沒錯(cuò),別人寫代碼,我寫B(tài)ug),比如對(duì)象的某些屬性莫名奇妙地改變。究其原因,是對(duì)Python中的變量機(jī)制存在一些誤解,畢竟以前一直是用C語言居多。無奈,只能深入學(xué)習(xí)這一部分的知識(shí),并總結(jié)成此文。

閱讀本文,你可以:

  • 了解Python中變量的“儲(chǔ)存”機(jī)制。
  • 了解Python中賦值、淺拷貝于深拷貝的區(qū)別和使用場(chǎng)景。
  • 了解Python中的函數(shù)傳參形式。

當(dāng)然,你需要一點(diǎn)基礎(chǔ)的編程和面向?qū)ο蟮闹R(shí)才能看懂本文。

2、引用式變量

相信學(xué)過Python的小伙伴都聽過這樣一句話:Python中一切皆是對(duì)象。這意味著,哪怕是Python中的基本數(shù)據(jù)類型,其本質(zhì)上也是對(duì)象,例如對(duì)于一個(gè)int類型的變量a,你可以調(diào)用int類對(duì)象的方法來求a的絕對(duì)值:

>>> a = -1
>>> a.__abs__()
1

在這個(gè)例子中,可以說:a是int類的一個(gè)實(shí)例對(duì)象,其值是-1。當(dāng)然,這句話其實(shí)說的不對(duì),因?yàn)閍并不是一個(gè)對(duì)象,而是對(duì)象的引用。這聽起來很奇怪,但事實(shí)就是如此。Python中的變量都是引用式變量,他并不像C/C++中的變量,儲(chǔ)存著具體的數(shù)據(jù)類型或?qū)ο?,他像是C++中的引用。通俗的講,Python中的變量相當(dāng)于對(duì)象的別名,如果你有C語言的基礎(chǔ),可以把它理解為C語言中的指針,通過它你可以在內(nèi)存中找到對(duì)象。話不多說,先看圖:

圖片來源:《流暢的Python》8.1

左邊的圖表示的就是C語言中的變量,變量相當(dāng)于一個(gè)“盒子”,“盒子”里裝著值,右邊表示的就是Python中的引用式變量,a和b都是列表對(duì)象[1, 2, 3]的別名,像是貼在[1, 2, 3]上的”標(biāo)簽“,順著這些”標(biāo)簽“,解釋器可以在內(nèi)存中找到他們對(duì)應(yīng)的對(duì)象。你也許會(huì)問,這有啥區(qū)別,不都是變量嗎。還是先看代碼:

a = [1, 2, 3]
b = a
a[2] = 9
print(a)
print(b)

----運(yùn)行結(jié)果----
[1, 2, 9]
[1, 2, 9]

意想不到的事情發(fā)生了,明明代碼只改變了a的值,為什么b也跟著變了呢?這是因?yàn)椋琣、b都是列表的引用,并不是實(shí)際的列表,上述代碼通過a這個(gè)”標(biāo)簽“改變了內(nèi)存中列表[1, 2 ,3]的值,于是乎,你順著b”標(biāo)簽“找到的列表,當(dāng)然是改變了的。再看代碼:

a = [1, 2, 3]
b = a
a = [1, 2, 9]
print(a)
print(b)

----運(yùn)行結(jié)果----
[1, 2, 9]
[1, 2, 3]

在這個(gè)例程中,我們把[1, 2, 9]賦值給了a,然后再輸出a和b,此時(shí)a已經(jīng)發(fā)生變化,而b沒有改變,a從列表[1, 2, 3]的引用變成了列表[1, 2, 9]的引用,列表[1, 2, 3]在內(nèi)存中并未發(fā)生任何改變,這就是b輸出的值不發(fā)生變化的原因。到這里,你應(yīng)該可以理解上面說的:a是int類的一個(gè)實(shí)例對(duì)象,其值是-1為什么是錯(cuò)的了。這樣的賦值語句在Python中的應(yīng)該這樣理解:創(chuàng)建一個(gè)int類對(duì)象-1,讓a作為-1的引用。當(dāng)然,右邊的值是常量或是可變對(duì)象,解釋器都會(huì)做出不同的反應(yīng),這將在下文進(jìn)一步講解。總之,啰啰嗦嗦說了這么多,就是希望大家都能搞明白這個(gè)問題,核心就是一句話:Python中的變量都是引用式變量,變量存儲(chǔ)的不是值,而是引用。

3、賦值、淺拷貝與深拷貝

看完上一節(jié),肯定有人會(huì)問,如果Python中的賦值都是引用,那我想創(chuàng)建一個(gè)變量的副本做備份怎么辦?這在C語言中簡(jiǎn)單的一句b=a就可以實(shí)現(xiàn)的需求在Python中如何實(shí)現(xiàn)?Python中提供了三種復(fù)制的方式,即:

  • 賦值:創(chuàng)建對(duì)象的引用。
  • 淺拷貝:拷貝對(duì)象,但不拷貝對(duì)象內(nèi)部的子對(duì)象。
  • 深拷貝:拷貝對(duì)象,并且拷貝對(duì)象內(nèi)部的子對(duì)象。

一如既往地先看代碼,畢竟代碼最能說明問題:

import copy
a = [1, 2, [3, 3 , 3], [4, 4]]
b = a # 賦值
c = a.copy() # 淺拷貝,調(diào)用對(duì)象的copy()方法
d = copy.deepcopy(a) # 深拷貝,需要引入copy模塊,使用deepcopy()方法
a[1] = -2  # 改變1
a[2] = [-3, -3, -3]  # 改變2
a[3][0] = -4  # 改變3
print(a)
print(b)
print(c)
print(d)

----運(yùn)行結(jié)果----
[1, -2, [-3, -3, -3], [-4, 4]]
[1, -2, [-3, -3, -3], [-4, 4]]
[1, 2, [3, 3, 3], [-4, 4]]
[1, 2, [3, 3, 3], [4, 4]]

為了更方便闡述,這里我先給出這個(gè)例程中對(duì)象在內(nèi)存中的變化情況,當(dāng)然我更建議你自己去這個(gè)網(wǎng)站逐步可視化地運(yùn)行上面的代碼,甚至是本文中的所有代碼,這能加深你的理解。

在這里插入圖片描述

在這段代碼中,首先創(chuàng)建了一個(gè)列表對(duì)象,這個(gè)列表的第3、4個(gè)元素也是列表對(duì)象,a是這個(gè)列表的引用,把a(bǔ)賦值給b,此時(shí)b也是同一個(gè)對(duì)象的引用,在內(nèi)存中,它們指向同一個(gè)對(duì)象,因此可以看到無論怎么通過a改變這個(gè)對(duì)象,a和b都是相同的。c則是對(duì)a的淺拷貝,解釋器新開辟了一塊內(nèi)存,存儲(chǔ)了原列表的一個(gè)副本,但是由于是淺拷貝,對(duì)象內(nèi)部的子對(duì)象沒有被拷貝。因此,這個(gè)副本列表的后面兩個(gè)元素依舊和原列表一樣,是列表[3, 3 , 3]和[4, 4]的引用,在內(nèi)存中指向同樣的對(duì)象。代碼中的改變2讓原列表的第三個(gè)元素變成了另一個(gè)列表[-3, -3 , -3]的引用,但是這個(gè)副本列表的第三個(gè)元素還是[3, 3 , 3]的引用。改變3則修給了原列表第四個(gè)元素指向的列表中的一個(gè)元素,因此打印c你會(huì)發(fā)現(xiàn)它指向的列表對(duì)應(yīng)位置的元素也改變了。而對(duì)于d,d是a的深拷貝,解釋器新開辟了一塊內(nèi)存,完全復(fù)制了原列表對(duì)象(包括子列表對(duì)象)放在這塊內(nèi)存中。因此,d指向的對(duì)象和a指向的對(duì)象沒有任何關(guān)系,無論怎么改變a指向的那個(gè)列表,都不會(huì)影響d指向的列表。

看到這里,你應(yīng)該知道如何實(shí)現(xiàn)本節(jié)開頭的需求了。

4、is的用法和id()函數(shù)

在Python中,每個(gè)對(duì)象都有各自的編號(hào)、類型和值,一個(gè)對(duì)象被創(chuàng)建以后,它的編號(hào)就不會(huì)改變,可以理解為對(duì)象在內(nèi)存中的地址。id()函數(shù)可以獲取對(duì)象的編號(hào),在CPython解釋器中,這個(gè)編號(hào)就是對(duì)象在內(nèi)存中的地址。is是一個(gè)雙目運(yùn)算符,運(yùn)算結(jié)果是布爾變量,用來比較兩個(gè)對(duì)象的編號(hào)是否相同,準(zhǔn)確的說,可以用于比較兩個(gè)變量是否是同一個(gè)對(duì)象的引用。

a = [1, 2, 3]
b = a  # 賦值
c = a.copy()  # 淺拷貝
print(id(a))
print(id(b))
print(id(c))
print(a is b)
print(a is c)

----運(yùn)行結(jié)果----
2667871075272
2667871075272
2667871075208
True
False

顯然,a、b是同一個(gè)對(duì)象的引用,而c是淺拷貝的副本,因此a和c引用的不是同一個(gè)對(duì)象,即使這兩個(gè)對(duì)象的值相等。不知你是否還記得,第1節(jié)中還提到在賦值語句中,右邊是可變對(duì)象與不可變對(duì)象,解釋器會(huì)由不同的操作,比如下面的代碼:

a = 5
b = 5
print(a is b)
c = [1, 2, 3]
d = [1, 2, 3]
print(c is d)

----運(yùn)行結(jié)果----
True
False

對(duì)a、b分別賦值為5,但是它們卻是同一個(gè)對(duì)象的引用,這是因?yàn)椋?是一個(gè)常量,對(duì)應(yīng)的int類對(duì)象就是不可變的對(duì)象。Python解釋器認(rèn)為,這樣的不可變對(duì)象,只需要在內(nèi)存中存在一個(gè)就可以,因此,a和b指向同一個(gè)對(duì)象。而對(duì)于列表[1, 2, 3],由于列表是可變對(duì)象,即使這兩個(gè)對(duì)象的值相同,但它們不指向同一個(gè)對(duì)象。畢竟,誰也不知道后面的程序中會(huì)不會(huì)改變其中一個(gè)列表中的值。說到這里,或許能夠解釋Python的作者為什么要將Python的變量設(shè)計(jì)成只有引用式變量了,按照筆者粗淺的理解,這樣做的優(yōu)勢(shì)在于可以節(jié)約內(nèi)存。畢竟,Python為了能夠”簡(jiǎn)潔、優(yōu)雅“,為了能夠用一行代碼解決C語言用20行代碼才能解決的問題,在性能上犧牲了不少。

5、函數(shù)傳參機(jī)制

在Python中,函數(shù)傳參同樣傳遞的是對(duì)象的引用,函數(shù)參數(shù)是不可變對(duì)象時(shí),這沒有什么討論的價(jià)值。但是,倘若傳遞的參數(shù)是可變對(duì)象,如果你不注意這一點(diǎn),Bug可能就會(huì)默默地在凝視你,譬如:

def test1(a):
    a[-1] = 'end'

a = [1, 2, 3]
test1(a)
print(a)

----運(yùn)行結(jié)果----
[1, 2, 'end']

可以看到,在運(yùn)行完函數(shù)test1后,a的值改變了,如果你不想讓他改變,這是Bug就來啦。

同樣,還有需要注意的一點(diǎn)是,不要把參數(shù)的默認(rèn)值設(shè)置成一個(gè)可變對(duì)象,否則Bug大概已經(jīng)在和你招手了:

# 用可變對(duì)象做參數(shù)默認(rèn)值帶來的bug
# 例程來源于《流暢的Python》
class HauntedBus():
    def __init__(self, passengers=[]):
        self.passengers = passengers

    def pick(self, name): # 乘客上車
        self.passengers.append(name)

    def drop(self, name): # 乘客下車
        self.passengers.remove(name)

bus1 = HauntedBus(['zhang_san', 'li_si'])
bus1.pick('wang_mazi')
bus1.drop('zhang_san')
print(bus1.passengers)

bus2 = HauntedBus()
bus2.pick('zhao_wu')
print(bus2.passengers)

bus3 = HauntedBus()
print(bus3.passengers)
print(bus2.passengers is bus3.passengers)
print(bus3.passengers is bus1.passengers)


----運(yùn)行結(jié)果----
['li_si', 'wang_mazi']
['zhao_wu']
['zhao_wu']
True
False

你會(huì)驚奇地發(fā)現(xiàn),bus3.passengers難道不應(yīng)該是空列表嗎?這是因?yàn)?,HauntedBus的構(gòu)造函數(shù)中passengers的默認(rèn)值是一個(gè)可變對(duì)象,在對(duì)bus2進(jìn)行操作的時(shí)候,由于引用式變量的特性,改變了默認(rèn)值指向的可變對(duì)象。于是乎,就出現(xiàn)了意向不到的Bug。

6、擴(kuò)展閱讀

講到這里,其實(shí)本文的主要內(nèi)容就基本講完了。本節(jié)的內(nèi)容,除非說你需要開發(fā)自己的Python庫(kù),否則了解與否都基本不會(huì)影響你使用Python,你完全可以跳過本節(jié),完結(jié)撒花。

垃圾回收:在其他編程語言中都會(huì)討論變量或?qū)ο蟮纳嬷芷?,?huì)有垃圾回收機(jī)制,但在Python中好像很少談及這個(gè)問題。實(shí)際上,Python也存在垃圾回收機(jī)制,Python中每個(gè)變量都是對(duì)象的引用,如果某個(gè)對(duì)象不再被引用,這個(gè)對(duì)象就會(huì)被銷毀,這就是Python中的垃圾回收機(jī)制。del語句可以刪除變量,解除變量對(duì)對(duì)象的引用,如果這是對(duì)象的最后一個(gè)引用,這個(gè)對(duì)象就會(huì)被銷毀。

弱引用:弱引用不增加對(duì)象的引用數(shù),若對(duì)象存在,通過弱引用可以獲取對(duì)象。若對(duì)象已被銷毀,則弱引用返回None,這常用于緩存中。

最后,本文的目的在于幫助那些像我一樣從C語言轉(zhuǎn)移到Python的人,或者是被Python的變量、拷貝整得暈頭轉(zhuǎn)向的人。為了讓小白也有可能能看懂本文,我盡量寫得通俗易懂。但是限于本人水平,難免會(huì)有謬誤或疏漏之處,如有發(fā)現(xiàn),煩請(qǐng)?jiān)僭u(píng)論區(qū)指正,over。

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • 利用Python找出序列中出現(xiàn)最多的元素示例代碼

    利用Python找出序列中出現(xiàn)最多的元素示例代碼

    這篇文章主要給大家介紹了關(guān)于利用Python找出序列中出現(xiàn)最多的元素的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12
  • Python日志syslog使用原理詳解

    Python日志syslog使用原理詳解

    這篇文章主要介紹了Python日志syslog使用原理詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • Python之文字轉(zhuǎn)圖片方法

    Python之文字轉(zhuǎn)圖片方法

    今天小編就為大家分享一篇Python之文字轉(zhuǎn)圖片方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-05-05
  • python requests 測(cè)試代理ip是否生效

    python requests 測(cè)試代理ip是否生效

    這篇文章主要介紹了python requests 測(cè)試代理ip是否生效的相關(guān)資料,需要的朋友可以參考下
    2018-07-07
  • Python 登錄網(wǎng)站詳解及實(shí)例

    Python 登錄網(wǎng)站詳解及實(shí)例

    這篇文章主要介紹了Python 登錄網(wǎng)站詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • python代碼如何實(shí)現(xiàn)切換中英文輸入法

    python代碼如何實(shí)現(xiàn)切換中英文輸入法

    這篇文章主要介紹了python代碼如何實(shí)現(xiàn)切換中英文輸入法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • Django加載配置的過程詳解

    Django加載配置的過程詳解

    這篇文章主要介紹了Django加載配置的過程詳解,包括Django服務(wù)啟動(dòng) manage.py的詳細(xì)介紹,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-05-05
  • python第三方包安裝路徑site-packages下.libs作用詳解

    python第三方包安裝路徑site-packages下.libs作用詳解

    這篇文章主要為大家介紹了python?第三方包安裝路徑?site-packages?下面的以?.libs?結(jié)尾的路徑作用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 在Python中使用MySQL--PyMySQL的基本使用方法

    在Python中使用MySQL--PyMySQL的基本使用方法

    PyMySQL 是在 Python3.x 版本中用于連接 MySQL 服務(wù)器的一個(gè)庫(kù),Python2中則使用mysqldb。這篇文章主要介紹了在Python中使用MySQL--PyMySQL的基本使用,需要的朋友可以參考下
    2019-11-11
  • Python 等分切分?jǐn)?shù)據(jù)及規(guī)則命名的實(shí)例代碼

    Python 等分切分?jǐn)?shù)據(jù)及規(guī)則命名的實(shí)例代碼

    這篇文章主要介紹了Python 等分切分?jǐn)?shù)據(jù)及規(guī)則命名的實(shí)例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08

最新評(píng)論