面向?qū)ο蟮腜ython:類class(es)和對(duì)象object(s)
面向?qū)ο蟮木幊淌钱?dāng)今最廣泛使用的編程范式,幾乎所有的編程范式都提供了一種創(chuàng)建和管理對(duì)象的方法。下面是對(duì)象的含義。

面向?qū)ο缶幊讨械膶?duì)象的表示方法
大多數(shù)編程語(yǔ)言都提供了一個(gè)叫做 "類 "的關(guān)鍵字來(lái)創(chuàng)建一個(gè)對(duì)象,python也不例外。
那么,什么是類?
一個(gè)類定義了藍(lán)圖,它可以被實(shí)例化來(lái)創(chuàng)建對(duì)象(s)

一個(gè)類定義了可識(shí)別的特征和行為,對(duì)象將在此基礎(chǔ)上被識(shí)別。關(guān)鍵字class在Python中也有同樣的作用。
然而,在我們深入了解類和對(duì)象之前,讓我們先來(lái)談?wù)凱ython語(yǔ)言的另一個(gè)內(nèi)置數(shù)據(jù)結(jié)構(gòu)--字典。
Python 字典
字典是Python的內(nèi)置數(shù)據(jù)結(jié)構(gòu),它用 "鍵 "和 "值 "對(duì)來(lái)實(shí)現(xiàn) "哈希 "函數(shù)。
哈希函數(shù)在鍵上工作,生成哈希值,并根據(jù)這些哈希值存儲(chǔ)相應(yīng)的值。存儲(chǔ)取決于哈希函數(shù),這就是為什么字典有時(shí)不按我們創(chuàng)建的順序存儲(chǔ)的原因。
字典也可以代表一個(gè)對(duì)象的靜態(tài)狀態(tài),即不改變其行為的對(duì)象,其特點(diǎn)是提到的鍵(名稱)和值對(duì)。
作為一個(gè)例子,讓我們考慮當(dāng)?shù)厣鐓^(qū)中人們的郵政地址,我們可以將單個(gè)地址表示為
p1 = {'name' : "ABC" , 'street': "中央大街-1" }
p2 = {'name' : "DEF" , 'street': "中央街-2" }
p3 = {'name' : "AXY" , 'street': "中央街-1" }
這里p1, p2 & p3是住在同一條街上的不同人,這些字典描述了對(duì)象(即本例中的人)的靜態(tài)可識(shí)別特征。
我們也可以使用Python類來(lái)存儲(chǔ)對(duì)象的靜態(tài)狀態(tài),讓我們看看如何做到這一點(diǎn)。
Python類
與其他OOP語(yǔ)言類似,Python中的類提供了一個(gè)創(chuàng)建對(duì)象的藍(lán)圖。就像我們?cè)谏厦嬗米值浯鎯?chǔ)郵政地址一樣,我們也可以用類的對(duì)象來(lái)存儲(chǔ)它們。
我們可以先創(chuàng)建一個(gè)沒(méi)有預(yù)定義藍(lán)圖的假類PostalAddress。Python允許為對(duì)象添加運(yùn)行時(shí)成員(Identifiable Characteristics),我們將使用同樣的方法來(lái)創(chuàng)建居住在同一地區(qū)的人的地址,類似于上面的字典p1和p2。
class PostalAddress:
pass
cP1 = PostalAddress()
# 為ABC這個(gè)人創(chuàng)建一個(gè)實(shí)例
cP1.name = "ABC"
cP1.street = "Central Street - 1"
# 為DEF創(chuàng)建一個(gè)實(shí)例
cP2 = PostalAddress()
cP2.name = "DEF"
cP2.street = "Central Street - 2"
所以,我們現(xiàn)在有兩種方法來(lái)創(chuàng)建和保存PostAddress,即通過(guò)字典或使用類的對(duì)象。順便說(shuō)一下,對(duì)象也可以用一個(gè)內(nèi)置的函數(shù)__dict__來(lái)表示。
print(cP1.__dict__)
print(cP2.__dict__)
# 輸出將與p1和p2相同
{'name': 'ABC', 'street': 'Central Street - 1'}
{'name': 'DEF', 'street': 'Central Street - 2'}
這并不意味著 Python 將對(duì)象存儲(chǔ)為字典,但我們可以有把握地得出結(jié)論,字典也可以表示靜態(tài)狀態(tài)的對(duì)象。一旦我們使用 __dict__得到了字典,我們就可以用鍵來(lái)訪問(wèn)元素,就像我們對(duì)普通字典一樣。
print(p1["name"])
# 從普通字典中打印出 ABC
print(cP1.__dict__["name"])
# 從對(duì)象中打印 ABC
根據(jù)我們目前所了解的情況,我們可能會(huì)形成一種觀點(diǎn),即至少在上述情況下,我們使用字典還是對(duì)象并不重要。
好吧,對(duì)于創(chuàng)建非常簡(jiǎn)單的靜態(tài)對(duì)象來(lái)說(shuō),這可能是真的,但是類不僅提供了添加行為的能力(從而允許改變對(duì)象的狀態(tài)),而且還提供了額外的功能,幫助程序員以簡(jiǎn)單和可維護(hù)的方式管理對(duì)象。
在我們研究類所提供的功能之前,讓我們先看看我們上面寫的代碼的一個(gè)主要缺點(diǎn)。我們可以把上面寫的代碼改成
p1 = {'name' : "ABC" , 'street': "Central Street - 1" }
p2 = {'names' : "DEF" , 'street': "Central Street - 2", "NewVar" : "Not Needed" }
# and
class PostalAddress:
pass
cP1 = PostalAddress()
cP1.name = "ABC"
cP2 = PostalAddress()
cP2.names = "DEF"
cP2.NewVar = "Not Needed"
如果你沒(méi)有注意到,鍵 "name "被錯(cuò)寫成 "names",并且在 "p2 "和 "cP2 "中分別創(chuàng)建了額外的 "NewVar"。然而,兩者都是有效的字典和對(duì)象。
這就是我們使用類來(lái)定義藍(lán)圖的地方,這樣每個(gè)相同類型的對(duì)象都有相同的特征。
Python 提供了一個(gè)特殊的成員函數(shù)叫做 __init__,每次我們創(chuàng)建一個(gè)類的實(shí)例時(shí)都會(huì)調(diào)用這個(gè)函數(shù),這個(gè)函數(shù)被用來(lái)創(chuàng)建對(duì)象的藍(lán)圖。
有時(shí) __init__也被稱為構(gòu)造函數(shù)。
__init__(...)函數(shù)
Python 為對(duì)象提供了特殊的函數(shù),其前綴和后綴是"__"。
我們先前使用的函數(shù) __dict__也是一個(gè)類似的特殊函數(shù)。
當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí),函數(shù) __init__被調(diào)用。這個(gè)函數(shù)需要一個(gè)強(qiáng)制性的參數(shù),稱為self,這里self指的是對(duì)象本身。
class PostalAddress:
def __init__(self):
pass
在這個(gè)
__init__
函數(shù)中,我們創(chuàng)建了局部實(shí)例變量,這些變量定義了由這個(gè)類構(gòu)建的對(duì)象的狀態(tài)(可識(shí)別的特征)。
class PostalAddress:
def __init__(self):
self.name = "ABC"
self.street = "Central Street - 1"
cP1 = PostalAddress()
print(cP1.__dict__)
self在Python中不是一個(gè)關(guān)鍵字,它只是一個(gè)規(guī)范,用self這個(gè)詞作為對(duì)自己的引用,然而,我們可以為它命名任何我們想要的東西,比如說(shuō)
class PostalAddress。
def __init__(theClassInstance):
theClassInstance.name = "ABC"
theClassInstance.street = "Centeral Street - 1"
然而,self的用法是如此的普遍,以至于它已經(jīng)成為類中方法的一個(gè)事實(shí)上的關(guān)鍵詞,很難找到一個(gè)程序員除了使用self之外還使用其他的東西。在進(jìn)一步的例子中,我也將使用self來(lái)描述類的實(shí)例,即對(duì)象。
向類中添加函數(shù)
類中的函數(shù)提供了對(duì)象的行為方面。讓我創(chuàng)建一個(gè)函數(shù)來(lái)打印對(duì)象的當(dāng)前狀態(tài)。這個(gè)函數(shù)將被稱為prnInfo,看起來(lái)像
class PostalAddress:
def __init__(self):
self.name = "ABC"
self.street = "Centeral Street - 1"
def prnInfo(self):
print("Name =>", self.name, " Street =>", self.street)
cP1 = PostalAddress()
cP1.prnInfo()
就像 __init__ 函數(shù)一樣,所有的函數(shù)都把self作為一個(gè)強(qiáng)制參數(shù)。然而,當(dāng)我們調(diào)用函數(shù)時(shí),我們不需要向它傳遞任何參數(shù),因?yàn)?python 自動(dòng)將對(duì)象實(shí)例作為self
我們也可以使用類而不是對(duì)象來(lái)調(diào)用函數(shù),然而,如果我們想通過(guò)類來(lái)調(diào)用方法,我們需要明確地傳遞實(shí)例,即 self,因?yàn)楫?dāng)我們通過(guò)類來(lái)調(diào)用實(shí)例時(shí),Python 不知道該把哪個(gè)實(shí)例作為 self。
使用類的調(diào)用應(yīng)該看起來(lái)像
cP1 = PostalAddress()
# 使用類來(lái)調(diào)用函數(shù),我們提供實(shí)例
PostalAddress.prnInfo(cP1)
# 這與
cP1.prnInfo()
參數(shù)化的__init__
PostalAddress這個(gè)類類似于一個(gè)靜態(tài)字典,因?yàn)槲覀円呀?jīng)硬編碼了名稱和街道。然而,我們想用不同的值來(lái)創(chuàng)建不同的實(shí)例對(duì)象。
為了達(dá)到同樣的目的,在__init__函數(shù)中把這些值作為輸入?yún)?shù),正如下面的代碼所描述的那樣
# PostalAddress將名字和街道作為輸入?yún)?shù)
class PostalAddress:
def __init__(self, name, street):
self.name = name
self.street = street
def prnInfo(self):
print("Name =>", self.name, " Street =>", self.street)
cP1 = PostalAddress("ABC", "Central Street - 1")
cP1.prnInfo()
cP2 = PostalAddress("DEF", "Central Street - 2")
cP2.prnInfo()
__init__的多種變化
不像其他面向?qū)ο蟮木幊陶Z(yǔ)言,如 "Java "和 "C++",Python 沒(méi)有提供用不同參數(shù)集重載
__init__
的機(jī)制。任何后來(lái)的函數(shù)定義都會(huì)覆蓋之前的函數(shù)。
避免這種情況的一個(gè)方法是為 __init__ 函數(shù)提供默認(rèn)參數(shù),這樣我們就可以使用變量參數(shù)創(chuàng)建實(shí)例。
class PostalAddress。
def __init__(self, name = "Default Name", street = "Central Street - 1")。
self.name = name
self.street = street
cP0 = PostalAddress();
# 0參數(shù)
cP1 = PostalAddress("ABC")
# 1個(gè)參數(shù)
cP2 = PostalAddress("DEF", "Central Street - 2")
# 2參數(shù)
在多個(gè)函數(shù)中創(chuàng)建實(shí)例變量
并不是說(shuō)我們只能在__init__函數(shù)中創(chuàng)建實(shí)例變量。我們可以在類的任何成員函數(shù)中做同樣的事情。
唯一的問(wèn)題是,這些實(shí)例變量只有在創(chuàng)建實(shí)例變量的函數(shù)被調(diào)用時(shí)才會(huì)被創(chuàng)建。
class PostalAddress:
def __init__(self, name = "Default Name", street = "Central Street - 1"):
self.name = name
self.street = street
def createMember(self):
self.newMember = "Temporary Value"
cP0 = PostalAddress();
print(cP0.__dict__);
# prints {'name': 'Default Name', 'street': 'Central Street - 1'}
cP0.createMember();
print(cP0.__dict__);
# print {'name': 'Default Name', 'street': 'Central Street - 1', 'newMember': 'Temporary Value'}
類變量
到目前為止,我們一直以居住在一個(gè)社區(qū)的人為例來(lái)編寫我們的代碼。大多數(shù)時(shí)候,一個(gè)小社區(qū)里的人的地址都有一些共同的信息,比如說(shuō)郵政編碼。
這意味著PostalAddress類的所有對(duì)象必須有相同的郵政編碼。
實(shí)現(xiàn)這個(gè)目的的一個(gè)方法是在init方法中硬編碼郵政編碼,這樣它就可以在這個(gè)類中創(chuàng)建的每一個(gè)對(duì)象中使用。
class PostalAddress:
def __init__(self, name = "Default Name", street = "Central Street - 1"):
self.name = name
self.street = street
self.postalcode = 12345 # 硬編碼郵政編碼
cP0 = PostalAddress();
print(cP0.__dict__); # 打印 {'postalcode': 12345, 'name': 'Default Name', ' street': 'Central Street - 1'}.
cP1 = PostalAddress("ABC")
print(cP1.__dict__); # prints {'postalcode': 12345, 'name': 'ABC', ' street': 'Central Street - 1'}
然而,如果我們以后想改變郵政編碼,這就帶來(lái)了一個(gè)問(wèn)題。唯一的辦法是為每個(gè)對(duì)象實(shí)例手動(dòng)改變,如
cP0.postalcode = 54321
cP1.postalcode = 54321
...
...
cPn.postalcode = 54321
這不僅是繁瑣的,而且容易出錯(cuò)。幸運(yùn)的是,在這些情況下,我們可以通過(guò)類變量來(lái)拯救我們的生活。
類變量是在類中聲明的變量,但不在類的任何方法中。
這個(gè)變量對(duì)PostalAddress的所有實(shí)例都可用。讓我們看看我們?nèi)绾味x類變量
class PostalAddress:
postalCode = 12345; # class Variable
def __init__(self, name = "Default Name", street = "Central Street - 1")。
self.name = name
self.street = street
cP0 = PostalAddress()
print(cP0.postalCode) # print 12345
在研究如何改變類變量以使類的每一個(gè)實(shí)例都被更新為新的postalCode之前,我們需要了解類變量和實(shí)例變量之間的一個(gè)主要區(qū)別
類變量被定義為類的一部分,而不是實(shí)例的一部分
如果我們使用 print(cP0.__dict__)打印實(shí)例 cP0 的字典,我們不會(huì)在其中找到 'postalCode' 。
print(cP0.__dict__)
# printts {'street': 'Central Street - 1', 'name': ' Default Name'}
為了找到'postalCode',我們需要打印該類的字典,可以打印為
print(PostalAddress.__dict__)
# 打印 {'postalCode': 12345. .... }以及其他許多東西
這是 python 的查找序列,它首先查看 "實(shí)例",如果沒(méi)有找到變量/函數(shù),它就查看 "類"。
這就是我們可以使用實(shí)例對(duì)象打印cP0.postalCode的原因。
類的方法
可以通過(guò)兩種方式改變類的變量,要么使用類本身,要么使用類的函數(shù)。如果使用類本身來(lái)改變,我們需要寫下以下代碼
PostalAddress.postalCode = 54321
這不僅會(huì)改變現(xiàn)有實(shí)例的 "postalCode",也會(huì)改變這一行執(zhí)行后創(chuàng)建的所有實(shí)例的 "postalCode"。
# 實(shí)例創(chuàng)建前
cP0 = PostalAddress()
cP1 = PostalAddress()
PostalAddress.postalCode = 54321
# 改變后創(chuàng)建的實(shí)例
cP2 = PostalAddress()
print(cP0.postalCode) # 打印出54321
print(cP1.postalCode) # 打印54321
print(cP2.postalCode) #打印54321
另一種實(shí)現(xiàn)的方法是使用Python中的類方法。類方法是一種特殊的方法,它以類為參數(shù),而不是以實(shí)例即self為參數(shù)。
在定義類方法的時(shí)候,我們需要使用關(guān)鍵字@classmethod明確地告訴Python這個(gè)方法是一個(gè)類方法。
class PostalAddress:
postalCode = 12345; # class Variable
def __init__(self, name = "Default Name", street = "Central Street - 1"):
self.name = name
self.street = street
@classmethod
def newPostalCode(cls, newcode):
cls.postalCode = newcode
實(shí)例方法將self作為強(qiáng)制參數(shù),而類方法將class作為強(qiáng)制參數(shù)。
在這兩種情況下,我們都不需要明確地傳遞Instance或Class,Python會(huì)自動(dòng)處理它。
現(xiàn)在我們可以通過(guò)Instance或者Class來(lái)調(diào)用這個(gè)新方法newPostalCode。在這兩種情況下,最終的結(jié)果都是一樣的。下面是我們?nèi)绾问褂脤?shí)例調(diào)用這個(gè)方法的
cP0 = PostalAddress()
cP1 = PostalAddress()
# 使用實(shí)例調(diào)用
cP0.newPostalCode(9999)
cP2 = PostalAddress()
print(cP0.postalCode) # 打印9999
print(cP1.postalCode) # 打印9999
print(cP2.postalCode) # 打印 9999
而我們可以使用類來(lái)調(diào)用
cP0 = PostalAddress()
cP1 = PostalAddress()
# 使用實(shí)例調(diào)用
PostalAddress.newPostalCode(9999)
cP2 = PostalAddress()
print(cP0.postalCode) # 打印9999
print(cP1.postalCode) # 打印9999
print(cP2.postalCode) #打印9999
這就是關(guān)于面向?qū)ο蟮腜ython的基本介紹,應(yīng)該足以讓讀者理解類和對(duì)象之間的區(qū)別,并允許他們編寫基本的面向?qū)ο蟮腜ython代碼。
本文由mdnice多平臺(tái)發(fā)布