本文收錄在Python從入門到精通系列文章系列html
活在當下的程序員應該都聽過"面向對象編程"一詞,也常常有人問能不能用一句話解釋下什麼是"面向對象編程",咱們先來看看比較正式的說法。程序員
"把一組數據結構和處理它們的方法組成對象(object),把相同行爲的對象概括爲類(class),經過類的封裝(encapsulation)隱藏內部細節,經過繼承(inheritance)實現類的特化(specialization)和泛化(generalization),經過多態(polymorphism)實現基於對象類型的動態分派。"編程
這樣一說是否是更不明白了。因此咱們仍是看看更通俗易懂的說法,請看下圖:數據結構
以前咱們說過"程序是指令的集合",咱們在程序中書寫的語句在執行時會變成一條或多條指令而後由CPU去執行。固然爲了簡化程序的設計,咱們引入了函數的概念,把相對獨立且常常重複使用的代碼放置到函數中,在須要使用這些功能的時候只要調用函數便可;若是一個函數的功能過於複雜和臃腫,咱們又能夠進一步將函數繼續切分爲子函數來下降系統的複雜性。編程語言
可是說了這麼多,不知道你們是否發現,所謂編程就是程序員按照計算機的工做方式控制計算機完成各類任務。可是,計算機的工做方式與正常人類的思惟模式是不一樣的,若是編程就必須得拋棄人類正常的思惟方式去迎合計算機,編程的樂趣就少了不少,"每一個人都應該學習編程"這樣的豪言壯語就只能說說而已。固然,這些還不是最重要的,最重要的是當咱們須要開發一個複雜的系統時,代碼的複雜性會讓開發和維護工做都變得舉步維艱,因此在上世紀60年代末期,"軟件危機"、"軟件工程"等一系列的概念開始在行業中出現。函數
固然,程序員圈子內的人都知道,現實中並無解決上面所說的這些問題的"銀彈",真正讓軟件開發者看到但願的是上世紀70年代誕生的Smalltalk編程語言中引入的面向對象的編程思想(面向對象編程的雛形能夠追溯到更早期的Simula語言)。按照這種編程理念,程序中的數據和操做數據的函數是一個邏輯上的總體,咱們稱之爲「對象」,而咱們解決問題的方式就是建立出須要的對象並向對象發出各類各樣的消息,多個對象的協同工做最終可讓咱們構造出複雜的系統來解決現實中的問題。學習
說明: 固然面向對象也不是解決軟件開發中全部問題的最後的「銀彈」,因此今天的高級程序設計語言幾乎都提供了對多種編程範式的支持,Python也不例外。spa
簡單的說,類是對象的藍圖和模板,而對象是類的實例。這個解釋雖然有點像用概念在解釋概念,可是從這句話咱們至少能夠看出,類是抽象的概念,而對象是具體的東西。在面向對象編程的世界中,一切皆爲對象,對象都有屬性和行爲,每一個對象都是獨一無二的,並且對象必定屬於某個類(型)。當咱們把一大堆擁有共同特徵的對象的靜態特徵(屬性)和動態特徵(行爲)都抽取出來後,就能夠定義出一個叫作「類」的東西。設計
在Python中可使用class關鍵字定義類,而後在類中經過以前學習過的函數來定義方法,這樣就能夠將對象的動態特徵描述出來,代碼以下所示。code
class Student(object): # __init__是一個特殊方法用於在建立對象時進行初始化操做 # 經過這個方法咱們能夠爲學生對象綁定name和age兩個屬性 def __init__(self, name, age): self.name = name self.age = age def study(self, course_name): print('%s正在學習%s.' % (self.name, course_name)) # PEP 8要求標識符的名字用全小寫多個單詞用下劃線鏈接 # 可是部分程序員和公司更傾向於使用駝峯命名法(駝峯標識) def watch_movie(self): if self.age < 18: print('%s只能觀看《熊出沒》.' % self.name) else: print('%s正在觀看島國愛情大電影.' % self.name)
說明: 寫在類中的函數,咱們一般稱之爲(對象的)方法,這些方法就是對象能夠接收的消息。
當咱們定義好一個類以後,能夠經過下面的方式來建立對象並給對象發消息。
def main(): # 建立學生對象並指定姓名和年齡 stu1 = Student('along', 66) # 給對象發study消息 stu1.study('Python從入門到精通系列文章總目錄') # 給對象發watch_av消息 stu1.watch_movie() stu2 = Student('小黑', 15) stu2.study('數學') stu2.watch_movie() main()
對於上面的代碼,有C++、Java、C#等編程經驗的程序員可能會問,咱們給Student對象綁定的name和age屬性到底具備怎樣的訪問權限(也稱爲可見性)。由於在不少面向對象編程語言中,咱們一般會將對象的屬性設置爲私有的(private)或受保護的(protected),簡單的說就是不容許外界訪問,而對象的方法一般都是公開的(public),由於公開的方法就是對象可以接受的消息。在Python中,屬性和方法的訪問權限只有兩種,也就是公開的和私有的,若是但願屬性是私有的,在給屬性命名時能夠用兩個下劃線做爲開頭,下面的代碼能夠驗證這一點。
class Test: def __init__(self, foo): self.__foo = foo def __bar(self): print(self.__foo) print('__bar') def main(): test = Test('hello') # AttributeError: 'Test' object has no attribute '__bar' # test.__bar() # AttributeError: 'Test' object has no attribute '__foo' # print(test.__foo) if __name__ == "__main__": main()
可是,Python並無從語法上嚴格保證私有屬性或方法的私密性,它只是給私有的屬性和方法換了一個名字來妨礙對它們的訪問,事實上若是你知道更換名字的規則仍然能夠訪問到它們,下面的代碼就能夠驗證這一點。之因此這樣設定,能夠用這樣一句名言加以解釋,就是"We are all consenting adults here"。由於絕大多數程序員都認爲開放比封閉要好,並且程序員要本身爲本身的行爲負責。
class Test: def __init__(self, foo): self.__foo = foo def __bar(self): print(self.__foo) print('__bar') def main(): test = Test('hello') test._Test__bar() print(test._Test__foo) if __name__ == "__main__": main()
在實際開發中,咱們並不建議將屬性設置爲私有的,由於這會致使子類沒法訪問(後面會講到)。因此大多數Python程序員會遵循一種命名慣例就是讓屬性名以單下劃線開頭來表示屬性是受保護的,本類以外的代碼在訪問這樣的屬性時應該要保持慎重。這種作法並非語法上的規則,單下劃線開頭的屬性和方法外界仍然是能夠訪問的,因此更多的時候它是一種暗示或隱喻.
面向對象有三大支柱:封裝、繼承和多態。後面兩個概念在下一篇中進行詳細的說明,這裏咱們先說一下什麼是封裝。我本身對封裝的理解是"隱藏一切能夠隱藏的實現細節,只向外界暴露(提供)簡單的編程接口"。咱們在類中定義的方法其實就是把數據和對數據的操做封裝起來了,在咱們建立了對象以後,只須要給對象發送一個消息(調用方法)就能夠執行方法中的代碼,也就是說咱們只須要知道方法的名字和傳入的參數(方法的外部視圖),而不須要知道方法內部的實現細節(方法的內部視圖)。
參考答案:
from time import sleep class Clock(object): """數字時鐘""" def __init__(self, hour=0, minute=0, second=0): """初始化方法 :param hour: 時 :param minute: 分 :param second: 秒 """ self._hour = hour self._minute = minute self._second = second def run(self): """走字""" self._second += 1 if self._second == 60: self._second = 0 self._minute += 1 if self._minute == 60: self._minute = 0 self._hour += 1 if self._hour == 24: self._hour = 0 def show(self): """顯示時間""" return '%02d:%02d:%02d' % \ (self._hour, self._minute, self._second) def main(): clock = Clock(23, 59, 58) while True: print(clock.show()) sleep(1) clock.run() if __name__ == '__main__': main()
輸出結果:
23:59:58
23:59:59
00:00:00
... ...
參考答案:
from math import sqrt class Point(object): def __init__(self, x=0, y=0): """初始化方法 :param x: 橫座標 :param y: 縱座標 """ self.x = x self.y = y def move_to(self, x, y): """移動到指定位置 :param x: 新的橫座標 "param y: 新的縱座標 """ self.x = x self.y = y def move_by(self, dx, dy): """移動指定的增量 :param dx: 橫座標的增量 "param dy: 縱座標的增量 """ self.x += dx self.y += dy def distance_to(self, other): """計算與另外一個點的距離 :param other: 另外一個點 """ dx = self.x - other.x dy = self.y - other.y return sqrt(dx ** 2 + dy ** 2) def __str__(self): return '(%s, %s)' % (str(self.x), str(self.y)) def main(): p1 = Point(1, 2) p2 = Point(2, 1) print(p1) print(p2) p2.move_by(-1, 2) print(p2) print(p1.distance_to(p2))
輸出結果:
(1, 2) (2, 1) (1, 3) 1.0
說明: 本章中的插圖來自於Grady Booch等著做的《面向對象分析與設計》一書,該書是講解面向對象編程的經典著做,有興趣的讀者能夠購買和閱讀這本書來了解更多的面向對象的相關知識。