Python基础——PyCharm版本——第七章、面向对象编程
🤵🤗Python_Base:Chapter vii🤗🤵
目錄
OOP
類和實例
封裝
繼承和多態(tài)
OOP
面向?qū)ο缶幊獭狾bject Oriented Programming,簡稱OOP,是一種程序設(shè)計思想。OOP把對象作為程序的基本單元,一個對象包含了數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)。
面向過程的程序設(shè)計把計算機程序視為一系列的命令集合,即一組函數(shù)的順序執(zhí)行。為了簡化程序設(shè)計,面向過程把函數(shù)繼續(xù)切分為子函數(shù),即把大塊函數(shù)通過切割成小塊函數(shù)來降低系統(tǒng)的復(fù)雜度。
而面向?qū)ο蟮某绦蛟O(shè)計把計算機程序視為一組對象的集合,而每個對象都可以接收其他對象發(fā)過來的消息,并處理這些消息,計算機程序的執(zhí)行就是一系列消息在各個對象之間傳遞。
在Python中,所有數(shù)據(jù)類型都可以視為對象,當(dāng)然也可以自定義對象。自定義的對象數(shù)據(jù)類型就是面向?qū)ο笾械念?#xff08;Class)的概念。
我們以一個例子來說明面向過程和面向?qū)ο笤诔绦蛄鞒躺系牟煌帯?/p>
假設(shè)我們要處理學(xué)生的成績表,為了表示一個學(xué)生的成績,面向過程的程序可以用一個dict表示:
std1 = { 'name': 'Michael', 'score': 98 } std2 = { 'name': 'Bob', 'score': 81 }而處理學(xué)生成績可以通過函數(shù)實現(xiàn),比如打印學(xué)生的成績:
def print_score(std):print('%s: %s' % (std['name'], std['score']))如果采用面向?qū)ο蟮某绦蛟O(shè)計思想,我們首選思考的不是程序的執(zhí)行流程,而是Student這種數(shù)據(jù)類型應(yīng)該被視為一個對象,這個對象擁有name和score這兩個屬性(Property)。如果要打印一個學(xué)生的成績,首先必須創(chuàng)建出這個學(xué)生對應(yīng)的對象,然后,給對象發(fā)一個print_score消息,讓對象自己把自己的數(shù)據(jù)打印出來。
class Student(object):def __init__(self, name, score):self.name = nameself.score = scoredef print_score(self):print('%s: %s' % (self.name, self.score))給對象發(fā)消息實際上就是調(diào)用對象對應(yīng)的關(guān)聯(lián)函數(shù),我們稱之為對象的方法(Method)。面向?qū)ο蟮某绦驅(qū)懗鰜砭拖襁@樣:
bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.print_score() lisa.print_score()面向?qū)ο蟮脑O(shè)計思想是從自然界中來的,因為在自然界中,類(Class)和實例(Instance)的概念是很自然的。Class是一種抽象概念,比如我們定義的Class——Student,是指學(xué)生這個概念,而實例(Instance)則是一個個具體的Student,比如,Bart Simpson和Lisa Simpson是兩個具體的Student。
所以,面向?qū)ο蟮脑O(shè)計思想是抽象出Class,根據(jù)Class創(chuàng)建Instance。
面向?qū)ο蟮某橄蟪潭扔直群瘮?shù)要高,因為一個Class既包含數(shù)據(jù),又包含操作數(shù)據(jù)的方法。
類和實例
面向?qū)ο笞钪匾母拍罹褪穷?#xff08;Class)和實例(Instance),必須牢記類是抽象的模板,比如Student類,而實例是根據(jù)類創(chuàng)建出來的一個個具體的“對象”,每個對象都擁有相同的方法,但各自的數(shù)據(jù)可能不同。
仍以Student類為例,在Python中,定義類是通過class關(guān)鍵字:
class Student(object):passclass后面緊接著是類名,即Student,類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個類繼承下來的,繼承的概念我們后面再講,通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。
定義好了Student類,就可以根據(jù)Student類創(chuàng)建出Student的實例,創(chuàng)建實例是通過類名+()實現(xiàn)的:
class Student(object):pass bart = Student() print(bart) print(Student)可以看到,變量bart指向的就是一個Student的實例,后面的0x10a67a590是內(nèi)存地址,每個object的地址都不一樣,而Student本身則是一個類。
可以自由地給一個實例變量綁定屬性,比如,給實例bart綁定一個name屬性:
class Student(object):pass bart = Student() bart.name = 'Blus li' print(bart.name)由于類可以起到模板的作用,因此,可以在創(chuàng)建實例的時候,把一些我們認為必須綁定的屬性強制填寫進去。通過定義一個特殊的__init__方法,在創(chuàng)建實例的時候,就把name,score等屬性綁上去:
class Student(object):def __init__(self, name, score):self.name = nameself.score = score注意:特殊方法“__init__”前后分別有兩個下劃線!!!
注意到__init__方法的第一個參數(shù)永遠是self,表示創(chuàng)建的實例本身,因此,在__init__方法內(nèi)部,就可以把各種屬性綁定到self,因為self就指向創(chuàng)建的實例本身。
有了__init__方法,在創(chuàng)建實例的時候,就不能傳入空的參數(shù)了,必須傳入與__init__方法匹配的參數(shù),但self不需要傳,Python解釋器自己會把實例變量傳進去:
class Student(object):def __init__(self, name, score):self.name = nameself.score = score bart = Student('Blus li', 97) print(bart.name) print(bart.score)?
和普通的函數(shù)相比,在類中定義的函數(shù)只有一點不同,就是第一個參數(shù)永遠是實例變量self,并且,調(diào)用時,不用傳遞該參數(shù)。除此之外,類的方法和普通函數(shù)沒有什么區(qū)別,所以,你仍然可以用默認參數(shù)、可變參數(shù)、關(guān)鍵字參數(shù)和命名關(guān)鍵字參數(shù)。
封裝
面向?qū)ο缶幊痰囊粋€重要特點就是數(shù)據(jù)封裝。在上面的Student類中,每個實例就擁有各自的name和score這些數(shù)據(jù)。我們可以通過函數(shù)來訪問這些數(shù)據(jù),比如打印一個學(xué)生的成績:
class Student(object):def __init__(self, name, score):self.name = nameself.score = score bart = Student('Blus li', 97) def print_score(std):print('%s: %s' % (std.name, std.score)) print_score(bart)但是,既然Student實例本身就擁有這些數(shù)據(jù),要訪問這些數(shù)據(jù),就沒有必要從外面的函數(shù)去訪問,可以直接在Student類的內(nèi)部定義訪問數(shù)據(jù)的函數(shù),這樣,就把“數(shù)據(jù)”給封裝起來了。這些封裝數(shù)據(jù)的函數(shù)是和Student類本身是關(guān)聯(lián)起來的,我們稱之為類的方法:
class Student(object):def __init__(self, name, score):self.name = nameself.score = scoredef print_score(self):print('%s: %s' % (self.name, self.score))要定義一個方法,除了第一個參數(shù)是self外,其他和普通函數(shù)一樣。要調(diào)用一個方法,只需要在實例變量上直接調(diào)用,除了self不用傳遞,其他參數(shù)正常傳入:
class Student(object):def __init__(self, name, score):self.name = nameself.score = scoredef print_score(self):print('%s: %s' % (self.name, self.score))bart=Student("Blue Li",96); bart.print_score()和普通的函數(shù)相比,在類中定義的函數(shù)只有一點不同,就是第一個參數(shù)永遠是實例變量self,并且,調(diào)用時,不用傳遞該參數(shù)。除此之外,類的方法和普通函數(shù)沒有什么區(qū)別,所以,你仍然可以用默認參數(shù)、可變參數(shù)、關(guān)鍵字參數(shù)和命名關(guān)鍵字參數(shù)。
繼承和多態(tài)
在OOP程序設(shè)計中,當(dāng)我們定義一個class的時候,可以從某個現(xiàn)有的class繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。
比如,我們已經(jīng)編寫了一個名為Animal的class,有一個run()方法可以直接打印:
class Animal(object):def run(self):print('Animal is running...')當(dāng)我們需要編寫Dog和Cat類時,就可以直接從Animal類繼承:
class Dog(Animal):passclass Cat(Animal):pass對于Dog來說,Animal就是它的父類,對于Animal來說,Dog就是它的子類。Cat和Dog類似。
繼承有什么好處?最大的好處是子類獲得了父類的全部功能。由于Animial實現(xiàn)了run()方法,因此,Dog和Cat作為它的子類,什么事也沒干,就自動擁有了run()方法:
dog = Dog() dog.run()cat = Cat() cat.run()運行結(jié)果如下:
Animal is running... Animal is running...當(dāng)然,也可以對子類增加一些方法,比如Dog類:
class Dog(Animal):def run(self):print('Dog is running...')def eat(self):print('Eating meat...')繼承的第二個好處需要我們對代碼做一點改進。你看到了,無論是Dog還是Cat,它們run()的時候,顯示的都是Animal is running...,符合邏輯的做法是分別顯示Dog is running...和Cat is running...,因此,對Dog和Cat類改進如下:
class Dog(Animal):def run(self):print('Dog is running...')class Cat(Animal):def run(self):print('Cat is running...')再次運行,結(jié)果如下:
Dog is running... Cat is running...當(dāng)子類和父類都存在相同的run()方法時,我們說,子類的run()覆蓋了父類的run(),在代碼運行的時候,總是會調(diào)用子類的run()。這樣,我們就獲得了繼承的另一個好處:多態(tài)。
要理解什么是多態(tài),我們首先要對數(shù)據(jù)類型再作一點說明。當(dāng)我們定義一個class的時候,我們實際上就定義了一種數(shù)據(jù)類型。我們定義的數(shù)據(jù)類型和Python自帶的數(shù)據(jù)類型,比如str、list、dict沒什么兩樣:
a = list() # a是list類型 b = Animal() # b是Animal類型 c = Dog() # c是Dog類型判斷一個變量是否是某個類型可以用isinstance()判斷:
class Animal(object):def run(self):print('Animal is running...') class Dog(Animal):def run(self):print('Dog is running...')class Cat(Animal):def run(self):print('Cat is running...') a = list() # a是list類型 b = Animal() # b是Animal類型 c = Dog() # c是Dog類型 print(isinstance(a, list)) print(isinstance(b, Animal)) print(isinstance(c, Dog))看來a、b、c確實對應(yīng)著list、Animal、Dog這3種類型。
但是等等,試試:
class Animal(object):def run(self):print('Animal is running...') class Dog(Animal):def run(self):print('Dog is running...')class Cat(Animal):def run(self):print('Cat is running...') a = list() # a是list類型 b = Animal() # b是Animal類型 c = Dog() # c是Dog類型 print(isinstance(c, Animal))看來c不僅僅是Dog,c還是Animal!
不過仔細想想,這是有道理的,因為Dog是從Animal繼承下來的,當(dāng)我們創(chuàng)建了一個Dog的實例c時,我們認為c的數(shù)據(jù)類型是Dog沒錯,但c同時也是Animal也沒錯,Dog本來就是Animal的一種!
所以,在繼承關(guān)系中,如果一個實例的數(shù)據(jù)類型是某個子類,那它的數(shù)據(jù)類型也可以被看做是父類。但是,反過來就不行:
class Animal(object):def run(self):print('Animal is running...') class Dog(Animal):def run(self):print('Dog is running...')class Cat(Animal):def run(self):print('Cat is running...') b = Animal() print(isinstance(b, Dog))Dog可以看成Animal,但Animal不可以看成Dog。
要理解多態(tài)的好處,我們還需要再編寫一個函數(shù),這個函數(shù)接受一個Animal類型的變量:
def run_twice(animal):animal.run()animal.run()當(dāng)我們傳入Animal的實例時,run_twice()就打印出:
class Animal(object):def run(self):print('Animal is running...') class Dog(Animal):def run(self):print('Dog is running...')class Cat(Animal):def run(self):print('Cat is running...') def run_twice(animal):animal.run()animal.run() run_twice(Animal())當(dāng)我們傳入Dog的實例時,run_twice()就打印出:
class Animal(object):def run(self):print('Animal is running...') class Dog(Animal):def run(self):print('Dog is running...')class Cat(Animal):def run(self):print('Cat is running...') def run_twice(animal):animal.run()animal.run() run_twice(Dog())?看上去沒啥意思,但是仔細想想,現(xiàn)在,如果我們再定義一個Tortoise類型,也從Animal派生:
class Animal(object):def run(self):print('Animal is running...') class Dog(Animal):def run(self):print('Dog is running...')class Cat(Animal):def run(self):print('Cat is running...') class Tortoise(Animal):def run(self):print('Tortoise is running slowly...') def run_twice(animal):animal.run()animal.run() run_twice(Tortoise())你會發(fā)現(xiàn),新增一個Animal的子類,不必對run_twice()做任何修改,實際上,任何依賴Animal作為參數(shù)的函數(shù)或者方法都可以不加修改地正常運行,原因就在于多態(tài)。
多態(tài)的好處就是,當(dāng)我們需要傳入Dog、Cat、Tortoise……時,我們只需要接收Animal類型就可以了,因為Dog、Cat、Tortoise……都是Animal類型,然后,按照Animal類型進行操作即可。由于Animal類型有run()方法,因此,傳入的任意類型,只要是Animal類或者子類,就會自動調(diào)用實際類型的run()方法,這就是多態(tài)的意思:
對于一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調(diào)用run()方法,而具體調(diào)用的run()方法是作用在Animal、Dog、Cat還是Tortoise對象上,由運行時該對象的確切類型決定,這就是多態(tài)真正的威力:調(diào)用方只管調(diào)用,不管細節(jié),而當(dāng)我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調(diào)用的。這就是著名的“開閉”原則:
對擴展開放:允許新增Animal子類;
對修改封閉:不需要修改依賴Animal類型的run_twice()等函數(shù)。
繼承還可以一級一級地繼承下來,就好比從爺爺?shù)桨职帧⒃俚絻鹤舆@樣的關(guān)系。而任何類,最終都可以追溯到根類object,這些繼承關(guān)系看上去就像一顆倒著的樹。比如如下的繼承樹:
┌───────────────┐│ object │└───────────────┘│┌────────────┴────────────┐│ │▼ ▼┌─────────────┐ ┌─────────────┐│ Animal │ │ Plant │└─────────────┘ └─────────────┘│ │┌─────┴──────┐ ┌─────┴──────┐│ │ │ │▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Dog │ │ Cat │ │ Tree │ │ Flower │?
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Python基础——PyCharm版本——第七章、面向对象编程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python基础——PyCharm版本—
- 下一篇: Python基础——PyCharm版本—