現(xiàn)在開始不用偽代碼了,用真正的python代碼來理解類。當然,例子還是要用讀者感興趣的例子。
因為python是一個不斷發(fā)展的高級語言(似乎別的語言是不斷發(fā)展的,甚至于自然語言也是),導致了在python2.x的版本中,有“新式類”和“舊式類(也叫做經(jīng)典類)”之分。新式類是python2.2引進的,在此后的版本中,我們一般用的都是新式類。本著知其然還要知其所以然的目的,簡單回顧一下兩者的差別。
>>> class AA:
... pass
...
這是定義了一個非常簡單的類,而且是舊式類。至于如何定義類,下面會詳細說明。讀者姑且囫圇吞棗似的的認同我剛才建立的名為AA
的類,為了簡單,這個類內(nèi)部什么也不做,就是用pass
一帶而過。但不管怎樣,是一個類,而且是一個舊式類(或曰經(jīng)典類)
然后,將這個類實例化(還記得上節(jié)中實例化嗎?對,就是那個王美女干的事情):
>>> aa = AA()
不要忘記,實例化的時候,類的名稱后面有一對括號。接下來做如下操作:
>>> type(AA)
<type 'classobj'>
>>> aa.__class__
<class __main__.AA at 0xb71f017c>
>>> type(aa)
<type 'instance'>
解讀一下上面含義:
type(AA)
:查看類AA
的類型,返回的是'classobj'
aa.__class__
:aa是一個實例,也是一個對象,每個對象都有__class__
屬性,用于顯示它的類型。這里返回的結(jié)果是<class __main__.AA at 0xb71f017c>
,從這個結(jié)果中可以讀出的信息是,aa是類AA的實例,并且類AA在內(nèi)存中的地址是0xb71f017c
。type(aa)
:是要看實例aa的類型,它顯示的結(jié)果是'instance
,意思是告訴我們它的類型是一個實例。在這里是不是有點感覺不和諧呢?aa.__class__
和type(aa)
都可以查看對象類型,但是它們居然顯示不一樣的結(jié)果。比如,查看這個對象:
>>> a = 7
>>> a.__class__
<type 'int'>
>>> type(a)
<type 'int'>
別忘記了,前面提到過的“萬物皆對象”,那么一個整數(shù)7也是對象,用兩種方式查看,返回的結(jié)果一樣。為什么到類(嚴格講是舊式類)這里,居然返回不一樣呢?太不和諧了。
于是乎,就有了新式類,從python2.2開始,變成這樣了:
>>> class BB(object):
... pass
...
>>> bb = BB()
>>> bb.__class__
<class '__main__.BB'>
>>> type(bb)
<class '__main__.BB'>
終于把兩者統(tǒng)一起來了,世界和諧了。
這就是新式類和舊式類的不同。
當然,不同點絕非僅僅于此,這里只不過提到一個現(xiàn)在能夠理解的不同罷了。另外的不同還在于兩者對于多重繼承的查找和調(diào)用方法不同,舊式類是深度優(yōu)先,新式類是廣度優(yōu)先??梢韵炔焕斫?,后面會碰到的。
不管是新式類、還是舊式類,都可以通過這樣的方法查看它們在內(nèi)存中的存儲空間信息
>>> print aa
<__main__.AA instance at 0xb71efd4c>
>>> print bb
<__main__.BB object at 0xb71efe6c>
分別告訴了我們兩個實例是基于誰生成的,不過還是稍有區(qū)別。
知道了舊式類和新式類,那么下面的所有內(nèi)容,就都是對新式類而言?!跋残聟捙f”不是編程經(jīng)常干的事情嗎?所以,舊式類就不是我們討論的內(nèi)容了。
還要注意,如果你用的是python3,就不用為新式類和舊式類而擔心了,因為在python3中壓根兒就沒有這個問題存在。
如何定義新式類呢?
第一種定義方法,就是如同前面那樣:
>>> class BB(object):
... pass
...
跟舊式類的區(qū)別就在于類的名字后面跟上(object)
,這其實是一種名為“繼承”的類的操作,當前的類BB是以類object為上級的(object被稱為父類),即BB是繼承自類object的新類。在python3中,所有的類自然地都是類object的子類,就不用彰顯出繼承關(guān)系了。對了,這里說的有點讓讀者糊涂,因為冒出來了“繼承”、“父類”、“子類”,不用著急,繼續(xù)向下看。下面精彩,并且能解惑。
第二種定義方法,在類的前面寫上這么一句:__metaclass__ == type
,然后定義類的時候,就不需要在名字后面寫(object)
了。
>>> __metaclass__ = type
>>> class CC:
... pass
...
>>> cc = CC()
>>> cc.__class__
<class '__main__.CC'>
>>> type(cc)
<class '__main__.CC'>
兩種方法,任你選用,沒有優(yōu)劣之分。
因為在一般情況下,一個類都不是兩三行能搞定的。所以,下面可能很少使用交互模式了,因為那樣一旦有一點錯誤,就前功盡棄。我改用編輯界面。你用什么工具編輯?python自帶一個IDE,可以使用。我習慣用vim。你用你習慣的工具即可。如果你沒有別的工具,就用安裝python是自帶的那個IDE。
#!/usr/bin/env python
# coding=utf-8
__metaclass__ = type
class Person:
def __init__(self, name):
self.name = name
def getName(self):
return self.name
def color(self, color):
print "%s is %s" % (self.name, color)
上面定義的是一個比較常見的類,一般情況下,都是這樣子的。下面對這個“大眾臉”的類一一解釋。
__metaclass__ = type
,意味著下面的類是新式類。
class Person
,這是在聲明創(chuàng)建一個名為"Person"的類。類的名稱一般用大寫字母開頭,這是慣例。如果名稱是兩個單詞,那么兩個單詞的首字母都要大寫,例如class HotPerson
,這種命名方法有一個形象的名字,叫做“駝峰式命名”。當然,如果故意不遵循此慣例,也未嘗不可,但是,會給別人閱讀乃至于自己以后閱讀帶來麻煩,不要忘記“代碼通常是給人看的,只是偶爾讓機器執(zhí)行”。既然大家都是靠右走的,你就別非要在路中間睡覺了。
接下來,分別以縮進表示的,就是這個類的內(nèi)容了。其實那些東西看起來并不陌生,你一眼就認出它們了——就是已經(jīng)學習過的函數(shù)。沒錯,它們就是函數(shù)。不過,很多程序員喜歡把類里面的函數(shù)叫做“方法”。是的,就是上節(jié)中說到的對象的“方法”。我也看到有人撰文專門分析了“方法”和“函數(shù)”的區(qū)別。但是,我倒是認為這不重要,重要的是類的中所謂“方法”和前面的函數(shù),在數(shù)學角度看,絲毫沒有區(qū)別。所以,你盡可以稱之為函數(shù)。當然,聽到有人說方法,也不要詫異和糊涂。它們本質(zhì)是一樣的。
需要再次提醒,函數(shù)的命名方法是以def
發(fā)起,并且函數(shù)名稱首字母不要用大寫,可以使用aa_bb
的樣式,也可以使用aaBb
的樣式,一切看你的習慣了。
不過,要注意的是,類中的函數(shù)(方法)的參數(shù)跟以往的參數(shù)樣式有區(qū)別,那就是每個函數(shù)必須包括self
參數(shù),并且作為默認的第一個參數(shù)。這是需要注意的地方。至于它的用途,繼續(xù)學習即可知道。
def __init__
,這個函數(shù)是一個比較特殊的,并且有一個名字,叫做初始化函數(shù)(注意,很多教材和資料中,把它叫做構(gòu)造函數(shù),這種說法貌似沒有錯誤,但是一來從字面意義上看,它對應(yīng)的含義是初始化,二來在python中它的作用和其它語言比如java中的構(gòu)造函數(shù)還不完全一樣,因為還有一個__new__
的函數(shù),是真正地構(gòu)造。所以,在本教程中,我稱之為初始化函數(shù))。它是以兩個下劃線開始,然后是init,最后以兩個下劃線結(jié)束。
所謂初始化,就是讓類有一個基本的面貌,而不是空空如也。做很多事情,都要初始化,讓事情有一個具體的起點狀態(tài)。比如你要喝水,必須先初始化杯子里面有水。在python的類中,初始化就擔負著類似的工作。這個工作是在類被實例化的時候就執(zhí)行這個函數(shù),從而將初始化的一些屬性可以放到這個函數(shù)里面。
此例子中的初始化函數(shù),就意味著實例化的時候,要給參數(shù)name提供一個值,作為類初始化的內(nèi)容。通俗點啰嗦點說,就是在這個類被實例化的同時,要通過name參數(shù)傳一個值,這個值被一開始就寫入了類和實例中,成為了類和實例的一個屬性。比如:
girl = Person('canglaoshi')
girl是一個實例對象,就如同前面所說的一樣,它有屬性和方法。這里僅說屬性吧。當通過上面的方式實例化后,就自動執(zhí)行了初始化函數(shù),讓實例girl就具有了name屬性。
print girl.name
執(zhí)行這句話的結(jié)果是打印出canglaoshi
。
這就是初始化的功能。簡而言之,通過初始化函數(shù),確定了這個實例(類)的“基本屬性”(實例是什么樣子的)。比如上面的實例化之后,就確立了實例girl的name是"canglaoshi"。
初始化函數(shù),就是一個函數(shù),所以,它的參數(shù)設(shè)置,也符合前面學過的函數(shù)參數(shù)設(shè)置規(guī)范。比如
def __init__(self,*args):
pass
這種類型的參數(shù):*args和前面講述函數(shù)參數(shù)一樣,就不多說了。忘了的看官,請去復習。但是,self這個參數(shù)是必須的。
很多時候,并不是每次都要從外面?zhèn)魅霐?shù)據(jù),有時候會把初始化函數(shù)的某些參數(shù)設(shè)置默認值,如果沒有新的數(shù)據(jù)傳入,就應(yīng)用這些默認值。比如:
class Person:
def __init__(self, name, lang="golang", website="www.google.com"):
self.name = name
self.lang = lang
self.website = website
self.email = "qiwsir@gmail.com"
laoqi = Person("LaoQi")
info = Person("qiwsir",lang="python",website="qiwsir.github.io")
print "laoqi.name=",laoqi.name
print "info.name=",info.name
print "-------"
print "laoqi.lang=",laoqi.lang
print "info.lang=",info.lang
print "-------"
print "laoqi.website=",laoqi.website
print "info.website=",info.website
#運行結(jié)果
laoqi.name= LaoQi
info.name= qiwsir
-------
laoqi.lang= golang
info.lang= python
-------
laoqi.website= www.google.com
info.website= qiwsir.github.io
在編程界,有這樣一句話,說“類是實例工廠”,什么意思呢?工廠是干什么的?生產(chǎn)物品,比如生產(chǎn)電腦。一個工廠可以生產(chǎn)好多電腦。那么,類,就能“生產(chǎn)”好多實例,所以,它是“工廠”。比如上面例子中,就有兩個實例。
還是回到本節(jié)開頭的那個類。構(gòu)造函數(shù)下面的兩個函數(shù):def getName(self)
,def color(self, color)
,這兩個函數(shù)和前面的初始化函數(shù)有共同的地方,即都是以self作為第一個參數(shù)。
def getName(self):
return self.name
這個函數(shù)中的作用就是返回在初始化時得到的值。
girl = Person('canglaoshi')
name = girl.getName()
girl.getName()
就是調(diào)用實例girl的方法。調(diào)用該方法的時候特別注意,方法名后面的括號不可少,并且括號中不要寫參數(shù),在類中的getName(self)
函數(shù)第一個參數(shù)self是默認的,當類實例化之后,調(diào)用此函數(shù)的時候,第一個參數(shù)不需要賦值。那么,變量name的最終結(jié)果就是name = "canglaoshi"
。
同樣道理,對于方法:
def color(self, color):
print "%s is %s" % (self.name, color)
也是在實例化之后調(diào)用:
girl.color("white")
這也是在執(zhí)行實例化方法,只是由于類中的該方法有兩個參數(shù),除了默認的self之外,還有一個color,所以,在調(diào)用這個方法的時候,要為后面那個參數(shù)傳值了。
至此,已經(jīng)將這個典型的類和調(diào)用方法分解完畢,把全部代碼完整貼出,請讀者在從頭到尾看看,是否理解了每個部分的含義:
#!/usr/bin/env python
# coding=utf-8
__metaclass__ = type #新式類
class Person: #創(chuàng)建類
def __init__(self, name): #構(gòu)造函數(shù)
self.name = name
def getName(self): #類中的方法(函數(shù))
return self.name
def color(self, color):
print "%s is %s" % (self.name, color)
girl = Person('canglaoshi') #實例化
name = girl.getName() #調(diào)用方法(函數(shù))
print "the person's name is: ", name
girl.color("white") #調(diào)用方法(函數(shù))
print "------"
print girl.name #實例的屬性
保存后,運行得到如下結(jié)果:
$ python 20701.py
the person's name is: canglaoshi
canglaoshi is white
------
canglaoshi
有必要總結(jié)一下類和實例的關(guān)系:
class Person
,class是一個可執(zhí)行的語句。如果執(zhí)行,就得到了一個類對象,并且將這個類對象賦值給對象名(比如Person)。也許上述比較還不足以讓看官理解類和實例,沒關(guān)系,繼續(xù)學習,在前進中排除疑惑。
類里面的函數(shù),第一個參數(shù)是self,但是在實例化的時候,似乎沒有這個參數(shù)什么事兒,那么self是干什么的呢?
self是一個很神奇的參數(shù)。
在Person實例化的過程中girl = Person("canglaoshi")
,字符串"canglaoshi"通過初始化函數(shù)(__init__()
)的參數(shù)已經(jīng)存入到內(nèi)存中,并且以Person類型的面貌存在,組成了一個對象,這個對象和變量girl建立引用關(guān)系。這個過程也可說成這些數(shù)據(jù)附加到一個實例上。這樣就能夠以:object.attribute
的形式,在程序中任何地方調(diào)用某個數(shù)據(jù),例如上面的程序中以girl.name
的方式得到"canglaoshi"
。這種調(diào)用方式,在類和實例中經(jīng)常使用,點號“.”后面的稱之為類或者實例的屬性。
這是在程序中,并且是在類的外面。如果在類的里面,想在某個地方使用實例化所傳入的數(shù)據(jù)("canglaoshi"),怎么辦?
在類內(nèi)部,就是將所有傳入的數(shù)據(jù)都賦給一個變量,通常這個變量的名字是self。注意,這是習慣,而且是共識,所以,看官不要另外取別的名字了。
在初始化函數(shù)中的第一個參數(shù)self,就是起到了這個作用——接收實例化過程中傳入的所有數(shù)據(jù),這些數(shù)據(jù)是初始化函數(shù)后面的參數(shù)導入的。顯然,self應(yīng)該就是一個實例(準確說法是應(yīng)用實例),因為它所對應(yīng)的就是具體數(shù)據(jù)。
如果將上面的類稍加修改,看看效果:
#!/usr/bin/env python
# coding=utf-8
__metaclass__ = type
class Person:
def __init__(self, name):
self.name = name
print self #新增
print type(self) #新增
其它部分省略。當初始化的時候,就首先要運行構(gòu)造函數(shù),同時就打印新增的兩條。結(jié)果是:
<__main__.Person object at 0xb7282cec>
<class '__main__.Person'>
證實了推理。self就是一個實例(準確說是實例的引用變量)。
self這個實例跟前面說的那個girl所引用的實例對象一樣,也有屬性。那么,接下來就規(guī)定其屬性和屬性對應(yīng)的數(shù)據(jù)。上面代碼中:
self.name = name
就是規(guī)定了self實例的一個屬性,這個屬性的名字也叫做name,這個屬性的值等于初始化函數(shù)的參數(shù)name所導入的數(shù)據(jù)。注意,self.name
中的name和初始化函數(shù)的參數(shù)name
沒有任何關(guān)系,它們兩個一樣,只不過是一種起巧合(經(jīng)常巧合,其實是為了省事和以后識別方便,故意讓它們巧合。),或者說是寫代碼的人懶惰,不想另外取名字而已,無他。當然,如果寫成self.xxxooo = name
,也是可以的。
其實,從效果的角度來理解,這么理解更簡化:類的實例girl對應(yīng)著self,girl通過self導入實例屬性的所有數(shù)據(jù)。
當然,self的屬性數(shù)據(jù),也不一定非得是由參數(shù)傳入的,也可以在構(gòu)造函數(shù)中自己設(shè)定。比如:
#!/usr/bin/env python
#coding:utf-8
__metaclass__ = type
class Person:
def __init__(self, name):
self.name = name
self.email = "qiwsir@gmail.com" #這個屬性不是通過參數(shù)傳入的
info = Person("qiwsir") #換個字符串和實例化變量
print "info.name=",info.name
print "info.email=",info.email #info通過self建立實例,并導入實例屬性數(shù)據(jù)
運行結(jié)果
info.name= qiwsir
info.email= qiwsir@gmail.com #打印結(jié)果
通過這個例子,其實讓我們拓展了對self的認識,也就是它不僅僅是為了在類內(nèi)部傳遞參數(shù)導入的數(shù)據(jù),還能在初始化函數(shù)中,通過self.attribute
的方式,規(guī)定self實例對象的屬性,這個屬性也是類實例化對象的屬性,即做為類通過初始化函數(shù)初始化后所具有的屬性。所以在實例info中,通過info.email同樣能夠得到該屬性的數(shù)據(jù)。在這里,就可以把self形象地理解為“內(nèi)外兼修”了?;蛘甙凑涨懊嫠岬降?,將info和self對應(yīng)起來,self主內(nèi),info主外。
怎么樣?在"canglaoshi"的陪伴下,是不是明白了類的奧妙?
更多建議: