文章目錄
- 1、self
- 2、類
- 3、對象的方法
- 4、__init__方法:
- 5、類與對象的方法
- 6、繼承
九、物件導向的編程
到目前為止,在我們的程式中,我們都是根據操作資料的函數或語句塊來設計程式的。這被稱為面向過程的編程。還有一種把資料和功能結合起來,用稱為對象的東西包裹起來組織程式的方法。這種方法稱為物件導向的 編程理念。在大多數時候你可以使用過程性編程,但是有些時候當你想要編寫大型程式或是尋求一個更加合適的解決方案的時候,你就得使用物件導向的編程技術。
類和對象是物件導向編程的兩個主要方面。類建立一個新類型,而對象這個類的執行個體。這類似於你有一個int類型的變數,這儲存整數的變數是int類的執行個體(對象)。
注意:即便是整數也被作為對象(屬於int類)。這和C++、Java(1.5版之前)把整數純粹作為類型是不同的。通過help(int)瞭解更多這個類的詳情。 C#和Java 1.5程式員會熟悉這個概念,因為它類似與封裝與解鎖裝的概念。
對象可以使用屬於這個對象的變數儲存資料。屬於對象或類的變數稱作域(fields)。對象同樣可以利用屬於類的函數實現所需的功能。這些函數被稱作類的方法(method)。
這些術語協助我們把它們與孤立的函數和變數區分開來。域和方法可以合稱為類的屬性。
欄位分為兩種類型——它們既可以屬於類的執行個體/對象也可以屬於類本身,兩者分別稱為執行個體變數與類變數。
一個類通過關鍵字class建立。欄位與方法被列在類的縮排塊裡。
1、self
類方法與普通函數只有一個特殊區別——類方法必須增加一個額外的形參,而且它必須處於第一個形參的位置,但是在調用類方法時不要為這個額外的形參傳值,python會自動代勞。這個特別的變數引用對象本身,按照慣例它被命名為self。
儘管你可以隨便為這個形參取名字,但我強烈建議你使用self——其它名字都不贊成你使用。使用標準名字是有很多好處的——任何你的代碼的讀者都會立即明白它代表什麼,甚至當你使用標準名字時專業的IDE都會更好的協助你。
注意:Python中的self
等價於C++中的self
指標和Java、C#中的this
參考。
你一定很奇怪Python如何給self賦值以及為何你不需要給它賦值。舉一個例子會使此變得清晰。假如你有一個類稱為MyClass和這個類的一個執行個體MyObject。當你調用這個對象的方法MyObject.method(arg1, arg2)的時候,這會由Python自動轉為MyClass.method(MyObject, arg1, arg2)——這就是self的原理了。
這也意味著如果你有一個不需要參數的方法,你還是得給這個方法定義一個self參數。
2、類建立一個類:
下面的例子可以是一個最簡單的類了。
# Filename: simplestclass.py class Person: pass # An empty block p = Person()print(p)
輸出:
C:\Users\Administrator>python D:\python\simplestclass.py
<__main__.Person object at 0x01CF8A90>
工作原理:
我們使用class語句後跟類名,建立了一個新的類。這後面跟著一個縮排的語句塊形成類體。在這個例子中,我們使用了一個空白塊,它由pass語句表示。
接下來,我們使用類名後跟一對圓括弧來建立一個對象/執行個體。為了驗證,我們簡單地列印了這個變數的類型。它告訴我們我們已經在__main__模組中有了一個Person類的執行個體。
可以注意到儲存物件的電腦記憶體位址也列印了出來。這個地址在你的電腦上會是另外一個值,因為Python可以在任何空位儲存物件。
3、對象的方法
我們已經討論了類/對象可以擁有像函數一樣的方法,這些方法與函數的區別只是一個額外的self變數。現在我們來看一個例子:
# Filename: method.py class Person: def sayHi(self): print('Hello, how are you?') p = Person()p.sayHi() # This short example can also be written as Person().sayHi()
輸出:
C:\Users\Administrator>python D:\python\method.py
Hello, how are you?
工作原理:
本例中我們使用了self,注意方法sayHi雖無需參數但在函數中仍然要寫上self。
4、__init__方法:
在Python的類中有很多方法的名字有特殊的重要意義。現在我們將學習__init__方法的意義。
__init__方法在類的一個對象被建立時,馬上運行。這個方法可以用來對你的對象做一些你希望的初始化。注意,這個名稱的開始和結尾都是雙底線。
例如:
# Filename: class_init.py class Person: def __init__(self, name): self.name = name def sayHi(self): print('Hello, my name is', self.name) p = Person('Swaroop')p.sayHi() # This short example can also be written as Person('Swaroop').sayHi()
輸出:
C:\Users\Administrator>python D:\python\class_init.py
Hello, my name is Swaroop
工作原理:
這裡,我們把__init__方法定義為取一個參數name(以及普通的參數self)。在這個__init__裡,我們只是建立一個新的域,也稱為name。注意它們是兩個不同的變數,儘管它們有相同的名字。點號使我們能夠區分它們。
最重要的是,我們沒有專門調用__init__方法,只是在建立一個類的新執行個體的時候,把參數包括在圓括弧內跟在類名後面,從而傳遞給__init__方法。這是這種方法的重要之處。
現在,我們能夠在我們的方法中使用self.name域。這在sayHi方法中得到了驗證。
注意:__init__方法類似於C++、C#和Java中的constructor。
5、類與對象的方法
我們已經討論了類與對象的功能部分,現在我們來看一下它的資料部分。事實上,它們只是與類和對象的名稱空間綁定的普通變數,即這些名稱只在這些類與對象的前提下有效。
有兩種類型的域——類變數和物件變數,它們根據是類還是對象 擁有 這個變數而區分。
類變數由一個類的所有對象(執行個體)共用使用。只有一個類變數的拷貝,所以當某個對象對類的變數做了改動的時候,這個改動會反映到所有其他的執行個體上。
對象的變數 由類的每個對象/執行個體擁有。因此每個對象有自己對這個域的一份拷貝,即它們不是共用的,在同一個類的不同執行個體中,雖然對象的變數有相同的名稱,但是是互不相關的。通過一個例子會使這個易於理解。
# Filename: objvar.py class Robot: '''Represents a robot, with a name.''' # A class variable, counting the number of robots population = 0 def __init__(self, name): '''Initializes the data.''' self.name = name print('(Initializing {0})'.format(self.name)) # When this person is created, the robot # adds to the population Robot.population += 1 def __del__(self): '''I am dying.''' print('{0} is being destroyed!'.format(self.name)) Robot.population -= 1 if Robot.population == 0: print('{0} was the last one.'.format(self.name)) else: print('There are still {0:d} robots working.'.format(Robot.population)) def sayHi(self): '''Greeting by the robot. Yeah, they can do that.''' print('Greetings, my masters call me {0}.'.format(self.name)) def howMany(): '''Prints the current population.''' print('We have {0:d} robots.'.format(Robot.population)) howMany = staticmethod(howMany) droid1 = Robot('R2-D2')droid1.sayHi()Robot.howMany() droid2 = Robot('C-3PO')droid2.sayHi()Robot.howMany() print("\nRobots can do some work here.\n") print("Robots have finished their work. So let's destroy them.")del droid1del droid2 Robot.howMany()
輸出:
C:\Users\Administrator>python D:\python\objvar.py
(Initializing R2-D2)
Greetings, my masters call me R2-D2.
We have 1 robots.
(Initializing C-3PO)
Greetings, my masters call me C-3PO.
We have 2 robots.
Robots can do some work here.
Robots have finished their work. So let's destroy them.
R2-D2 is being destroyed!
There are still 1 robots working.
C-3PO is being destroyed!
C-3PO was the last one.
We have 0 robots.
工作原理:
這是一個很長的例子,但是它有助於說明類與對象的變數的本質。這裡,population屬於Person類,因此是一個類的變數。name變數屬於對象(它使用self賦值)因此是對象的變數。
所以,我們使用Robot.population引用類變數populatin而不是self.population.。而在方法中引用物件變數name時使用self.name文法。
請記住這個類變數和類對象中簡單的差異,還要注意物件變數會隱藏同名的類變數!
howMany實際上是一個屬於類的方法而不是對象。這意味著我們可以將及其定義為classmethod也可以定義為staticmethod,這取決於我們是否需要知道我們是哪個類的一部分。因為我們無需類似資訊,所以我們使用staticmethod。
另外我們還可以通過裝飾符(decorators)達到同樣的效果:
@staticmethod def howMany(): '''Prints the current population.''' print('We have {0:d} robots.'.format(Robot.population))
裝飾符可以被想象成調用一條顯式語句的捷徑,就像在這個例中看到的一樣。
觀察__init__方法,它用於以一個指定的名字初始化Robot執行個體。其內部對population累加1,因為我們又添加了一個機器人。同時觀察self.name,它的值特定於每個對象,這也指出了類對象的本質。
記住,你只能使用self引用相同對象的變數和方法。這被稱作屬性引用。
本例中,我們還在類與方法中使用了文檔字串。我們可以在運行時使用Robot.__doc__和Robot.sayhi.__doc__分別訪問類和方法的文檔字串。就像__init__方法,這裡還有另一個特殊方法__del__,當對象銷毀的時候將被調用。對象銷毀是指對象不再被使用了,它佔用的空間將返回給系統以便重複使用。在__del__中我們只是簡單的將Robot.population減1。當對象不再被使用時__del__方法將可以被執行,但無法保證到底什麼時執行它。如果你想顯式執行它則必須使用del語句,就象本例中做的那樣。(註:本例中del後對象的引用計數降為0)。
注意:Python中所有的類成員(包括資料成員)都是 公用的 ,所有的方法都是 有效 。只有一個例外:如果你使用的資料成員名稱以 雙底線首碼 比如__privatevar,Python的名稱管理體系會有效地把它作為私人變數。
這樣就有一個慣例,如果某個變數只想在類或對象中使用,就應該以單底線首碼。而其他的名稱都將作為公用的,可以被其他類/對象使用。記住這隻是一個慣例,並不是Python所要求的(與雙底線首碼不同)。
同樣,注意__del__方法與 destructor 的概念類似。
6、繼承
物件導向的編程帶來的主要好處之一是代碼的重用,實現這種重用的方法之一是通過 繼承 機制。繼承完全可以理解成類之間的 類型和子類型關係。
假設你想要寫一個程式來記錄學校之中的教師和學生情況。他們有一些共同屬性,比如姓名、年齡和地址。他們也有專有的屬性,比如教師的薪水、課程和假期,學生的成績和學費。
你可以為教師和學生建立兩個獨立的類來處理它們,但是這樣做的話,如果要增加一個新的共有屬性,就意味著要在這兩個獨立的類中都增加這個屬性。這很快就會顯得不實用。
一個比較好的方法是建立一個共同的類稱為SchoolMember然後讓教師和學生的類 繼承 這個共同的類。即它們都是這個類型(類)的子類型,然後我們再為這些子類型添加專有的屬性。
使用這種方法有很多優點。如果我們增加/改變了SchoolMember中的任何功能,它會自動地反映到子類型之中。例如,你要為教師和學生都增加一個新的身份證域,那麼你只需簡單地把它加到SchoolMember類中。然而,在一個子類型之中做的改動不會影響到別的子類型。另外一個優點是你可以把教師和學生對象都作為SchoolMember對象來使用,這在某些場合特別有用,比如統計學校成員的人數。一個子類型在任何需要父類型的場合可以被替換成父類型,即對象可以被視作是父類的執行個體,這種現象被稱為多態現象。
另外,我們會發現在 重用 父類的代碼的時候,我們無需在不同的類中重複它。而如果我們使用獨立的類的話,我們就不得不這麼做了。
在上述的場合中,SchoolMember類被稱為基本類或超類 。而Teacher和Student類被稱為匯出類或子類 。
下面我們看一個範例
# Filename: inherit.py class SchoolMember: '''Represents any school member.''' def __init__(self, name, age): self.name = name self.age = age print('(Initialized SchoolMember: {0})'.format(self.name)) def tell(self): '''Tell my details.''' print('Name:"{0}" Age:"{1}"'.format(self.name, self.age), end=" ") class Teacher(SchoolMember): '''Represents a teacher.''' def __init__(self, name, age, salary): SchoolMember.__init__(self, name, age) self.salary = salary print('(Initialized Teacher: {0})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('Salary: "{0:d}"'.format(self.salary)) class Student(SchoolMember): '''Represents a student.''' def __init__(self, name, age, marks): SchoolMember.__init__(self, name, age) self.marks = marks print('(Initialized Student: {0})'.format(self.name)) def tell(self): SchoolMember.tell(self) print('Marks: "{0:d}"'.format(self.marks)) t = Teacher('Mrs. Shrividya', 40, 30000)s = Student('Swaroop', 25, 75) print() # prints a blank line members = [t, s]for member in members: member.tell() # works for both Teachers and Students
輸出:
C:\Users\Administrator>python D:\python\inherit.py
(Initialized SchoolMember: Mrs. Shrividya)
(Initialized Teacher: Mrs. Shrividya)
(Initialized SchoolMember: Swaroop)
(Initialized Student: Swaroop)
Name:"Mrs. Shrividya" Age:"40" Salary: "30000"
Name:"Swaroop" Age:"25" Marks: "75"
工作原理:
為了使用繼承,我們把基本類的名稱作為一個元組跟在定義類時的類名稱之後。然後,我們注意到基本類的__init__方法專門使用self變數調用,這樣我們就可以初始化對象的基本類部分。這一點十分重要——Python不會自動調用基本類的constructor,你得親自專門調用它。
我們還觀察到我們在方法調用之前加上類名稱首碼,然後把self變數及其他參數傳遞給它。
注意,在我們使用SchoolMember類的tell方法的時候,我們把Teacher和Student的執行個體僅僅作為SchoolMember的執行個體。
另外,在這個例子中,我們調用了子類型的tell方法,而不是SchoolMember類的tell方法。可以這樣來理解,Python總是首先尋找對應類型的方法,在這個例子中就是如此。如果python沒有找到被呼叫者法,則會在基類中逐個尋找,尋找順序由類定義時在元組中指定的基類順序決定。
一個術語的注釋——如果一個類繼承了多個基類,則被稱做多重繼承。