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