鍍金池/ 教程/ Python/ 類(2)
標準庫 (4)
如何成為 Python 高手
標準庫 (6)
標準庫 (3)
類(2)
Pandas 使用 (2)
xml
用 tornado 做網(wǎng)站 (5)
文件(1)
練習
列表(3)
從小工到專家
除法
錯誤和異常 (2)
函數(shù)(1)
用 tornado 做網(wǎng)站 (7)
為做網(wǎng)站而準備
函數(shù)練習
標準庫 (8)
Pandas 使用 (1)
回顧 list 和 str
字典(1)
用 tornado 做網(wǎng)站 (3)
字符串(1)
函數(shù)(2)
寫一個簡單的程序
將數(shù)據(jù)存入文件
語句(5)
SQLite 數(shù)據(jù)庫
集成開發(fā)環(huán)境(IDE)
集合(1)
類(1)
用 tornado 做網(wǎng)站 (6)
用 tornado 做網(wǎng)站 (2)
自省
語句(4)
錯誤和異常 (1)
用 tornado 做網(wǎng)站 (4)
集合(2)
列表(1)
標準庫 (1)
生成器
mysql 數(shù)據(jù)庫 (1)
第三方庫
實戰(zhàn)
運算符
類(3)
字典(2)
語句(1)
數(shù)和四則運算
語句(2)
文件(2)
MySQL 數(shù)據(jù)庫 (2)
電子表格
迭代器
mongodb 數(shù)據(jù)庫 (1)
特殊方法 (2)
特殊方法 (1)
字符編碼
編寫模塊
用 tornado 做網(wǎng)站 (1)
標準庫 (5)
函數(shù)(4)
類(5)
字符串(2)
關(guān)于 Python 的故事
函數(shù)(3)
字符串(4)
處理股票數(shù)據(jù)
常用數(shù)學函數(shù)和運算優(yōu)先級
字符串(3)
為計算做準備
多態(tài)和封裝
類(4)
迭代
語句(3)
錯誤和異常 (3)
分析 Hello
Python 安裝
標準庫 (2)
列表(2)
元組

類(2)

現(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)劣之分。

創(chuàng)建類

因為在一般情況下,一個類都不是兩三行能搞定的。所以,下面可能很少使用交互模式了,因為那樣一旦有一點錯誤,就前功盡棄。我改用編輯界面。你用什么工具編輯?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('wangguniang')

girl 是一個實例對象,就如同前面所說的一樣,它有屬性和方法。這里僅說屬性吧。當通過上面的方式實例化后,就自動執(zhí)行了初始化函數(shù),讓實例 girl 就具有了 name 屬性。

print girl.name

執(zhí)行這句話的結(jié)果是打印出 wangguniang。

這就是初始化的功能。簡而言之,通過初始化函數(shù),確定了這個實例(類)的“基本屬性”(實例是什么樣子的)。比如上面的實例化之后,就確立了實例 girl 的 name 是"wangguniang"。

初始化函數(shù),就是一個函數(shù),所以,它的參數(shù)設(shè)置,也符合前面學過的函數(shù)參數(shù)設(shè)置規(guī)范。比如

def __init__(self,*args):
    pass

這種類型的參數(shù):*args 和前面講述函數(shù)參數(shù)一樣,就不多說了。忘了的看官,請去復(fù)習。但是,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)”好多實例,所以,它是“工廠”。比如上面例子中,就有兩個實例。

函數(shù)(方法)

還是回到本節(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('wangguniang')
name = girl.getName()

girl.getName()就是調(diào)用實例 girl 的方法。調(diào)用該方法的時候特別注意,方法名后面的括號不可少,并且括號中不要寫參數(shù),在類中的 getName(self) 函數(shù)第一個參數(shù) self 是默認的,當類實例化之后,調(diào)用此函數(shù)的時候,第一個參數(shù)不需要賦值。那么,變量 name 的最終結(jié)果就是 name = "wangguniang"。

同樣道理,對于方法:

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('wangguniang')      #實例化
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:  wangguniang
wangguniang is white
------
wangguniang

類和實例

有必要總結(jié)一下類和實例的關(guān)系:

  • “類提供默認行為,是實例的工廠”(源自 Learning Python),這句話非常經(jīng)典,一下道破了類和實例的關(guān)系。所謂工廠,就是可以用同一個模子做出很多具體的產(chǎn)品。類就是那個模子,實例就是具體的產(chǎn)品。所以,實例是程序處理的實際對象。
  • 類是由一些語句組成,但是實例,是通過調(diào)用類生成,每次調(diào)用一個類,就得到這個類的新的實例。
  • 對于類的:class Person,class 是一個可執(zhí)行的語句。如果執(zhí)行,就得到了一個類對象,并且將這個類對象賦值給對象名(比如 Person)。

也許上述比較還不足以讓看官理解類和實例,沒關(guān)系,繼續(xù)學習,在前進中排除疑惑。

self 的作用

類里面的函數(shù),第一個參數(shù)是 self,但是在實例化的時候,似乎沒有這個參數(shù)什么事兒,那么 self 是干什么的呢?

self 是一個很神奇的參數(shù)。

在 Person 實例化的過程中 girl = Person("wangguniang"),字符串"wangguniang"通過初始化函數(shù)(__init__())的參數(shù)已經(jīng)存入到內(nèi)存中,并且以 Person 類型的面貌存在,組成了一個對象,這個對象和變量 girl 建立引用關(guān)系。這個過程也可說成這些數(shù)據(jù)附加到一個實例上。這樣就能夠以:object.attribute的形式,在程序中任何地方調(diào)用某個數(shù)據(jù),例如上面的程序中以 girl.name 的方式得到"wangguniang"。這種調(diào)用方式,在類和實例中經(jīng)常使用,點號“.”后面的稱之為類或者實例的屬性。

這是在程序中,并且是在類的外面。如果在類的里面,想在某個地方使用實例化所傳入的數(shù)據(jù)("wangguniang"),怎么辦?

在類內(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 主外。

怎么樣?是不是明白了類的奧妙?


總目錄   |   上節(jié):類(1)   |   下節(jié):類(3)

如果你認為有必要打賞我,請通過支付寶:qiwsir@126.com,不勝感激。