python實用教程-第八章-類與對象課件_第1頁
python實用教程-第八章-類與對象課件_第2頁
python實用教程-第八章-類與對象課件_第3頁
python實用教程-第八章-類與對象課件_第4頁
python實用教程-第八章-類與對象課件_第5頁
已閱讀5頁,還剩87頁未讀 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第八章類與對象Python支持創(chuàng)建自己的對象,Python從設計之初就是一門面向對象語言,它提供了一些語言特性支持面向對象編程。創(chuàng)建對象是Python的核心概念,本章將介紹如何創(chuàng)建對象,以及多態(tài)、封裝、方法和繼承等概念。Python快樂學習班的同學結束函數(shù)樂高積木廳的創(chuàng)意學習后,導游帶領他們來到對象動物園。在對象動物園,將為同學們呈現(xiàn)各種動物對象,同學們將在這里了解各種動物所屬的類別,各種動物所擁有的技能,以及它們的技能繼承自哪里等知識點?,F(xiàn)在跟隨Python快樂學習班的同學一起進入對象動物園觀摩吧!第八章類與對象Python支持創(chuàng)建自己的對象,Pytho18.1理解面向對象8.1理解面向對象28.1.1面向對象編程Python是一門面向對象編程語言,對面向對象語言編碼的過程就叫做面向對象編程。面向對象編程——ObjectOrientedProgramming,簡稱OOP,是一種程序設計思想。OOP把對象作為程序的基本單元,一個對象包含了數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)。面向對象的程序設計把計算機程序視為一組對象的集合,每個對象都可以接收其他對象發(fā)過來的消息,并處理這些消息,計算機程序的執(zhí)行就是一系列消息在各個對象之間傳遞。在Python中,所有數(shù)據(jù)類型都被視為對象,也可以自定義對象。自定義的對象數(shù)據(jù)類型就是面向對象中的類(Class)的概念。8.1.1面向對象編程Python是一門面向對象編程語言38.1.2面向對象術語簡介類(Class):用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。類變量(或屬性):類變量在整個實例化的對象中是公用的。類變量定義在類中且在方法之外。類變量通常不作為實例變量使用。類變量也稱作屬性。數(shù)據(jù)成員:類變量或者實例變量用于處理類及其實例對象的相關的數(shù)據(jù)。方法重寫:如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱為方法的重寫。實例變量:定義在方法中的變量,只作用于當前實例的類。多態(tài)(Polymorphism):對不同類的對象使用同樣的操作。封裝(Encapsulation):對外部世界隱藏對象的工作細節(jié)。8.1.2面向對象術語簡介類(Class):用來描述具有4繼承(Inheritance):即一個派生類(derivedclass)繼承基類(baseclass)的字段和方法。繼承也允許把一個派生類的對象作為一個基類對象對待。以普通的類為基礎建立專門的類對象。實例化(Instance):創(chuàng)建一個類的實例,類的具體對象。方法:類中定義的函數(shù)。對象:通過類定義的數(shù)據(jù)結構實例。對象包括兩個數(shù)據(jù)成員(類變量和實例變量)和方法。Python中的類提供了面向對象編程的所有基本功能:類的繼承機制允許多個基類,派生類可以覆蓋基類中的任何方法,方法中可以調用基類中的同名方法。對象可以包含任意數(shù)量和類型的數(shù)據(jù)。繼承(Inheritance):即一個派生類(derived58.2類的定義與使用8.2類的定義與使用68.2.1類的定義類定義的語法格式如下:classClassName(object):<statement-1>.<statement-N>Python中定義類使用class關鍵字,class后面緊跟類名。示例如下(my_class.py):classMyClass(object):i=123deff(self):return'helloworld'由代碼可以看到,這里定義了一個名為MyClass的類。8.2.1類的定義類定義的語法格式如下:7在Python中,類名一般由以大寫字母開頭的單詞命名,并且若是由多個單詞組成的類名,那各個單詞的首字母都大寫。類名后面緊接著是(object),object表示該類是從哪個類繼承的。通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。類包含屬性(相當于函數(shù)中的語句)和方法(類中的方法大體可以理解成第7章所學的函數(shù))。在類中定義的方法的形式和函數(shù)差不多,但不稱為函數(shù),而稱為方法。因為方法需要靠類對象去調用,而函數(shù)不需要。在Python中,類名一般由以大寫字母開頭的單詞命名,并且若88.2.2類的使用classMyClass(object):i=123deff(self):return'helloworld'

use_class=MyClass()print('調用類的屬性:',use_class.i)print('調用類的方法:',use_class.f())類的使用比函數(shù)調用多了幾個操作,調用需要執(zhí)行如下操作:use_class=MyClass()這步叫做類的實例化,即創(chuàng)建一個類的實例,此處得到的use_class這個變量稱為類的具體對象。8.2.2類的使用classMyClass(objec9再看后面兩行的調用:print(f'調用類的屬性:{use_class.i}')print(f'調用類的方法:{use_class.f()}')這里第一行后面use_class.i部分的作用是調用類的屬性,也就是前面所說的類變量。第二行后面use_class.f()部分的作用是調用類的方法。在類中定義方法的要求:在類中定義方法時,第一個參數(shù)必須是

self。除第一個參數(shù)之外,類的方法和普通函數(shù)沒什么區(qū)別,如可以用默認參數(shù)、可變參數(shù)、關鍵字參數(shù)和命名關鍵字參數(shù)等。在類中調用方法的要求:要調用一個方法,在實例變量上直接調用即可。除了self不用傳遞,其他參數(shù)正常傳入。類對象支持兩種操作,屬性引用和實例化。實例化方式上面已經(jīng)介紹過,屬性引用的標準語法格式如下:語法中,obj代表類對象,name代表屬性。再看后面兩行的調用:108.3深

類本節(jié)將深入介紹類的相關內容,如類的構造方法和訪問權限。8.3深入類本節(jié)將深入介紹類的相關內容,如類的構118.3.1類的構造方法對前面的示例做一些改動,改動后代碼如下(my_calss_search.py):classMyClass(object):i=123def__init__(self,name):=name

deff(self):return'hello,'+

use_class=MyClass('xiaomeng')print('調用類的屬性:',use_class.i)print('調用類的方法:',use_class.f())8.3.1類的構造方法對前面的示例做一些改動,改動后代碼12實例化MyClass這個類時,調用了__init__()這個方法。在Python中,__init__()方法是一個特殊的方法,當對象實例化時會被調用,__init__()的意思是初始化,是initialization的簡寫。這個方法的書寫方式是:先兩個下劃線,后面接著是init,再接著兩個下劃線。這個方法也叫構造方法。在定義類時,若不顯式定義一個__init__()方法,則程序默認調用一個無參的__init__()方法。在Python中,定義類時,若沒有定義構造方法(__init__()方法),在類的實例化時,系統(tǒng)會調用默認的構造方法。另外,__init__()方法可以有參數(shù),參數(shù)通過__init__()傳遞到類的實例化操作上。一個類中可用定義多個構造方法,但類實例化時只實例化其中位于最后的一個構造方法,也即后面的構造方法會覆蓋前面的構造方法,并且實例化時需要根據(jù)最后一個構造方法的形式進行實例化。建議一個類中只定義一個構造函數(shù)。實例化MyClass這個類時,調用了__init__()這個138.3.2類的訪問權限在類內部,可以有屬性和方法,而外部代碼可以通過直接調用實例變量的方法來操作數(shù)據(jù),這樣,就隱藏了內部的復雜邏輯。要讓內部屬性不被外部訪問,可以在屬性的名稱前加上兩個下劃線__,在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問。在Python中,可以給類增加get_attrs這樣的方法來獲取類中的私有變量。在Python中,可以再給類增加set_attrs這樣的方法來修改類中的私有變量。在Python中,通過定義私有變量和定義對應的set方法,可以幫助我們做參數(shù)檢查,避免傳入無效的參數(shù)。類還有私有方法。類的私有方法也是以兩個下劃線開頭,聲明該方法為私有方法,且不能在類的外使用。8.3.2類的訪問權限在類內部,可以有屬性和方法,而外部148.4繼

承繼承的語法格式如下:classDerivedClassName(BaseClassName):<statement-1>..<statement-N>面向對象的編程帶來的主要好處之一是代碼的重用,實現(xiàn)這種重用的方法之一是通過繼承機制。繼承完全可以理解成類之間的類型和子類型關系。在面向對象程序設計中,當我們定義一個class的時候,可以從某個現(xiàn)有的class繼承,定義的新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Baseclass、Superclass)。8.4繼承繼承的語法格式如下:15繼承語法class子類名(基類名):基類名寫在括號里,基本類是在類定義的時候,在元組之中指明的。在python中繼承中的一些特點:(1)在繼承中,基類的構造方法(__init__()方法)不會被自動調用,它需要在其子類的構造方法中專門調用。(2)在調用基類的方法時,需要加上基類的類名前綴,且需要帶上self參數(shù)變量。區(qū)別于在類中調用普通函數(shù)時并不需要帶上self參數(shù)。(3)Python總是首先查找對應類型的方法,如果不能在子類中找到對應的方法,它才到基類中逐個查找。繼承有什么好處?最大的好處是子類獲得了父類的全部非私有的功能。繼承語法class子類名(基類名):基類名寫在括號里,基本類16classAnimal(object):defrun(self):print('Animalisrunning...')定義類:classDog(Animal):pass

classCat(Animal):passdog=Dog()dog.run()

cat=Cat()cat.run()classAnimal(object):178.5多重繼承Python還支持多重繼承。多重繼承的語法格式如下:classDerivedClassName(Base1,Base2,Base3):<statement-1>

.<statement-N>可以看到,多重繼承就是在定義類時,在類名后面的圓括號中添加多個基類(父類或超類),各個基類之間使用逗號分隔。需要注意圓括號中父類的順序,若父類中有相同的方法名,在子類使用時未指定,Python會從左到右進行搜索。即若某個方法在子類中沒有定義,則子類從左到右查找各個父類中是否包含這個方法。通過類的多重繼承,一個子類就可以繼承多個父類,并從多個父類中取得所有非私有方法。8.5多重繼承Python還支持多重繼承。多重繼承的語法188.6多

態(tài)使用繼承,可以重復使用代碼。但對于繼承中的示例,無論是Dog類還是Cat類,調用父類的run()方法時顯示的都是Animalisrunning...。對Dog類和Cat類做如下改進(完整代碼見animal_3.py):classDog(Animal):defrun(self):print('Dogisrunning...')

classCat(Animal):defrun(self):print('Catisrunning...')8.6多態(tài)使用繼承,可以重復使用代碼。但對于繼承19執(zhí)行如下語句:dog=Dog()print('實例化Dog類')dog.run()

cat=Cat()print('實例化Cat類')cat.run()執(zhí)行結果如下:實例化Dog類Dogisrunning...實例化Cat類Catisrunning...執(zhí)行如下語句:20由執(zhí)行結果看到,分別得到了Dog和Cat各自的running結果。當子類和父類都存在相同的run()方法時,子類的run()方法會覆蓋了父類的run()方法,在代碼運行的時候,總是會調用子類的run()方法。我們稱這個為:多態(tài)。多態(tài)這個術語來自希臘語,意思是有多種形式。多態(tài)意味著就算不知道變量所引用的對象類型是什么,還是能對對象進行操作,而多態(tài)也會根據(jù)對象(或類)的不同而表現(xiàn)出不同的行為。例如我們上面定義的Animal類,在類中定義了run方法,Dog和Cat類分別繼承Animal類,并且分別定義了自己的run方法,最后Dog和Cat調用run方法時,調用的是自己的定義的run方法。多態(tài)的好處就是,當我們需要傳入Dog、Cat等對象時,我們只需要接收Animal類型就可以了,因為Dog、Cat等都是Animal類型,然后,按照Animal類型進行操作即可。由執(zhí)行結果看到,分別得到了Dog和Cat各自的running21多態(tài)的意思是:對于一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調用run()方法,而具體調用的run()方法是作用在Animal、Dog、Cat對象上,由運行時該對象的確切類型決定。多態(tài)真正的威力在于:調用方只管調用,不管細節(jié),而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:對擴展開放:允許新增Animal子類。對修改封閉:不需要修改依賴Animal類型的函數(shù)。很多函數(shù)和運算符都是多態(tài)的——你寫的絕大多數(shù)程序可能也是,即便你并非有意這樣。多態(tài)的意思是:對于一個變量,我們只需要知道它是Animal類228.7封

裝封裝是對全局作用域中其他區(qū)域隱藏多余信息的原則。聽起來有些像多態(tài)——使用對象而不用知道其內部細節(jié),兩者概念類似,因為它們都是抽象的原則——它們都會幫忙處理程序組件而不用過多關系細節(jié),就像函數(shù)一樣。但是封裝并不等同于多態(tài)。多態(tài)可以讓用戶對于不知道什么類(或對象類型)的對象進行方法調用,而封裝是可以不用關心對象是如何構建的而直接進行使用。前面幾節(jié)的示例基本都有用到封裝的思想,如在前面定義的Student類中,每個實例就擁有各自的name和score這些數(shù)據(jù)。我們可以通過函數(shù)來訪問這些數(shù)據(jù),比如打印學生的成績,我們如下定義(student.py):8.7封裝封裝是對全局作用域中其他區(qū)域隱藏多余信23classStudent(object):def__init__(self,name,score):=nameself.score=score

std=Student('xiaozhi',90)definfo(std):print(f'學生:{};分數(shù):{std.score}')info(std)既然Student實例本身就擁有這些數(shù)據(jù),要訪問這些數(shù)據(jù),就沒有必要從外面的函數(shù)去訪問,可以直接在Student類的內部定義訪問數(shù)據(jù)的函數(shù),這樣,就把“數(shù)據(jù)”給封裝起來了。classStudent(object):24classStudent0(object):def__init__(self,name,score):=nameself.score=score

definfo(self):print(f'學生:{};分數(shù):{self.score}')要定義一個方法,除了第一個參數(shù)是self外,其他和普通函數(shù)一樣。要調用一個方法,只需要在實例變量上直接調用,除了self不用傳遞,其他參數(shù)正常傳入,執(zhí)行如下語句:stu=Student0('xiaomeng',95)封裝的另一個好處是可以給Student類增加新的方法。classStudent0(object):258.8獲取對象信息當我們調用一個方法時,可能需要傳遞一個參數(shù),這個參數(shù)類型我們是知道的,但是對于接收參數(shù)的方法,就不一定知道參數(shù)是什么類型了。那我們該怎么來得知參數(shù)的類型呢?Python為我們提供了如下幾種獲取對象類型的方法,具體如下:1.使用type()我們前面已經(jīng)學習過type函數(shù)的使用,基本類型都可以用type()判斷。如:>>>type(123)<class'int'>>>>type('abc')<class'str'>>>>type(None)<class'NoneType'>8.8獲取對象信息當我們調用一個方法時,可能需要傳遞一個262.使用isinstance()要明確class的繼承關系,使用type()很不方便,通過判斷class的數(shù)據(jù)類型,來確定class的繼承關系,這樣要方便的多,這個時候可以使用isinstance()函數(shù)。比如對于繼承關系是如下的形式:object->Animal->Dog即Animal繼承object,Dog繼承Animal,使用isinstance()就可以告訴我們,一個對象是否是某種類型。例如創(chuàng)建如下2種類型的對象:>>>animal=Animal()>>>dog=Dog()對上面2種類型對象,使用isinstance判斷如下:>>>isinstance(dog,Dog)True2.使用isinstance()27根據(jù)打印結果看到,dog是Dog類型,這個是沒有任何疑問的,因為dog變量指向的就是Dog對象。接下來再判斷Animal的類型,使用isinstance判斷如下:>>>isinstance(dog,Animal)True根據(jù)打印結果看到,dog也是Animal類型。由此我們得知:dog雖然自身是Dog類型,但由于Dog是從Animal繼承下來的,所以,dog也還是Animal類型。換句話說,isinstance()判斷的是一個對象是否是該類型本身,或者是否是該類型繼承類的類型。因此,我們可以確信,dog還是object類型:>>>isinstance(dog,object)True同時也確信,實際類型是Dog類型的dog,同時也是Animal類型:>>>isinstance(dog,Dog)andisinstance(dog,Animal)True根據(jù)打印結果看到,dog是Dog類型,這個是沒有任何疑問的,283.使用dir()如果要獲得一個對象的所有屬性和方法,可以使用dir()函數(shù),它返回一個字符串的list如要獲得一個str對象的所有屬性和方法,使用方式如下:>>>dir('abc')3.使用dir()298.9類的專有方法Python類還可以定義專用方法,專用方法是在特殊情況下或當使用特別語法時由Python替你調用的,而不是像普通的方法那樣在代碼中直接調用。本節(jié)我們講述幾個Python比較常用的專有方法??吹筋愃芲_init__這種形如__xxx__的變量或者函數(shù)名就要注意,這些在Python中是有特殊用途的。__init__我們已經(jīng)知道怎么用了,Python的class中有許多這種有特殊用途的函數(shù),可以幫助我們定制類。下面來介紹這種特殊類型的函數(shù)定制類的方法。8.9類的專有方法Python類還可以定義專用方法,專用301.__str__()方法classStudent(object):def__init__(self,name):=name

def__str__(self):return'學生名稱:%s'%

print(Student('xiaozhi'))輸出結果為:<__main__.Studentobjectat0x0000000000D64198>1.__str__()方法31結果輸出的是一堆字符串,基本沒有人看得懂,這樣打印出來的結果沒有可用性。怎樣才能輸出讀者能看懂,并且是可用的結果?要解決這個問題,需要定義__str__()方法,通過__str__()方法返回一個易懂的字符串就可以了。重新定義上面的示例(student_2.py):classStudent(object):def__init__(self,name):=name

def__str__(self):returnf'學生名稱:{}'

print(Student('xiaozhi'))程序輸出結果為:學生名稱:xiaozhi結果輸出的是一堆字符串,基本沒有人看得懂,這樣打印出來的結果32如果在交互模式下輸入:>>>s=Student('xiaozhi')>>>s<__main__.Studentobjectat0x00000000030EC550>由輸出結果可以看到,將實例化的類對象賦給一個變量,輸出變量的實例還是和之前一樣,是一串基本看不懂的字符。直接顯示變量調用的不是__str__()方法,而是__repr__()方法,兩者的區(qū)別在于__str__()方法返回用戶看到的字符串,而__repr__()方法返回程序開發(fā)者看到的字符串。也就是說,__repr__()方法是為調試服務的。所以這個問題的解決辦法是再定義一個__repr__()方法。如果在交互模式下輸入:33通常,__str__()方法和__repr__()方法是一樣的,所以有一個巧妙的寫法(student_3.py):classStudent(object):def__init__(self,name):=name

def__str__(self):returnf'學生名稱:{}'__repr__=__str__在交互模式下執(zhí)行:>>>s=Student('xiaozhi')>>>s學生名稱:xiaozhi通常,__str__()方法和__repr__()方法是一樣342.__iter__()方法如果一個類想被用于for...in循環(huán),類似list或tuple那樣,就必須實現(xiàn)一個__iter__()方法,該方法返回一個迭代對象,Python的for循環(huán)會不斷調用該迭代對象的__next__()方法拿(“拿”是否可以更改為“獲得”)到循環(huán)的下一個值,直到遇到StopIteration錯誤時退出循環(huán)。classFib(object):def__init__(self):self.a,self.b=0,1#初始化兩個計數(shù)器a,bdef__iter__(self):returnself#實例本身就是迭代對象,故返回自己def__next__(self):self.a,self.b=self.b,self.a+self.b#計算下一個值ifself.a>100000:#退出循環(huán)的條件raiseStopIteration();returnself.a#返回下一個值2.__iter__()方法353.__getitem__()方法Fib實例雖然能作用于for循環(huán),看起來和list有點像,但是,把它當成list來使用還是不行,要表現(xiàn)得像list那樣按照下標取出元素,需要實現(xiàn)__getitem__()方法,如下實現(xiàn):classFib(object):def__getitem__(self,n):a,b=1,1forxinrange(n):a,b=b,a+breturna3.__getitem__()方法364.__getattr__()方法前面示例講述過,正常情況下,當調用類的方法或屬性時,如果不存在,就會報錯。要避免這個錯誤,除了可以加上一個score屬性外,Python還有另一個機制,那就是寫一個__getattr__()方法,動態(tài)返回一個屬性。如下:classStudent(object):

def__init__(self):='xiaozhi'

def__getattr__(self,attr):ifattr=='score':return954.__getattr__()方法37當調用不存在的屬性(如score)時,Python解釋器會調用__getattr__(self,'score')嘗試獲得屬性,這樣就有機會返回score的值。在交互模式下輸入:>>>stu=Student()>>>xiaozhi>>>stu.score95由輸出結果可以看到,通過調用__getattr__()方法,可以正確輸出不存在的屬性的值。注意,程序只有在沒有找到屬性的情況下才調用__getattr__(),若已有屬性(如name),則不會在__getattr__()方法中查找。此外,所有__getattr__()方法的調用都有返回值,都會返回None(如stu.abc),定義的__getattr__()默認返回None。當調用不存在的屬性(如score)時,Python解釋器會調385.__call__()方法一個對象實例可以有自己的屬性和方法,當調用實例的方法時,采用的方式是使用instance.method()來調用。任何類,只需要定義一個__call__()方法,就可以直接對實例進行調用。如下示例:classStudent(object):def__init__(self,name):=name

def__call__(self):print(f'名稱:{}')__call__()還可以定義參數(shù)。5.__call__()方法39在交互模式下輸入:>>>stu=Student('xiaomeng')>>>stu()名稱:xiaomeng由輸出結果可以看到,通過定義__call__()方法,可以直接對實例進行調用并得到結果。__call__()方法還可以定義參數(shù)。對實例進行直接調用就像對一個函數(shù)進行調用一樣,完全可以把對象看成函數(shù),把函數(shù)看成對象,因為這兩者本來就沒有根本區(qū)別。如果把對象看成函數(shù),函數(shù)本身就可以在運行期間動態(tài)創(chuàng)建出來,因為類的實例都是運行期間創(chuàng)建出來的。這樣一來,就模糊了對象和函數(shù)的界限。怎么判斷一個變量是對象還是函數(shù)呢?很多時候,判斷一個對象是否能被調用,可以使用callable()函數(shù)。在交互模式下輸入:40比如max()函數(shù)和上面定義的帶有__call__()方法的Student類實例,示例如下:>>>callable(Student('xiaozhi'))True>>>callable(max)True>>>callable([1,2,3])False>>>callable(None)False>>>callable('a')False由輸出結果可以看到,通過callable()函數(shù)可以判斷一個對象是否為“可調用”對象。比如max()函數(shù)和上面定義的帶有__call__()方法的418.10活學活用——出行建議假如你今天想外出一趟,但不清楚今天的天氣是否適宜出行?,F(xiàn)在需要設計一個幫你提供建議的程序,程序要求輸入出行的時間,然后根據(jù)出行時間,結合能見度、溫度和當前空氣濕度給出出行建議,以及比較適合使用的交通工具,需要考慮需求變更的可能。需求分析:使用本章所學的封裝、繼承、多態(tài),比較容易實現(xiàn)。在父類中封裝查看能見度、查看溫度和查看濕度的方法,子類繼承父類。若有需要,子類可以覆蓋父類的方法,做自己的實現(xiàn)。子類也可以自定義方法。定義天氣查找類,類中定義3個方法,一個方法根據(jù)傳入的input_daytime值返回對應的能見度;一個方法根據(jù)傳入的input_daytime值返回對應的溫度;最后一個方法根據(jù)傳入的input_daytime值返回對應的濕度。具體代碼請參考書本。8.10活學活用——出行建議假如你今天想外出一趟,但不清428.11技巧點撥在init()方法中初始化對象的全部屬性是一個好習慣,可以幫助用戶更好地管理類中的屬性和對屬性值的更改。在程序運行的任何時刻為對象添加屬性都是合法的,不過應當避免讓對象擁有相同的類型卻有不同的屬性組。繼承會給調試帶來新挑戰(zhàn),因為當你調用對象的方法時,可能無法知道調用的是哪一個方法。一旦無法確認程序的運行流程,最簡單的解決辦法是在適當位置添加一個輸出語句,如在相關方法的開頭或方法調用開始處等。8.11技巧點撥在init()方法中初始化對象的全部屬性438.12問題探討(1)有辦法從外部訪問以雙下畫線開頭的實例變量嗎?(2)方法與函數(shù)有什么區(qū)別?(3)使用類的好處?8.12問題探討(1)有辦法從外部訪問以雙下畫線開頭的實448.13章節(jié)回顧(1)回顧什么是類,類如何使用。(2)回顧構造方法的定義,使用構造方法的好處。(3)回顧類的訪問權限有哪些,這些訪問權限都怎么使用。(4)回顧繼承、多重繼承的定義,它們都是怎樣實現(xiàn)的。(5)回顧多態(tài)的定義與實現(xiàn)。(6)回顧封裝的定義與實現(xiàn)。(7)回顧類的專有方法,各自如何使用。8.13章節(jié)回顧(1)回顧什么是類,類如何使用。458.14實戰(zhàn)演練8.14實戰(zhàn)演練46第八章類與對象Python支持創(chuàng)建自己的對象,Python從設計之初就是一門面向對象語言,它提供了一些語言特性支持面向對象編程。創(chuàng)建對象是Python的核心概念,本章將介紹如何創(chuàng)建對象,以及多態(tài)、封裝、方法和繼承等概念。Python快樂學習班的同學結束函數(shù)樂高積木廳的創(chuàng)意學習后,導游帶領他們來到對象動物園。在對象動物園,將為同學們呈現(xiàn)各種動物對象,同學們將在這里了解各種動物所屬的類別,各種動物所擁有的技能,以及它們的技能繼承自哪里等知識點?,F(xiàn)在跟隨Python快樂學習班的同學一起進入對象動物園觀摩吧!第八章類與對象Python支持創(chuàng)建自己的對象,Pytho478.1理解面向對象8.1理解面向對象488.1.1面向對象編程Python是一門面向對象編程語言,對面向對象語言編碼的過程就叫做面向對象編程。面向對象編程——ObjectOrientedProgramming,簡稱OOP,是一種程序設計思想。OOP把對象作為程序的基本單元,一個對象包含了數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)。面向對象的程序設計把計算機程序視為一組對象的集合,每個對象都可以接收其他對象發(fā)過來的消息,并處理這些消息,計算機程序的執(zhí)行就是一系列消息在各個對象之間傳遞。在Python中,所有數(shù)據(jù)類型都被視為對象,也可以自定義對象。自定義的對象數(shù)據(jù)類型就是面向對象中的類(Class)的概念。8.1.1面向對象編程Python是一門面向對象編程語言498.1.2面向對象術語簡介類(Class):用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的實例。類變量(或屬性):類變量在整個實例化的對象中是公用的。類變量定義在類中且在方法之外。類變量通常不作為實例變量使用。類變量也稱作屬性。數(shù)據(jù)成員:類變量或者實例變量用于處理類及其實例對象的相關的數(shù)據(jù)。方法重寫:如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆蓋(override),也稱為方法的重寫。實例變量:定義在方法中的變量,只作用于當前實例的類。多態(tài)(Polymorphism):對不同類的對象使用同樣的操作。封裝(Encapsulation):對外部世界隱藏對象的工作細節(jié)。8.1.2面向對象術語簡介類(Class):用來描述具有50繼承(Inheritance):即一個派生類(derivedclass)繼承基類(baseclass)的字段和方法。繼承也允許把一個派生類的對象作為一個基類對象對待。以普通的類為基礎建立專門的類對象。實例化(Instance):創(chuàng)建一個類的實例,類的具體對象。方法:類中定義的函數(shù)。對象:通過類定義的數(shù)據(jù)結構實例。對象包括兩個數(shù)據(jù)成員(類變量和實例變量)和方法。Python中的類提供了面向對象編程的所有基本功能:類的繼承機制允許多個基類,派生類可以覆蓋基類中的任何方法,方法中可以調用基類中的同名方法。對象可以包含任意數(shù)量和類型的數(shù)據(jù)。繼承(Inheritance):即一個派生類(derived518.2類的定義與使用8.2類的定義與使用528.2.1類的定義類定義的語法格式如下:classClassName(object):<statement-1>.<statement-N>Python中定義類使用class關鍵字,class后面緊跟類名。示例如下(my_class.py):classMyClass(object):i=123deff(self):return'helloworld'由代碼可以看到,這里定義了一個名為MyClass的類。8.2.1類的定義類定義的語法格式如下:53在Python中,類名一般由以大寫字母開頭的單詞命名,并且若是由多個單詞組成的類名,那各個單詞的首字母都大寫。類名后面緊接著是(object),object表示該類是從哪個類繼承的。通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。類包含屬性(相當于函數(shù)中的語句)和方法(類中的方法大體可以理解成第7章所學的函數(shù))。在類中定義的方法的形式和函數(shù)差不多,但不稱為函數(shù),而稱為方法。因為方法需要靠類對象去調用,而函數(shù)不需要。在Python中,類名一般由以大寫字母開頭的單詞命名,并且若548.2.2類的使用classMyClass(object):i=123deff(self):return'helloworld'

use_class=MyClass()print('調用類的屬性:',use_class.i)print('調用類的方法:',use_class.f())類的使用比函數(shù)調用多了幾個操作,調用需要執(zhí)行如下操作:use_class=MyClass()這步叫做類的實例化,即創(chuàng)建一個類的實例,此處得到的use_class這個變量稱為類的具體對象。8.2.2類的使用classMyClass(objec55再看后面兩行的調用:print(f'調用類的屬性:{use_class.i}')print(f'調用類的方法:{use_class.f()}')這里第一行后面use_class.i部分的作用是調用類的屬性,也就是前面所說的類變量。第二行后面use_class.f()部分的作用是調用類的方法。在類中定義方法的要求:在類中定義方法時,第一個參數(shù)必須是

self。除第一個參數(shù)之外,類的方法和普通函數(shù)沒什么區(qū)別,如可以用默認參數(shù)、可變參數(shù)、關鍵字參數(shù)和命名關鍵字參數(shù)等。在類中調用方法的要求:要調用一個方法,在實例變量上直接調用即可。除了self不用傳遞,其他參數(shù)正常傳入。類對象支持兩種操作,屬性引用和實例化。實例化方式上面已經(jīng)介紹過,屬性引用的標準語法格式如下:語法中,obj代表類對象,name代表屬性。再看后面兩行的調用:568.3深

類本節(jié)將深入介紹類的相關內容,如類的構造方法和訪問權限。8.3深入類本節(jié)將深入介紹類的相關內容,如類的構578.3.1類的構造方法對前面的示例做一些改動,改動后代碼如下(my_calss_search.py):classMyClass(object):i=123def__init__(self,name):=name

deff(self):return'hello,'+

use_class=MyClass('xiaomeng')print('調用類的屬性:',use_class.i)print('調用類的方法:',use_class.f())8.3.1類的構造方法對前面的示例做一些改動,改動后代碼58實例化MyClass這個類時,調用了__init__()這個方法。在Python中,__init__()方法是一個特殊的方法,當對象實例化時會被調用,__init__()的意思是初始化,是initialization的簡寫。這個方法的書寫方式是:先兩個下劃線,后面接著是init,再接著兩個下劃線。這個方法也叫構造方法。在定義類時,若不顯式定義一個__init__()方法,則程序默認調用一個無參的__init__()方法。在Python中,定義類時,若沒有定義構造方法(__init__()方法),在類的實例化時,系統(tǒng)會調用默認的構造方法。另外,__init__()方法可以有參數(shù),參數(shù)通過__init__()傳遞到類的實例化操作上。一個類中可用定義多個構造方法,但類實例化時只實例化其中位于最后的一個構造方法,也即后面的構造方法會覆蓋前面的構造方法,并且實例化時需要根據(jù)最后一個構造方法的形式進行實例化。建議一個類中只定義一個構造函數(shù)。實例化MyClass這個類時,調用了__init__()這個598.3.2類的訪問權限在類內部,可以有屬性和方法,而外部代碼可以通過直接調用實例變量的方法來操作數(shù)據(jù),這樣,就隱藏了內部的復雜邏輯。要讓內部屬性不被外部訪問,可以在屬性的名稱前加上兩個下劃線__,在Python中,實例的變量名如果以__開頭,就變成了一個私有變量(private),只有內部可以訪問,外部不能訪問。在Python中,可以給類增加get_attrs這樣的方法來獲取類中的私有變量。在Python中,可以再給類增加set_attrs這樣的方法來修改類中的私有變量。在Python中,通過定義私有變量和定義對應的set方法,可以幫助我們做參數(shù)檢查,避免傳入無效的參數(shù)。類還有私有方法。類的私有方法也是以兩個下劃線開頭,聲明該方法為私有方法,且不能在類的外使用。8.3.2類的訪問權限在類內部,可以有屬性和方法,而外部608.4繼

承繼承的語法格式如下:classDerivedClassName(BaseClassName):<statement-1>..<statement-N>面向對象的編程帶來的主要好處之一是代碼的重用,實現(xiàn)這種重用的方法之一是通過繼承機制。繼承完全可以理解成類之間的類型和子類型關系。在面向對象程序設計中,當我們定義一個class的時候,可以從某個現(xiàn)有的class繼承,定義的新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Baseclass、Superclass)。8.4繼承繼承的語法格式如下:61繼承語法class子類名(基類名):基類名寫在括號里,基本類是在類定義的時候,在元組之中指明的。在python中繼承中的一些特點:(1)在繼承中,基類的構造方法(__init__()方法)不會被自動調用,它需要在其子類的構造方法中專門調用。(2)在調用基類的方法時,需要加上基類的類名前綴,且需要帶上self參數(shù)變量。區(qū)別于在類中調用普通函數(shù)時并不需要帶上self參數(shù)。(3)Python總是首先查找對應類型的方法,如果不能在子類中找到對應的方法,它才到基類中逐個查找。繼承有什么好處?最大的好處是子類獲得了父類的全部非私有的功能。繼承語法class子類名(基類名):基類名寫在括號里,基本類62classAnimal(object):defrun(self):print('Animalisrunning...')定義類:classDog(Animal):pass

classCat(Animal):passdog=Dog()dog.run()

cat=Cat()cat.run()classAnimal(object):638.5多重繼承Python還支持多重繼承。多重繼承的語法格式如下:classDerivedClassName(Base1,Base2,Base3):<statement-1>

.<statement-N>可以看到,多重繼承就是在定義類時,在類名后面的圓括號中添加多個基類(父類或超類),各個基類之間使用逗號分隔。需要注意圓括號中父類的順序,若父類中有相同的方法名,在子類使用時未指定,Python會從左到右進行搜索。即若某個方法在子類中沒有定義,則子類從左到右查找各個父類中是否包含這個方法。通過類的多重繼承,一個子類就可以繼承多個父類,并從多個父類中取得所有非私有方法。8.5多重繼承Python還支持多重繼承。多重繼承的語法648.6多

態(tài)使用繼承,可以重復使用代碼。但對于繼承中的示例,無論是Dog類還是Cat類,調用父類的run()方法時顯示的都是Animalisrunning...。對Dog類和Cat類做如下改進(完整代碼見animal_3.py):classDog(Animal):defrun(self):print('Dogisrunning...')

classCat(Animal):defrun(self):print('Catisrunning...')8.6多態(tài)使用繼承,可以重復使用代碼。但對于繼承65執(zhí)行如下語句:dog=Dog()print('實例化Dog類')dog.run()

cat=Cat()print('實例化Cat類')cat.run()執(zhí)行結果如下:實例化Dog類Dogisrunning...實例化Cat類Catisrunning...執(zhí)行如下語句:66由執(zhí)行結果看到,分別得到了Dog和Cat各自的running結果。當子類和父類都存在相同的run()方法時,子類的run()方法會覆蓋了父類的run()方法,在代碼運行的時候,總是會調用子類的run()方法。我們稱這個為:多態(tài)。多態(tài)這個術語來自希臘語,意思是有多種形式。多態(tài)意味著就算不知道變量所引用的對象類型是什么,還是能對對象進行操作,而多態(tài)也會根據(jù)對象(或類)的不同而表現(xiàn)出不同的行為。例如我們上面定義的Animal類,在類中定義了run方法,Dog和Cat類分別繼承Animal類,并且分別定義了自己的run方法,最后Dog和Cat調用run方法時,調用的是自己的定義的run方法。多態(tài)的好處就是,當我們需要傳入Dog、Cat等對象時,我們只需要接收Animal類型就可以了,因為Dog、Cat等都是Animal類型,然后,按照Animal類型進行操作即可。由執(zhí)行結果看到,分別得到了Dog和Cat各自的running67多態(tài)的意思是:對于一個變量,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調用run()方法,而具體調用的run()方法是作用在Animal、Dog、Cat對象上,由運行時該對象的確切類型決定。多態(tài)真正的威力在于:調用方只管調用,不管細節(jié),而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:對擴展開放:允許新增Animal子類。對修改封閉:不需要修改依賴Animal類型的函數(shù)。很多函數(shù)和運算符都是多態(tài)的——你寫的絕大多數(shù)程序可能也是,即便你并非有意這樣。多態(tài)的意思是:對于一個變量,我們只需要知道它是Animal類688.7封

裝封裝是對全局作用域中其他區(qū)域隱藏多余信息的原則。聽起來有些像多態(tài)——使用對象而不用知道其內部細節(jié),兩者概念類似,因為它們都是抽象的原則——它們都會幫忙處理程序組件而不用過多關系細節(jié),就像函數(shù)一樣。但是封裝并不等同于多態(tài)。多態(tài)可以讓用戶對于不知道什么類(或對象類型)的對象進行方法調用,而封裝是可以不用關心對象是如何構建的而直接進行使用。前面幾節(jié)的示例基本都有用到封裝的思想,如在前面定義的Student類中,每個實例就擁有各自的name和score這些數(shù)據(jù)。我們可以通過函數(shù)來訪問這些數(shù)據(jù),比如打印學生的成績,我們如下定義(student.py):8.7封裝封裝是對全局作用域中其他區(qū)域隱藏多余信69classStudent(object):def__init__(self,name,score):=nameself.score=score

std=Student('xiaozhi',90)definfo(std):print(f'學生:{};分數(shù):{std.score}')info(std)既然Student實例本身就擁有這些數(shù)據(jù),要訪問這些數(shù)據(jù),就沒有必要從外面的函數(shù)去訪問,可以直接在Student類的內部定義訪問數(shù)據(jù)的函數(shù),這樣,就把“數(shù)據(jù)”給封裝起來了。classStudent(object):70classStudent0(object):def__init__(self,name,score):=nameself.score=score

definfo(self):print(f'學生:{};分數(shù):{self.score}')要定義一個方法,除了第一個參數(shù)是self外,其他和普通函數(shù)一樣。要調用一個方法,只需要在實例變量上直接調用,除了self不用傳遞,其他參數(shù)正常傳入,執(zhí)行如下語句:stu=Student0('xiaomeng',95)封裝的另一個好處是可以給Student類增加新的方法。classStudent0(object):718.8獲取對象信息當我們調用一個方法時,可能需要傳遞一個參數(shù),這個參數(shù)類型我們是知道的,但是對于接收參數(shù)的方法,就不一定知道參數(shù)是什么類型了。那我們該怎么來得知參數(shù)的類型呢?Python為我們提供了如下幾種獲取對象類型的方法,具體如下:1.使用type()我們前面已經(jīng)學習過type函數(shù)的使用,基本類型都可以用type()判斷。如:>>>type(123)<class'int'>>>>type('abc')<class'str'>>>>type(None)<class'NoneType'>8.8獲取對象信息當我們調用一個方法時,可能需要傳遞一個722.使用isinstance()要明確class的繼承關系,使用type()很不方便,通過判斷class的數(shù)據(jù)類型,來確定class的繼承關系,這樣要方便的多,這個時候可以使用isinstance()函數(shù)。比如對于繼承關系是如下的形式:object->Animal->Dog即Animal繼承object,Dog繼承Animal,使用isinstance()就可以告訴我們,一個對象是否是某種類型。例如創(chuàng)建如下2種類型的對象:>>>animal=Animal()>>>dog=Dog()對上面2種類型對象,使用isinstance判斷如下:>>>isinstance(dog,Dog)True2.使用isinstance()73根據(jù)打印結果看到,dog是Dog類型,這個是沒有任何疑問的,因為dog變量指向的就是Dog對象。接下來再判斷Animal的類型,使用isinstance判斷如下:>>>isinstance(dog,Animal)True根據(jù)打印結果看到,dog也是Animal類型。由此我們得知:dog雖然自身是Dog類型,但由于Dog是從Animal繼承下來的,所以,dog也還是Animal類型。換句話說,isinstance()判斷的是一個對象是否是該類型本身,或者是否是該類型繼承類的類型。因此,我們可以確信,dog還是object類型:>>>isinstance(dog,object)True同時也確信,實際類型是Dog類型的dog,同時也是Animal類型:>>>isinstance(dog,Dog)andisinstance(dog,Animal)True根據(jù)打印結果看到,dog是Dog類型,這個是沒有任何疑問的,743.使用dir()如果要獲得一個對象的所有屬性和方法,可以使用dir()函數(shù),它返回一個字符串的list如要獲得一個str對象的所有屬性和方法,使用方式如下:>>>dir('abc')3.使用dir()758.9類的專有方法Python類還可以定義專用方法,專用方法是在特殊情況下或當使用特別語法時由Python替你調用的,而不是像普通的方法那樣在代碼中直接調用。本節(jié)我們講述幾個Python比較常用的專有方法。看到類似__init__這種形如__xxx__的變量或者函數(shù)名就要注意,這些在Python中是有特殊用途的。__init__我們已經(jīng)知道怎么用了,Python的class中有許多這種有特殊用途的函數(shù),可以幫助我們定制類。下面來介紹這種特殊類型的函數(shù)定制類的方法。8.9類的專有方法Python類還可以定義專用方法,專用761.__str__()方法classStudent(object):def__init__(self,name):=name

def__str__(self):return'學生名稱:%s'%

print(Student('xiaozhi'))輸出結果為:<__main__.Studentobjectat0x0000000000D64198>1.__str__()方法77結果輸出的是一堆字符串,基本沒有人看得懂,這樣打印出來的結果沒有可用性。怎樣才能輸出讀者能看懂,并且是可用的結果?要解決這個問題,需要定義__str__()方法,通過__str__()方法返回一個易懂的字符串就可以了。重新定義上面的示例(student_2.py):classStudent(object):def__init__(self,name):=name

def__str__(self):returnf'學生名稱:{}'

print(Student('xiaozhi'))程序輸出結果為:學生名稱:xiaozhi結果輸出的是一堆字符串,基本沒有人看得懂,這樣打印出來的結果78如果在交互模式下輸入:>>>s=Student('xiaozhi')>>>s<__main__.Studentobjectat0x00000000030EC550>由輸出結果可以看到,將實例化的類對象賦給一個變量,輸出變量的實例還是和之前一樣,是一串基本看不懂的字符。直接顯示變量調用的不是__str__()方法,而是__repr__()方法,兩者的區(qū)別在于__str__()方法返回用戶看到的字符串,而__repr__()方法返回程序開發(fā)者看到的字符串。也就是說,__repr__()方法是為調試服務的。所以這個問題的解決辦法是再定義一個__repr__()方法。如果在交互模式下輸入:79通常,__str__()方法和__repr__()方法是一樣的,所以有一個巧妙的寫法(student_3.py):classStudent(object):def__init__(self,name):=name

def__str__(self):returnf'學生名稱:{}'__repr__=__str__在交互模式下執(zhí)行:>>>s=Student('xiaozhi')>>>s學生名稱:xiaozhi通常,__str__()方法和__repr__()方法是一樣802.__iter__()方法如果一個類想被用于for...in循環(huán),類似list或tuple那樣,就必須實現(xiàn)一個__iter__()方法,該方法返回一個迭代對象,Python的for循環(huán)會不斷調用該迭代對象的__next__()方法拿(“拿”是否可以更改為“獲得”)到循環(huán)的下一個值,直到遇到StopIteration錯誤時退出循環(huán)。classFib(object):def__init__(self):self.a,self.b=0,1#初始化兩個計數(shù)器a,bdef__iter__(self):returnself#實例本身就是迭代對象,故返回自己def__next__(self):self.a,self.b=self.b,self.a+self.b#計算下一個值ifself.a>100000:#退出循環(huán)的條件raiseStopIteration();returnself.a#返回下一個值2.__iter__()方法813.__getitem__()方法Fib實例雖然能作用于for循環(huán),看起來和list有點像,但是,把它當成list來使用還是不行,要表現(xiàn)得像list那樣按照下標取出元素,需要實現(xiàn)__getitem__()方法,如下實現(xiàn):

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論