到目前為止,在我們的程式中,我們都是根據操作資料的函數或語句塊來設計程式的。這被稱為 面向過程的
編程。還有一種把資料和功能結合起來,用稱為對象的東西包裹起來組織程式的方法。這種方法稱為 物件導向的
編程理念。在大多數時候你可以使用過程性編程,但是有些時候當你想要編寫大型程式或是尋求一個更加合適的解決方案的時候,你就得使用物件導向的編程技術。
類和對象是物件導向編程的兩個主要方面。類建立一個新類型,而對象這個類的 執行個體 。這類似於你有一個int類型的變數,這儲存整數的變數是int類的執行個體(對象)。
給C/C++/Java/C#程式員的注釋
注意,即便是整數也被作為對象(屬於int類)。這和C++、Java(1.5版之前)把整數純粹作為類型是不同的。通過help(int)瞭解更多這個類的詳情。 C#和Java 1.5程式員會熟悉這個概念,因為它類似與 封裝與解鎖裝 的概念。
對象可以使用普通的 屬於 對象的變數儲存資料。屬於一個對象或類的變數被稱為域。對象也可以使用 屬於 類的函數來具有功能。這樣的函數被稱為類的方法。這些術語協助我們把它們與孤立的函數和變數區分開來。域和方法可以合稱為類的屬性。
域有兩種類型——屬於每個執行個體/類的對象或屬於類本身。它們分別被稱為執行個體變數和類變數。
類使用class關鍵字建立。類的域和方法被列在一個縮排塊中。
self
類的方法與普通的函數只有一個特別的區別——它們必須有一個額外的第一個參數名稱,但是在調用這個方法的時候你不為這個參數賦值,Python會提供這個值。這個特別的變數指對象本身,按照慣例它的名稱是self。
雖然你可以給這個參數任何名稱,但是 強烈建議 你使用self這個名稱——其他名稱都是不贊成你使用的。使用一個標準的名稱有很多優點——你的程式讀者可以迅速識別它,如果使用self的話,還有些IDE(整合式開發環境)也可以協助你。
給C++/Java/C#程式員的注釋
Python中的self等價於C++中的self指標和Java、C#中的this參考。
你一定很奇怪Python如何給self賦值以及為何你不需要給它賦值。舉一個例子會使此變得清晰。假如你有一個類稱為MyClass和這個類的一個執行個體
MyObject。當你調用這個對象的方法MyObject.method(arg1,
arg2)的時候,這會由Python自動轉為MyClass.method(MyObject, arg1, arg2)——這就是self的原理了。
這也意味著如果你有一個不需要參數的方法,你還是得給這個方法定義一個self參數。
一個儘可能簡單的類如下面這個例子所示。
建立一個類
例11.1 建立一個類
#!/usr/bin/python
# Filename: simplestclass.py
class Person:
pass # An empty block
p = Person()
print p
對象的方法
我們已經討論了類/對象可以擁有像函數一樣的方法,這些方法與函數的區別只是一個額外的self變數。現在我們來學習一個例子。
使用對象的方法
例11.2 使用對象的方法
#!/usr/bin/python
# Filename: method.py
class Person:
def sayHi(self):
print 'Hello, how are you?'
p = Person()
p.sayHi()
__init__方法
在Python的類中有很多方法的名字有特殊的重要意義。現在我們將學習__init__方法的意義。
__init__方法在類的一個對象被建立時,馬上運行。這個方法可以用來對你的對象做一些你希望的 初始化 。注意,這個名稱的開始和結尾都是雙底線。
使用__init__方法
例11.3 使用__init__方法
#!/usr/bin/python
# 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()
類與對象的方法
我們已經討論了類與對象的功能部分,現在我們來看一下它的資料部分。事實上,它們只是與類和對象的名稱空間 綁定 的普通變數,即這些名稱只在這些類與對象的前提下有效。
有兩種類型的 域 ——類的變數和對象的變數,它們根據是類還是對象 擁有 這個變數而區分。
類的變數 由一個類的所有對象(執行個體)共用使用。只有一個類變數的拷貝,所以當某個對象對類的變數做了改動的時候,這個改動會反映到所有其他的執行個體上。
對象的變數 由類的每個對象/執行個體擁有。因此每個對象有自己對這個域的一份拷貝,即它們不是共用的,在同一個類的不同執行個體中,雖然對象的變數有相同的名稱,但是是互不相關的。通過一個例子會使這個易於理解。
使用類與對象的變數
例11.4 使用類與對象的變數
#!/usr/bin/python
# Filename: objvar.py
class Person:
'''Represents a person.'''
population = 0
def __init__(self, name):
'''Initializes the person's data.'''
self.name = name
print '(Initializing %s)' % self.name
# When this person is created, he/she
# adds to the population
Person.population += 1
def __del__(self):
'''I am dying.'''
print '%s says bye.' % self.name
Person.population -= 1
if Person.population == 0:
print 'I am the last one.'
else:
print 'There are still %d people left.' % Person.population
def sayHi(self):
'''Greeting by the person.
Really, that's all it does.'''
print 'Hi, my name is %s.' % self.name
def howMany(self):
'''Prints the current population.'''
if Person.population == 1:
print 'I am the only person here.'
else:
print 'We have %d persons here.' % Person.population
swaroop = Person('Swaroop')
swaroop.sayHi()
swaroop.howMany()
kalam = Person('Abdul Kalam')
kalam.sayHi()
kalam.howMany()
swaroop.sayHi()
swaroop.howMany()
(源檔案:code/objvar.py)
輸出
$ python objvar.py
(Initializing Swaroop)
Hi, my name is Swaroop.
I am the only person here.
(Initializing Abdul Kalam)
Hi, my name is Abdul Kalam.
We have 2 persons here.
Hi, my name is Swaroop.
We have 2 persons here.
Abdul Kalam says bye.
There are still 1 people left.
Swaroop says bye.
I am the last one.
它如何工作
這是一個很長的例子,但是它有助於說明類與對象的變數的本質。這裡,population屬於Person類,因此是一個類的變數。name變數屬於對象(它使用self賦值)因此是對象的變數。
觀察可以發現__init__方法用一個名字來初始化Person執行個體。在這個方法中,我們讓population增加1,這是因為我們增加了一個人。同樣可以發現,self.name的值根據每個對象指定,這表明了它作為對象的變數的本質。
記住,你只能使用self變數來參考同一個對象的變數和方法。這被稱為 屬性參考 。
在這個程式中,我們還看到docstring對於類和方法同樣有用。我們可以在運行時使用Person.__doc__和Person.sayHi.__doc__來分別訪問類與方法的文檔字串。
就如同__init__方法一樣,還有一個特殊的方法__del__,它在對象消逝的時候被調用。對象消逝即對象不再被使用,它所佔用的記憶體將返回給系統作它用。在這個方法裡面,我們只是簡單地把Person.population減1。
當對象不再被使用時,__del__方法運行,但是很難保證這個方法究竟在 什麼時候 運行。如果你想要指明它的運行,你就得使用del語句,就如同我們在以前的例子中使用的那樣。
給C++/Java/C#程式員的注釋
Python中所有的類成員(包括資料成員)都是 公用的 ,所有的方法都是 有效 。
只有一個例外:如果你使用的資料成員名稱以 雙底線首碼 比如__privatevar,Python的名稱管理體系會有效地把它作為私人變數。
這樣就有一個慣例,如果某個變數只想在類或對象中使用,就應該以單底線首碼。而其他的名稱都將作為公用的,可以被其他類/對象使用。記住這隻是一個慣例,並不是Python所要求的(與雙底線首碼不同)。
同樣,注意__del__方法與 destructor 的概念類似。
繼承
物件導向的編程帶來的主要好處之一是代碼的重用,實現這種重用的方法之一是通過 繼承 機制。繼承完全可以理解成類之間的 類型和子類型 關係。
假設你想要寫一個程式來記錄學校之中的教師和學生情況。他們有一些共同屬性,比如姓名、年齡和地址。他們也有專有的屬性,比如教師的薪水、課程和假期,學生的成績和學費。
你可以為教師和學生建立兩個獨立的類來處理它們,但是這樣做的話,如果要增加一個新的共有屬性,就意味著要在這兩個獨立的類中都增加這個屬性。這很快就會顯得不實用。
一個比較好的方法是建立一個共同的類稱為SchoolMember然後讓教師和學生的類 繼承 這個共同的類。即它們都是這個類型(類)的子類型,然後我們再為這些子類型添加專有的屬性。
使用這種方法有很多優點。如果我們增加/改變了SchoolMember中的任何功能,它會自動地反映到子類型之中。例如,你要為教師和學生都增加一個新
的身份證域,那麼你只需簡單地把它加到SchoolMember類中。然而,在一個子類型之中做的改動不會影響到別的子類型。另外一個優點是你可以把教師
和學生對象都作為SchoolMember對象來使用,這在某些場合特別有用,比如統計學校成員的人數。一個子類型在任何需要父類型的場合可以被替換成父
類型,即對象可以被視作是父類的執行個體,這種現象被稱為多態現象。
另外,我們會發現在 重用 父類的代碼的時候,我們無需在不同的類中重複它。而如果我們使用獨立的類的話,我們就不得不這麼做了。
在上述的場合中,SchoolMember類被稱為 基本類 或 超類 。而Teacher和Student類被稱為 匯出類 或 子類 。
現在,我們將學習一個例子程式。
使用繼承
例11.5 使用繼承
#!/usr/bin/python
# Filename: inherit.py
class SchoolMember:
'''Represents any school member.'''
def __init__(self, name, age):
self.name = name
self.age = age
print '(Initialized SchoolMember: %s)' % self.name
def tell(self):
'''Tell my details.'''
print 'Name:"%s" Age:"%s"' % (self.name, self.age),
class Teacher(SchoolMember):
'''Represents a teacher.'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print '(Initialized Teacher: %s)' % self.name
def tell(self):
SchoolMember.tell(self)
print 'Salary: "%d"' % 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: %s)' % self.name
def tell(self):
SchoolMember.tell(self)
print 'Marks: "%d"' % self.marks
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 22, 75)
print # prints a blank line
members = [t, s]
for member in members:
member.tell() # works for both Teachers and Students
(源檔案:code/inherit.py)
輸出
$ 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:"22" Marks: "75"
它如何工作
為了使用繼承,我們把基本類的名稱作為一個元組跟在定義類時的類名稱之後。然後,我們注意到基本類的__init__方法專門使用self變數調用,這樣
我們就可以初始化對象的基本類部分。這一點十分重要——Python不會自動調用基本類的constructor,你得親自專門調用它。
我們還觀察到我們在方法調用之前加上類名稱首碼,然後把self變數及其他參數傳遞給它。
注意,在我們使用SchoolMember類的tell方法的時候,我們把Teacher和Student的執行個體僅僅作為SchoolMember的執行個體。
另外,在這個例子中,我們調用了子類型的tell方法,而不是SchoolMember類的tell方法。可以這樣來理解,Python總是首先尋找對應
類型的方法,在這個例子中就是如此。如果它不能在匯出類中找到對應的方法,它才開始到基本類中逐個尋找。基本類是在類定義的時候,在元組之中指明的。
一個術語的注釋——如果在繼承元組中列了一個以上的類,那麼它就被稱作 多重繼承 。