自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

解密 Python 的變量和對(duì)象,它們之間有什么區(qū)別和聯(lián)系呢?

開(kāi)發(fā) 前端
盡管 Python 一切皆對(duì)象,但你拿到的都是對(duì)象的指針,變量是一個(gè)指針,函數(shù)是一個(gè)指針,元組、列表、字典里面存儲(chǔ)的還是指針。

Python 中一切皆對(duì)象

在學(xué)習(xí) Python 的時(shí)候,你肯定聽(tīng)過(guò)這么一句話:Python 中一切皆對(duì)象。沒(méi)錯(cuò),在 Python 世界里,一切都是對(duì)象。整數(shù)是一個(gè)對(duì)象、字符串是一個(gè)對(duì)象、字典是一個(gè)對(duì)象,甚至 int, str, list 以及我們使用 class 關(guān)鍵字自定義的類(lèi),它們也是對(duì)象。

像 int, str, list 等基本類(lèi)型,以及自定義的類(lèi),由于它們可以表示類(lèi)型,因此我們稱之為類(lèi)型對(duì)象;類(lèi)型對(duì)象實(shí)例化得到的對(duì)象,我們稱之為實(shí)例對(duì)象。但不管是哪種對(duì)象,它們都屬于對(duì)象。

因此 Python 將面向?qū)ο罄砟钬瀼氐姆浅氐?,面向?qū)ο笾械念?lèi)和對(duì)象在 Python 中都是通過(guò)對(duì)象實(shí)現(xiàn)的。

在面向?qū)ο罄碚撝?,存在著?lèi)和對(duì)象兩個(gè)概念,像 int、dict、tuple、以及使用 class 關(guān)鍵字自定義的類(lèi)型對(duì)象實(shí)現(xiàn)了面向?qū)ο罄碚撝蓄?lèi)的概念,而 123、3.14,"string" 等等這些實(shí)例對(duì)象則實(shí)現(xiàn)了面向?qū)ο罄碚撝袑?duì)象的概念。但在 Python 里面,面向?qū)ο蟮念?lèi)和對(duì)象都是通過(guò)對(duì)象實(shí)現(xiàn)的。

我們舉個(gè)例子:

# dict 是一個(gè)類(lèi),因此它屬于類(lèi)型對(duì)象
# 類(lèi)型對(duì)象實(shí)例化得到的對(duì)象屬于實(shí)例對(duì)象
print(dict)
"""
<class 'dict'>
"""
print(dict(a=1, b=2))
"""
{'a': 1, 'b': 2}
"""

因此可以用一張圖來(lái)描述面向?qū)ο笤?Python 中的體現(xiàn)。

圖片圖片

而如果想查看一個(gè)對(duì)象的類(lèi)型,可以使用 type,或者通過(guò)對(duì)象的 __class__ 屬性。

numbers = [1, 2, 3]
# 查看類(lèi)型
print(type(numbers))
"""
<class 'list'>
"""
print(numbers.__class__)
"""
<class 'list'>
"""

如果想判斷一個(gè)對(duì)象是不是指定類(lèi)型的實(shí)例對(duì)象,可以使用 isinstance。

numbers = [1, 2, 3]
# 判斷是不是指定類(lèi)型的實(shí)例對(duì)象
print(isinstance(numbers, list))
"""
True
"""

但是問(wèn)題來(lái)了,按照面向?qū)ο蟮睦碚搧?lái)說(shuō),對(duì)象是由類(lèi)實(shí)例化得到的,這在 Python 中也是適用的。既然是對(duì)象,那么就必定有一個(gè)類(lèi)來(lái)實(shí)例化它,換句話說(shuō)對(duì)象一定要有類(lèi)型。

至于一個(gè)對(duì)象的類(lèi)型是什么,就看這個(gè)對(duì)象是被誰(shuí)實(shí)例化的,被誰(shuí)實(shí)例化那么類(lèi)型就是誰(shuí),比如列表的類(lèi)型是 list,字典的類(lèi)型是 dict 等等。

而 Python 中一切皆對(duì)象,所以像 int, str, tuple 這些內(nèi)置的類(lèi)對(duì)象也是具有相應(yīng)的類(lèi)型的,那么它們的類(lèi)型又是誰(shuí)呢?使用 type 查看一下就知道了。

>>> type(int)
<class 'type'>
>>> type(str)
<class 'type'>
>>> type(dict)
<class 'type'>
>>> type(type)
<class 'type'>

我們看到類(lèi)型對(duì)象的類(lèi)型,無(wú)一例外都是 type。而 type 我們也稱其為元類(lèi),表示類(lèi)型對(duì)象的類(lèi)型。至于 type 本身,它的類(lèi)型還是 type,所以它連自己都沒(méi)放過(guò),把自己都變成自己的對(duì)象了。

因此在 Python 中,你能看到的任何對(duì)象都是有類(lèi)型的,可以使用 type 查看,也可以獲取該對(duì)象的 __class__ 屬性查看。所以:實(shí)例對(duì)象、類(lèi)型對(duì)象、元類(lèi),Python 中任何一個(gè)對(duì)象都逃不過(guò)這三種身份。

到這里可能有人會(huì)發(fā)現(xiàn)一個(gè)有意思的點(diǎn),我們說(shuō) int 是一個(gè)類(lèi)對(duì)象,這顯然是沒(méi)有問(wèn)題的。因?yàn)檎驹谡麛?shù)(比如 123)的角度上,int 是一個(gè)不折不扣的類(lèi)對(duì)象;但如果站在 type 的角度上呢?顯然我們又可以將 int 理解為實(shí)例對(duì)象,因此 class 具有二象性。

至于 type 也是同理,雖然它是元類(lèi),但本質(zhì)上也是一個(gè)類(lèi)對(duì)象。

注:不僅 type 是元類(lèi),那些繼承了 type 的類(lèi)也可以叫做元類(lèi)。

然后 Python 中還有一個(gè)關(guān)鍵的類(lèi)型(對(duì)象),叫做 object,它是所有類(lèi)型對(duì)象的基類(lèi)。不管是什么類(lèi),內(nèi)置的類(lèi)也好,我們自定義的類(lèi)也罷,它們都繼承自 object。因此 object 是所有類(lèi)型對(duì)象的基類(lèi)、或者說(shuō)父類(lèi)。

那如果我們想獲取一個(gè)類(lèi)都繼承了哪些基類(lèi),該怎么做呢?方式有三種:

class A: pass

class B: pass

class C(A): pass

class D(B, C): pass

# 首先 D 繼承自 B 和 C, C 又繼承 A
# 我們現(xiàn)在要來(lái)查看 D 繼承的父類(lèi)

# 方法一: 使用 __base__
print(D.__base__)  
"""
<class '__main__.B'>
"""

# 方法二: 使用 __bases__
print(D.__bases__)  
"""
(<class '__main__.B'>, <class '__main__.C'>)
"""

# 方法三: 使用 __mro__
print(D.__mro__)
"""
(<class '__main__.D'>, <class '__main__.B'>, 
 <class '__main__.C'>, <class '__main__.A'>, 
 <class 'object'>)
"""
  • __base__:如果繼承了多個(gè)類(lèi),那么只顯示繼承的第一個(gè)類(lèi),沒(méi)有顯式繼承則返回 <class 'object'>
  • __bases__:返回一個(gè)元組,會(huì)顯示所有直接繼承的父類(lèi),沒(méi)有顯式繼承則返回 (<class 'object'>,)
  • __mro__: mro(Method Resolution Order)表示方法查找順序,會(huì)從自身出發(fā),找到最頂層的父類(lèi)。因此返回自身、繼承的基類(lèi)、以及基類(lèi)繼承的基類(lèi), 一直找到 object

而如果想查看某個(gè)類(lèi)型是不是另一個(gè)類(lèi)型的子類(lèi),可以通過(guò) issubclass。

print(issubclass(str, object))
"""
True
"""

因此,我們可以得出以下兩個(gè)結(jié)論:

  • type 站在類(lèi)型金字塔的最頂端,任何一個(gè)對(duì)象按照類(lèi)型追根溯源,最終得到的都是 type;
  • object 站在繼承金字塔的最頂端,任何一個(gè)類(lèi)型對(duì)象按照繼承關(guān)系追根溯源,最終得到的都是 object;

但要注意的是,我們說(shuō) type 的類(lèi)型還是 type,但 object 的基類(lèi)則不再是 object,而是 None。

print(
    type.__class__
)  # <class 'type'>

# 注:以下打印結(jié)果容易讓人產(chǎn)生誤解
# 它表達(dá)的含義是 object 的基類(lèi)為空
# 而不是說(shuō) object 繼承 None
print(
    object.__base__
)  # None

但為什么 object 的基類(lèi)是 None,而不是它自身呢?其實(shí)答案很簡(jiǎn)單,Python 在查找屬性或方法的時(shí)候,自身如果沒(méi)有的話,會(huì)按照 __mro__ 指定的順序去基類(lèi)中查找。所以繼承鏈一定會(huì)有一個(gè)終點(diǎn),否則就會(huì)像沒(méi)有出口的遞歸一樣出現(xiàn)死循環(huán)了。

我們用一張圖將對(duì)象之間的關(guān)系總結(jié)一下:

圖片圖片

  • 實(shí)例對(duì)象的類(lèi)型是類(lèi)型對(duì)象,類(lèi)型對(duì)象的類(lèi)型是元類(lèi);
  • 所有類(lèi)型對(duì)象的基類(lèi)都收斂于 object;
  • 所有對(duì)象的類(lèi)型都收斂于 type;

因此 Python 算是將一切皆對(duì)象的理念貫徹到了極致,也正因?yàn)槿绱?,Python 才具有如此優(yōu)秀的動(dòng)態(tài)特性。

但是還沒(méi)結(jié)束,我們?cè)僦匦聦徱曇幌律厦婺菑垐D,會(huì)發(fā)現(xiàn)里面有兩個(gè)箭頭看起來(lái)非常的奇怪。object 的類(lèi)型是 type,type 又繼承了 object。

>>> type.__base__
<class 'object'>
>>> object.__class__
<class 'type'>

因?yàn)?nbsp;type 是所有類(lèi)的元類(lèi),而 object 是所有類(lèi)的基類(lèi),這就說(shuō)明 type 要繼承自 object,而 object 的類(lèi)型是 type。很多人都會(huì)對(duì)這一點(diǎn)感到奇怪,這難道不是一個(gè)先有雞還是先有蛋的問(wèn)題嗎?其實(shí)不是的,這兩個(gè)對(duì)象是共存的,它們之間的定義其實(shí)是互相依賴的。而具體是怎么一回事,我們后續(xù)分析。

Python 的變量其實(shí)是指針

Python 的變量只是一個(gè)名字,如果站在 C 語(yǔ)言的角度來(lái)看,那么就是一個(gè)指針。所以 Python 的變量保存的其實(shí)是對(duì)象的內(nèi)存地址,或者說(shuō)指針,而指針指向的內(nèi)存存儲(chǔ)的才是對(duì)象。

所以在 Python 中,我們都說(shuō)變量指向了某個(gè)對(duì)象。在其它靜態(tài)語(yǔ)言中,變量相當(dāng)于是為某塊內(nèi)存起的別名,獲取變量等于獲取這塊內(nèi)存所存儲(chǔ)的值。而 Python 中變量代表的內(nèi)存所存儲(chǔ)的不是對(duì)象,而是對(duì)象的指針(或者說(shuō)引用)。

我們舉例說(shuō)明,看一段 C 代碼。

#include <stdio.h>

void main()
{
    int a = 666;
    printf("address of a = %p\n", &a);

    a = 667;
    printf("address of a = %p\n", &a);
}

編譯執(zhí)行一下:

圖片圖片

賦值前后地址都是 0x7fff9eda521c,沒(méi)有變化,再來(lái)看一段 Python 代碼。

a = 666
print(hex(id(a)))  # 0x7febf803a3d0

a = 667
print(hex(id(a)))  # 0x7fec180677b0

我們看到 Python 里面輸出的地址發(fā)生了變化,下面分析一下原因。

首先在 C 中,創(chuàng)建一個(gè)變量的時(shí)候必須規(guī)定好類(lèi)型,比如 int a = 666,那么變量 a 就是 int 類(lèi)型,以后在所處的作用域中就不可以變了。如果這時(shí)候再設(shè)置 a = 777,那么等于是把內(nèi)存中存儲(chǔ)的 666 換成 777,a 的地址和類(lèi)型是不會(huì)變化的。

而在 Python 中,a = 666 等于是先開(kāi)辟一塊內(nèi)存,存儲(chǔ)的值為 666,然后讓變量 a 指向這片內(nèi)存,或者說(shuō)讓變量 a 保存這塊內(nèi)存的地址。然后 a = 777 的時(shí)候,再開(kāi)辟一塊內(nèi)存,然后讓 a 指向存儲(chǔ) 777 的內(nèi)存,由于是兩塊不同的內(nèi)存,所以它們的地址是不一樣的。

圖片圖片

所以 Python 的變量只是一個(gè)和對(duì)象關(guān)聯(lián)的名字,它代表的是對(duì)象的指針。換句話說(shuō) Python 的變量就是個(gè)便利貼,可以貼在任何對(duì)象上,一旦貼上去了,就代表這個(gè)對(duì)象被引用了。

值傳遞?引用傳遞?

再來(lái)看看變量之間的傳遞,在 Python 中是如何體現(xiàn)的。

a = 666
print(hex(id(a)))  # 0x1f4e8ca7fb0

b = a
print(hex(id(b)))  # 0x1f4e8ca7fb0

我們看到打印的地址是一樣的,再用一張圖解釋一下。

圖片圖片

a = 666 的時(shí)候,先開(kāi)辟一份內(nèi)存,再讓 a 存儲(chǔ)對(duì)應(yīng)內(nèi)存的地址;然后 b = a 的時(shí)候,會(huì)把 a 拷貝一份給 b,所以 b 和 a 存儲(chǔ)了相同的地址,它們都指向了同一個(gè)對(duì)象。

因此說(shuō) Python 是值傳遞、或者引用傳遞都是不準(zhǔn)確的,準(zhǔn)確的說(shuō) Python 是變量的值傳遞,對(duì)象的引用傳遞。因?yàn)?Python 的變量可以認(rèn)為是 C 的一個(gè)指針,在 b = a 的時(shí)候,等于把 a 指向的對(duì)象的地址(a 本身)拷貝一份給 b,所以對(duì)于變量來(lái)說(shuō)是值傳遞;然后 a 和 b 又都是指向?qū)ο蟮闹羔?,因此?duì)于對(duì)象來(lái)說(shuō)是引用傳遞。

在這個(gè)過(guò)程中,對(duì)象沒(méi)有重復(fù)創(chuàng)建,它只是多了一個(gè)引用。

另外還有最關(guān)鍵的一點(diǎn),Python 的變量是一個(gè)指針,當(dāng)傳遞變量的時(shí)候,傳遞的是指針;但是在操作變量的時(shí)候,會(huì)操作變量指向的內(nèi)存。所以 id(a) 獲取的不是 a 的地址,而是 a 指向的內(nèi)存的地址(在底層其實(shí)就是 a 本身);同理 b = a,是將 a 本身,或者說(shuō)將 a 存儲(chǔ)的、指向某個(gè)具體的對(duì)象的地址傳遞給了 b。

另外在 C 的層面,顯然 a 和 b 屬于指針變量,那么 a 和 b 有沒(méi)有地址呢?顯然是有的,只不過(guò)在 Python 中是獲取不到的,解釋器只允許獲取對(duì)象的地址。

我們?cè)倥e個(gè)函數(shù)的例子:

def some_func(num):
    print("address of local num", hex(id(num)))
    num = 667
    print("address of local num", hex(id(num)))

num = 666
print("address of global num", hex(id(num)))
some_func(num)
"""
address of global num 0x2356cd698d0
address of local num 0x2356cd698d0
address of local num 0x2356c457f90
"""

函數(shù)的參數(shù)也是一個(gè)變量,所以 some_func(num) 其實(shí)就是把全局變量 num 存儲(chǔ)的對(duì)象的地址拷貝一份給局部變量 num,所以兩個(gè) num 指向了同一個(gè)對(duì)象,打印的地址相同。

然后在函數(shù)內(nèi)部執(zhí)行 num = 667,相當(dāng)于讓局部變量指向新的對(duì)象,或者說(shuō)保存新對(duì)象的地址,因此打印的結(jié)果發(fā)生變化。

變量有類(lèi)型嗎?

當(dāng)提到類(lèi)型時(shí),這個(gè)類(lèi)型指的是變量的類(lèi)型還是對(duì)象的類(lèi)型呢?不用想,肯定是對(duì)象的類(lèi)型。因?yàn)?Python 的變量是個(gè)指針,操作指針會(huì)自動(dòng)操作它指向的內(nèi)存,所以使用 type(a) 查看的其實(shí)是變量 a 指向的對(duì)象的類(lèi)型。

那么問(wèn)題來(lái)了,我們?cè)趧?chuàng)建變量的時(shí)候,并沒(méi)有顯式地指定類(lèi)型啊,那么 Python 是如何判斷一個(gè)變量指向的是什么類(lèi)型的數(shù)據(jù)呢?答案是:解釋器是通過(guò)靠猜的方式,通過(guò)賦的值(或者說(shuō)變量引用的值)來(lái)推斷類(lèi)型。

因此在 Python 中,如果你想創(chuàng)建一個(gè)變量,那么必須在創(chuàng)建變量的時(shí)候同時(shí)賦值,否則解釋器就不知道這個(gè)變量指向的數(shù)據(jù)是什么類(lèi)型。所以 Python 是先創(chuàng)建相應(yīng)的值,這個(gè)值在 C 中對(duì)應(yīng)一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體里面有一個(gè)成員專(zhuān)門(mén)用來(lái)存儲(chǔ)該值對(duì)應(yīng)的類(lèi)型,因此在 Python 中,類(lèi)型是和對(duì)象綁定的,而不是和變量。當(dāng)創(chuàng)建完值之后,再讓這個(gè)變量指向它,所以 Python 中是先有值后有變量。

但顯然在 C 里面不是這樣的,因?yàn)?C 的變量代表的內(nèi)存所存儲(chǔ)的就是具體的值,所以在 C 里面可以直接聲明一個(gè)變量的同時(shí)不賦值。因?yàn)?C 要求聲明變量時(shí)必須指定類(lèi)型,因此變量聲明之后,其類(lèi)型和內(nèi)存大小就已經(jīng)固定了。

而 Python 的變量存的是個(gè)地址,它只是指向了某個(gè)對(duì)象,所以由于其便利貼的特性,可以貼在任意對(duì)象上面。但是不管貼在哪個(gè)對(duì)象,都必須先有對(duì)象才可以,不然變量貼誰(shuí)去。

另外,盡管 Python 在創(chuàng)建變量的時(shí)候不需要指定類(lèi)型,但 Python 是強(qiáng)類(lèi)型語(yǔ)言,而且是動(dòng)態(tài)強(qiáng)類(lèi)型。

小結(jié)

以上我們就聊了聊 Python 的變量和對(duì)象,核心就在于:變量保存的不是對(duì)象本身,而是對(duì)象的內(nèi)存地址,站在 C 的角度上看變量就是一個(gè)指針。

盡管 Python 一切皆對(duì)象,但你拿到的都是對(duì)象的指針,變量是一個(gè)指針,函數(shù)是一個(gè)指針,元組、列表、字典里面存儲(chǔ)的還是指針。我們可以想象一下列表,它底層是基于數(shù)組實(shí)現(xiàn)的,由于 C 數(shù)組要求里面的每個(gè)元素的類(lèi)型和大小都相同,因此從這個(gè)角度上講,列表內(nèi)部存儲(chǔ)的只能是指針。

責(zé)任編輯:武曉燕 來(lái)源: 古明地覺(jué)的編程教室
相關(guān)推薦

2018-01-16 00:11:18

數(shù)據(jù)中心云計(jì)算大數(shù)據(jù)

2020-09-08 11:00:00

IaaSPaaSSaaS

2025-04-09 08:15:00

分布式系統(tǒng)微服務(wù)架構(gòu)

2022-07-18 14:05:08

云計(jì)算邊緣計(jì)算IT

2023-07-19 21:54:02

小區(qū)扇區(qū)信號(hào)

2018-10-23 09:13:24

程序員學(xué)歷求職者

2020-06-02 10:28:17

機(jī)器學(xué)習(xí)技術(shù)人工智能

2023-10-07 00:26:09

2021-02-25 10:07:42

人工智能AI機(jī)器學(xué)習(xí)

2021-09-08 05:52:57

工業(yè)物聯(lián)網(wǎng)物聯(lián)網(wǎng)IOT

2022-08-31 08:33:54

Bash操作系統(tǒng)Linux

2021-09-10 17:02:43

Python協(xié)程goroutine

2024-01-05 08:31:08

C語(yǔ)言

2021-12-17 14:40:02

while(1)for(;;)語(yǔ)言

2024-03-05 18:59:59

前端開(kāi)發(fā)localhost

2022-08-02 08:23:37

SessionCookies

2024-05-27 00:40:00

2022-02-27 15:33:22

安全CASBSASE

2024-09-09 13:10:14

2021-05-16 14:26:08

RPAIPACIO
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)