Python面向對象程序設計OOP入門教程【類,實例,繼承,重載等】
本文實例講述了Python面向對象程序設計OOP。分享給大家供大家參考,具體如下:
類是Python所提供的最有用的的工具之一。合理使用時,類可以大量減少開發(fā)的時間。類也在流行的Python工具中使用,例如,tkinter GUI API。
為何使用類
與面向對象的Java一樣,類是對現實世界的一種抽象。
從更具體的程序設計觀點來看,類是Python的程序組成單元,就像函數和模塊一樣:類是封裝邏輯和數據的另一種方式。實際上,類也定義新的命名空間,在很大程度上就像模塊。但是類有三個重要的獨到之處,使其在建立對象時更為有用。
1.多重實例
類基本上就是產生對象的工廠。每次調用一個類,就會產生一個有獨立命名空間的新對象。每個又類產生的對象都能讀取類的屬性,并獲得自己的命名空間來儲存數據,這些數據對于每個對象來說都不同。
2.通過繼承進行定制
類也支持OOP的繼承的概念。我們可以在類的外部重新定義其屬性從而擴充這個類。
3.運算符重載
通過提供特定的協(xié)議方法,類可以定義對象來響應在內置類型上的幾種運算。例如,通過類創(chuàng)建的對象可以進行切片,級聯(lián)和索引等運算。Python提供了一些可以由類使用的鉤子,從而能夠中斷并實現任何的內置類型運算。
Python的類與Java類似,所以關于繼承、屬性和方法以及類和實例的基礎知識在這里不再贅述。直接介紹語法內容。
類產生多個實例對象
【OOP模型中的兩種對象:類對象和實例對象】
類對象提供默認行為,是實例對象的工廠。實例對象是程序處理的實際對象:各自都有獨立的命名空間,但是繼承(可自動存?。﹦?chuàng)建該實例的類中的變量名。類對象來自于語句,而實例來自于調用。每次調用一個類,就會得到這個類的新的實例。
【類對象提供默認行為】
執(zhí)行class語句,就會得到類對象。以下是Python類的主要特性:
1.class語句創(chuàng)建類對象并將其賦值給變量名。就像函數def語句,Python class語句也是可執(zhí)行語句,執(zhí)行時,會產生新的類對象,并將其賦值給class頭部的變量名。此外,就像def應用,class語句一般是在其所在文件導入時執(zhí)行的。
2.class語句內的賦值語句會創(chuàng)建類的屬性。class語句內的頂層的賦值語句會產生類對象中的屬性。從技術角度講,class語句的作用域會變成類對象的屬性的命名空間,就像模塊的全局作用域一樣。執(zhí)行class語句后,類的屬性可由變量名點號運算獲取object.name
。
3.類屬性提供對象的狀態(tài)和行為。類對象的屬性記錄狀態(tài)信息和行為,可由這個類創(chuàng)建的所有實例共享。位于類中的函數def語句會生成方法,方法將會處理實例。
【實例對象是具體元素】
當調用類對象時,我們得到了實例對象。以下是類的實例內含的重點概要。
1.像函數那樣調用類對象會創(chuàng)建新的實例對象。實例代表了程序領域中的具體元素。
2.每個實例對象繼承類的屬性并獲得了自己的命名空間。由類所創(chuàng)建的實例對象是新的命名空間。一開始是空的,但是會繼承創(chuàng)建該實例的類對象內的屬性。
3.在方法內對self屬性做賦值運算會產生每個實例自己的屬性。在類方法函數內,第一個參數(self)會引用正處理的實例對象。對self的屬性做賦值運算,會創(chuàng)建或修改實例內的數據,而不是類的數據。
第一個例子
首先定義一個名為FirstClass的類,通過交互模式運行Python class 語句。
>>> class FirstClass: def setdata(self,value): self.data = value def display(self): print(self.data)
這里是在交互模式下工作,但一般來說,這種語句應該是當其所在的模塊文件導入時運行的。就像通過def建立的函數,這個類在Python抵達并執(zhí)行語句前是不會存在的。
就像所有的復合語句一樣,class開頭一行會列出類的名稱,后面再接一個或多個內嵌并且縮進的語句的主體。
就像之前學過的,def其實是賦值運算。在這里把函數對象賦值給變量名setdata,而display位于class語句范圍內,因此會產生附加在類上的屬性:FirstClass.setdata
和FirstClass.display
。事實上,在類嵌套的代碼塊中頂層的賦值的任何變量名,都會變成類的屬性。
位于類中的函數通常稱為方法。方法時普通def,支持先去學過的函數的所有內容。在方法函數中,調用時,第一個參數(self)自動接收隱含的實例對象:調用的主體。下面建立這個類兩個實例:
>>> x = FirstClass() >>> y = FirstClass()
以此方式調用類時,【注意后面有小括號】,會產生實例對象。確切的講,此時有三個對象:兩個實例和一個類。
這兩個實例一開始是空的,但是它們被連接到創(chuàng)建它們的類。如果對實例以及類對象內的屬性名稱進行點號運算,Python會通過繼承搜索從類取得變量名。
>>> x.setdata('King Arthur') >>> y.setdata(3.14159)
x或y本身都沒有setdata屬性,為了尋找這個屬性,Python會順著實例到類的連接搜索。而這就是所謂的Python的繼承:繼承是在屬性點號運算時發(fā)生的,而且只與查找連接對象內的變量名有關。
在FirstClass的setdata函數中,傳入的值會賦給self.data
。在方法中,self(按慣例,這是最左側參數的名稱)會自動引用正在處理的實例(x或y),所以賦值語句會把值儲存在實例的命名空間,而不是類的命名空間。
因為類會產生多個實例,方法必須經過self參數才能獲取正在處理的實例。當調用類的display方法來打印self.data
時,會發(fā)現每個實例的值都不同。另外,變量名display在x和y之內都相同,因為它是來自于類的:
>>> x.display() King Arthur >>> y.display() 3.14159
注意:在每個實例內的data成員儲存了不同對象類型(字符串和浮點數)。就像Python中的其他事物,實例屬性并沒有聲明。首次賦值后,實例就會存在,就像簡單的變量。事實上,如果在調用setdata之前,就對某一實例調用display,則會觸發(fā)未定義變量名的錯誤:data屬性以setdata方法賦值前,是不會在內存中存在的。
我們可以在類的內部或外部修改實例屬性。在類內時,通過方法對self進行賦值運算,而在類外時,則可以通過對實例對象進行賦值運算:
>>> x.data = 'New value' >>> x.display() New value
雖然比較少見,通過在類方法函數外對變量名進行賦值運算,我們甚至可以在實例命名空間內產生全新的屬性:
>>> x.anothername = 'spam'
這里會增加一個名為anothername的新屬性,實例對象x的任何類方法都可以使用它。
不過,類通常是通過self參數進行賦值運算從而建立實例的所有屬性的。
類通過繼承進行定制
除了作為工廠來生成多個實例對象之外,類也可以引入新組件(子類)來進行修改,而不對現有組件進行原地的修改。由類產生的實例對象會繼承該類的屬性。
在Python中,實例從類中繼承,而類繼承于超類。以下是屬性繼承機制的核心觀點:
1.超類列在了類開頭的括號中。含有繼承的類稱為子類,而子類所繼承的類就是其超類。
2.類從其超類中繼承屬性。就像實例繼承其類中所定義的屬性名一樣,類也會繼承其超類中定義的所有屬性名稱。當讀取屬性時,如果它不存在于子類中,Python會自動搜索這個屬性。
3.每個object.attribute
都會開啟新的獨立搜索。
4.邏輯的修改是通過創(chuàng)建子類,而不是修改超類。在樹中層次較低的子類中重新定義超類的變量名,子類就可以取代并定制所繼承的行為。
第二個例子
下個例子建立在上一個例子的基礎之上。首先,定義一個新的類SecondClass,繼承FirstClass所有變量名,并提供自己的一個變量名。
>>> class secondClass(FirstClass): def display(self): print('Current value = " %s "'%self.data)
SecondClass定義display方法以不同格式打印。定義一個和FirstClass中的屬性同名的屬性,SecondClass有效地取代其超類內的display屬性。因為繼承搜索會從實例向上進行,之后到子類,然后到超類,直到所找到的屬性名稱首次出現為止。
有時候,我們把這種在樹中較低處發(fā)生的重新定義的、取代屬性的動作稱為【重載】。結果就是,SecondClass改變了display的行為,把FirstClass特定化了。另外,SecondClass(以及其任何實例)依然會繼承FirstClass的setdata方法:
>>> z = SecondClass() >>> z.setdata(42) >>> z.display() Current value = " 42 "
這里有一個和OOP相關的很重要的事情要留意:SecondClass引入的專有化完全是在FirstClass外部完成的。也就是說,不影響當前存在的或未來的FirstClass對象,就像上一個例子中的x:
>>> x.display() New value
類是模塊內的屬性
類的名稱沒有什么神奇之處。當class語句執(zhí)行時,這只是賦值給對象的變量,而對象可以用任何普通表達式引用。
例如,如果FirstClass是寫在模塊文件內,而不是在交互模式下輸入的,就可將其導入,在類開頭的那行可以正常地使用它的名稱。
from modulename import FirstClass class SecondClass(FirstClass): def display(self):...
或者,其等效寫法:
import modulename class SecondClass(module.FirstClass): def display():...
像其他事物一樣,類名稱總是存在于模塊中。每個模塊可以任意混合任意數量的變量、函數以及類。文件food.py示例如下:
#food.py var = 1 def func(): ... class spam: ... class ham: ... class eggs: ...
如果模塊和類碰巧有相同的名稱,也是如此。文件person.py,寫法如下:
class person: ...
需要像往常一樣通過模塊獲取類:
import person x = person.person()
person.person()
指的是person模塊內的person類。只寫person只會取得模塊,而不是類,除非使用from語句。
from person import person x = person()
Python的通用慣例之處,類名應該以一個大寫字母開頭,以使得他們更為清晰:
import person x = person.Person()
類可以截獲Python運算符:運算符重載
運算符重載就是讓類寫成的對象,可以截獲并響應用在內置類型上的運算:加法、切片、打印和點號運算等。
因為運算符重載,可以讓我們自己的對象行為就像內置對象那樣,這可促進對象接口更為一致并更易于學習,而且可讓類對象由預期的內置類型接口的代碼處理。以下是重載運算符主要概念的概要:
1.以雙下劃線命名的方法(__X__)是特殊鉤子。Python運算符重載的實現是提供特殊命名的方法來攔截運算。Python語言替每種運算和特殊命名的方法之間,定義了固定不變的映射關系。
2.當實例出現在內置運算時,這類方法會自動調用。例如,如果實例對象繼承了__add__方法,當對象出現在+表達式內時,該方法就會調用。該方法的返回值變成相應表達式的結果。
3.類可覆蓋多數內置類型運算。有幾十種特殊運算符重載的方法的名稱,幾乎可以截獲并實現內置類型的所有運算。它不僅包括了表達式,而且像打印和對象建立這類基本運算也包括在內。
4.運算符覆蓋方法沒有默認值,而且也不需要。如果類沒有定義或繼承運算符重載方法,就是說相應的運算在類實例中并不支持。例如,如果沒有__add__,+表達式就會引發(fā)異常。
5.運算符可讓類與Python的對象模型相集成。
【不過,要注意的是,運算符重載是可選的功能,一般的應用程序開發(fā)并不需要,除非真的有特殊的需求需要模仿內置類型接口?!?/p>
第三個例子
這一次,要定義SecondClass的子類,實現三個特殊名稱的屬性,讓Python自動進行調用:
1.當新的實例構造時,會調用__init__(self是新的ThirdClass對象)
2.當ThirdClass實例出現在+表達式中時,則會調用__add__。
3.當打印一個對象的時候(從技術上講,當通過str內置函數或者其Python內部的等價形式來將其轉換為打印字符串的時候),運行__str__
新的子類也定義了一個常規(guī)命名的方法,叫做mul,它在原處修改該實例的對象。如下是一個新的子類:
>>> class ThirdClass(SecondClass): def __init__(self,value): self.data = value def __add__(self,other): return ThirdClass(self.data+other) def __str__(self): return '[ThirdClass:%s]'%self.data def mul(self,other): self.data*=other >>> >>> a = ThirdClass('abc') >>> a.display() Current value = " abc " >>> print(a) [ThirdClass:abc] >>> b = a + 'xyz' >>> b.display() Current value = " abcxyz " >>> a.mul(3) >>> print(a) [ThirdClass:abcabcabc]
ThirdClass是一個SecondClass對象,所以其實例會繼承SecondClass的display方法。但是,ThirdClass生成的調用現在會傳遞一個參數(例如,‘abc'),這是傳給__init__構造函數內的參數value的,并將其值賦給self.data。直接效果就是,ThirdClass計劃在構建時自動設置data屬性,而不是在構建之后請求setdata調用。
此外,ThirdClass對象現在可以出現在+表達式和print調用中。對于+,Python把左側的實例對象傳給__add__中的self參數,而把右邊的值傳給other。__add__返回的內容成為+表達式的結果。對于print,Python把要打印的對象傳遞給__str__中的self;該方法返回的字符串看作是對象的打印字符串。使用__str__,我們可以用一個常規(guī)的print來顯示該類的對象,而不是調用特殊的display方法。
__init__、__add__和__str__這樣的特殊命名的方法會由子類和實例繼承,就像這個類中賦值的其他變量名。Python通常會自動調用,但偶爾也能由程序代碼調用。
【注意:只有在實現本質為數學的對象時,才會用到許多運算符重載方法。例如,向量或矩陣類可以重載加法運算符,但員工類可能就不用。就較簡單的類而言,可能根本不會用到重載】
【幾乎每個實例的類都會出現一個重載方法是:__init__構造函數。雖然Python不會對實例的屬性進行聲明,但通常也可以通過找到類的__init__方法的代碼,而了解實例有哪些屬性。】
注意:Python中沒有Java中的方法重載,即方法名相同,但參數和參數類型不同,比如__init__函數只能有一個,取最后一個賦給__init__的函數對象。
>>> class Test: def __init__(): pass def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex >>> a = Test() Traceback (most recent call last): File "<pyshell#54>", line 1, in <module> a = Test() TypeError: __init__() missing 3 required positional arguments: 'name', 'age', and 'sex'
把兩個__init__換了位置之后就沒有報錯了,因為__init__函數已經更改成了沒有參數的:
>>> class Test: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def __init__(self): pass >>> a = Test()
世界上最簡單的Python類
實際上,我們建立的類可以什么東西都沒有,下列語句建立一個類,其內完全沒有附加的屬性:
>>> class rec:pass
因為沒有寫任何方法,所以我們需要無操作的pass語句。以交互模式執(zhí)行此語句,建立這個類后,就可以完全在最初的class語句外,通過賦值變量名給這個類增加屬性:
>>> rec.name = 'Bob' >>> rec.age = 40
通過賦值語句創(chuàng)建這些屬性后,就可以用一般的語法將它們取出。這樣用時,類差不多就像C的struct,我們也可以用字典的鍵做類似的事情,但是需要額外的字符。
>>> print(rec.name) Bob
現在建立兩個該類的實例:
>>> x = rec() >>> y = rec() >>> x.name,y.name ('Bob', 'Bob')
這些實例本身沒有屬性,它們只是從類對象那里取出name屬性。不過,如果把一個屬性賦值給一個實例,就會在該對象內創(chuàng)建(或修改)該屬性,而不會因屬性的引用而啟動繼承搜索,因為屬性賦值運算只會影響屬性賦值所在的對象。在這里,x得到自己的name,但y依然繼承附加在他的類上的name:
>>> x.name = 'Gavin' >>> rec.name,x.name,y.name ('Bob', 'Gavin', 'Bob')
事實上,命名空間對象的屬性通常都是以字典的形式實現的。例如,__dict__屬性是針對大多數基于類的對象的命名空間字典。如下,名稱和__X__內部名稱集合所出現的順序可能隨著版本的不同而有所不同:
>>> rec.__dict__.keys() dict_keys(['__weakref__', 'name', '__module__', '__doc__', 'age', '__dict__']) >>> list(x.__dict__.keys()) ['name'] >>> list(y.__dict__.keys()) []
在這里,類的字典顯示出我們進行賦值了的name和age屬性,x有自己的name,而y依然是空的。不過,每個實例都連接至其類以便于繼承,如果你想查看的話,這個連接叫做__class__:
>>> x.__class__ <class '__main__.rec'>
類也有一個__bases__屬性,它是其超類的元祖:
>>> rec.__bases__ (<class 'object'>,)
這兩個屬性時Python在內存中類樹常量的表示方式。
即使是方法也可以完全獨立地在任意類對象的外部創(chuàng)建。例如,下列在任意類之外定義了一個簡單函數,并帶有一個參數:
>>> def upperName(self): return self.name.upper()
這里與類完全沒有什么關系——這是一個簡單函數,在此時就能予以調用,只要我們傳入一個帶有name屬性的對象:
>>> upperName(x) 'GAVIN'
不過,如果我們把這個簡單函數賦值成類的屬性,就會變成方法,可以由任何實例調用:
>>> rec.method = upperName >>> x.method() 'GAVIN' >>> y.method() 'BOB' >>> rec.method(x) 'GAVIN'
類與字典的關系
看如下字典的示例:
>>> rec = {} >>> rec['name'] = 'mel' >>> rec['age'] = 45 >>> rec['job'] = 'trainer/writer' >>> >>> print(rec['name']) mel
這段代碼模擬了像其他語言中記錄這樣的工具,這里也可以用類做同樣的事情:
>>> class rec:pass >>> rec.name = 'mel' >>> rec.age = 45 >>> rec.job = 'trainer/writer' >>> >>> print(rec.age) 45
這段代碼的語法比其字典等價形式要少很多。它使用了一個空的class語句來產生一個空的命名空間。
這是有效的,但是,對于我們將需要的每一條不同的記錄,都需要一條新的class語句。更通俗的講,我們可以產生一個空類的實例來表示每條不同的記錄:
>>> class rec:pass >>> pers1 = rec() >>> pers1.name='mel' >>> pers1.job = 'trainer' >>> pers1.age = 40 >>> >>> pers2 = rec() >>> pers2.name = 'vls' >>> pers2.job = 'developer' >>> >>> pers1.name,pers2.name ('mel', 'vls')
這里,我們通過對屬性賦值來填充記錄,實際上,同一個類的實例甚至不一定必須有相同的一組屬性名稱,在這個示例中,pers1有唯一的age屬性。每一個實例都有一個不同的屬性字典。
最后,我們可以編寫一個更完整的類來實現記錄及處理:
>>> class Person: def __init__(self,name,job): self.name = name self.job = job def info(self): return (self.name,self.job) >>> rec1 = Person('mel','trainer') >>> rec2 = Person('vls','developer') >>> >>> rec1.job,rec2.info() ('trainer', ('vls', 'developer'))
更多關于Python相關內容感興趣的讀者可查看本站專題:《Python面向對象程序設計入門與進階教程》、《Python數據結構與算法教程》、《Python函數使用技巧總結》、《Python字符串操作技巧匯總》、《Python編碼操作技巧總結》及《Python入門與進階經典教程》
希望本文所述對大家Python程序設計有所幫助。
相關文章
淺析Django 接收所有文件,前端展示文件(包括視頻,文件,圖片)ajax請求
這篇文章主要介紹了Django 接收所有文件,前端展示文件(包括視頻,文件,圖片)ajax請求,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值 ,需要的朋友可以參考下2020-03-03