到目前為止,在我們的程序中,我們都是根據(jù)操作數(shù)據(jù)的函數(shù)或語(yǔ)句塊來(lái)設(shè)計(jì)程序的。這被稱為 面向過(guò)程的 編程。還有一種把數(shù)據(jù)和功能結(jié)合起來(lái),用稱為對(duì)象的東西包裹起來(lái)組織程序的方法。這種方法稱為 面向?qū)ο蟮?/em> 編程理念。在大多數(shù)時(shí)候你可以使用過(guò)程性編程,但是有些時(shí)候當(dāng)你想要編寫(xiě)大型程序或是尋求一個(gè)更加合適的解決方案的時(shí)候,你就得使用面向?qū)ο蟮木幊碳夹g(shù)。
類和對(duì)象是面向?qū)ο缶幊痰膬蓚€(gè)主要方面。類創(chuàng)建一個(gè)新類型,而對(duì)象這個(gè)類的 實(shí)例 。這類似于你有一個(gè) int 類型的變量,這存儲(chǔ)整數(shù)的變量是 int 類的實(shí)例(對(duì)象)。
給 C/C++/Java/C#程序員的注釋
注意,即便是整數(shù)也被作為對(duì)象(屬于 int 類)。這和 C++、Java(1.5 版之前)把整數(shù)純粹作為類型是不同的。通過(guò) help(int)了解更多這個(gè)類的詳情。 C#和 Java 1.5 程序員會(huì)熟悉這個(gè)概念,因?yàn)樗愃婆c 封裝與解封裝 的概念。
對(duì)象可以使用普通的 屬于 對(duì)象的變量存儲(chǔ)數(shù)據(jù)。屬于一個(gè)對(duì)象或類的變量被稱為域。對(duì)象也可以使用屬于 類的函數(shù)來(lái)具有功能。這樣的函數(shù)被稱為類的方法。這些術(shù)語(yǔ)幫助我們把它們與孤立的函數(shù)和變量區(qū)分開(kāi)來(lái)。域和方法可以合稱為類的屬性。
域有兩種類型——屬于每個(gè)實(shí)例/類的對(duì)象或?qū)儆陬惐旧?。它們分別被稱為實(shí)例變量和類變量。
類使用 class 關(guān)鍵字創(chuàng)建。類的域和方法被列在一個(gè)縮進(jìn)塊中。
類的方法與普通的函數(shù)只有一個(gè)特別的區(qū)別——它們必須有一個(gè)額外的第一個(gè)參數(shù)名稱,但是在調(diào)用這個(gè)方法的時(shí)候你不為這個(gè)參數(shù)賦值,Python 會(huì)提供這個(gè)值。這個(gè)特別的變量指對(duì)象本身,按照慣例它的名稱是 self。
雖然你可以給這個(gè)參數(shù)任何名稱,但是 強(qiáng)烈建議 你使用 self 這個(gè)名稱——其他名稱都是不贊成你使用的。使用一個(gè)標(biāo)準(zhǔn)的名稱有很多優(yōu)點(diǎn)——你的程序讀者可以迅速識(shí)別它,如果使用 self 的話,還有些 IDE(集成開(kāi)發(fā)環(huán)境)也可以幫助你。
給 C++/Java/C#程序員的注釋
Python 中的 self 等價(jià)于 C++中的 self 指針和 Java、C#中的 this 參考。
你一定很奇怪 Python 如何給 self 賦值以及為何你不需要給它賦值。舉一個(gè)例子會(huì)使此變得清晰。假如你有一個(gè)類稱為 MyClass 和這個(gè)類的一個(gè)實(shí)例 MyObject。當(dāng)你調(diào)用這個(gè)對(duì)象的方法 MyObject.method(arg1, arg2)的時(shí)候,這會(huì)由 Python 自動(dòng)轉(zhuǎn)為 MyClass.method(MyObject, arg1, arg2)——這就是 self 的原理了。
這也意味著如果你有一個(gè)不需要參數(shù)的方法,你還是得給這個(gè)方法定義一個(gè) self 參數(shù)。
一個(gè)盡可能簡(jiǎn)單的類如下面這個(gè)例子所示。
例 11.1 創(chuàng)建一個(gè)類
#!/usr/bin/python
# Filename: simplestclass.py
class Person:
pass # An empty block
p = Person()
print p
(源文件:code/simplestclass.py)
輸出
$ python simplestclass.py
<__main__.Person instance at 0xf6fcb18c>
它如何工作
我們使用 class 語(yǔ)句后跟類名,創(chuàng)建了一個(gè)新的類。這后面跟著一個(gè)縮進(jìn)的語(yǔ)句塊形成類體。在這個(gè)例子中,我們使用了一個(gè)空白塊,它由 pass 語(yǔ)句表示。
接下來(lái),我們使用類名后跟一對(duì)圓括號(hào)來(lái)創(chuàng)建一個(gè)對(duì)象/實(shí)例。(我們將在下面的章節(jié)中學(xué)習(xí)更多的如何創(chuàng)建實(shí)例的方法)。為了驗(yàn)證,我們簡(jiǎn)單地打印了這個(gè)變量的類型。它告訴我們我們已經(jīng)在main模塊中有了一個(gè) Person 類的實(shí)例。
可以注意到存儲(chǔ)對(duì)象的計(jì)算機(jī)內(nèi)存地址也打印了出來(lái)。這個(gè)地址在你的計(jì)算機(jī)上會(huì)是另外一個(gè)值,因?yàn)?Python 可以在任何空位存儲(chǔ)對(duì)象。
我們已經(jīng)討論了類/對(duì)象可以擁有像函數(shù)一樣的方法,這些方法與函數(shù)的區(qū)別只是一個(gè)額外的 self 變量?,F(xiàn)在我們來(lái)學(xué)習(xí)一個(gè)例子。
例 11.2 使用對(duì)象的方法
#!/usr/bin/python
# 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()
(源文件:code/method.py)
輸出
$ python method.py
Hello, how are you?
它如何工作
這里我們看到了 self 的用法。注意 sayHi 方法沒(méi)有任何參數(shù),但仍然在函數(shù)定義時(shí)有 self。
在 Python 的類中有很多方法的名字有特殊的重要意義?,F(xiàn)在我們將學(xué)習(xí) init 方法的意義。
init 方法在類的一個(gè)對(duì)象被建立時(shí),馬上運(yùn)行。這個(gè)方法可以用來(lái)對(duì)你的對(duì)象做一些你希望的 初始化 。注意,這個(gè)名稱的開(kāi)始和結(jié)尾都是雙下劃線。
例 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()
(源文件:code/class_init.py)
輸出
$ python class_init.py
Hello, my name is Swaroop
它如何工作
這里,我們把 init 方法定義為取一個(gè)參數(shù) name(以及普通的參數(shù) self)。在這個(gè) init 里,我們只是創(chuàng)建一個(gè)新的域,也稱為 name。注意它們是兩個(gè)不同的變量,盡管它們有相同的名字。點(diǎn)號(hào)使我們能夠區(qū)分它們。
最重要的是,我們沒(méi)有專門調(diào)用 init 方法,只是在創(chuàng)建一個(gè)類的新實(shí)例的時(shí)候,把參數(shù)包括在圓括號(hào)內(nèi)跟在類名后面,從而傳遞給 init 方法。這是這種方法的重要之處。
現(xiàn)在,我們能夠在我們的方法中使用 self.name 域。這在 sayHi 方法中得到了驗(yàn)證。
給 C++/Java/C#程序員的注釋
init 方法類似于 C++、C#和 Java 中的 constructor 。
我們已經(jīng)討論了類與對(duì)象的功能部分,現(xiàn)在我們來(lái)看一下它的數(shù)據(jù)部分。事實(shí)上,它們只是與類和對(duì)象的名稱空間 綁定 的普通變量,即這些名稱只在這些類與對(duì)象的前提下有效。
有兩種類型的 域 ——類的變量和對(duì)象的變量,它們根據(jù)是類還是對(duì)象 擁有 這個(gè)變量而區(qū)分。
類的變量 由一個(gè)類的所有對(duì)象(實(shí)例)共享使用。只有一個(gè)類變量的拷貝,所以當(dāng)某個(gè)對(duì)象對(duì)類的變量做了改動(dòng)的時(shí)候,這個(gè)改動(dòng)會(huì)反映到所有其他的實(shí)例上。
對(duì)象的變量 由類的每個(gè)對(duì)象/實(shí)例擁有。因此每個(gè)對(duì)象有自己對(duì)這個(gè)域的一份拷貝,即它們不是共享的,在同一個(gè)類的不同實(shí)例中,雖然對(duì)象的變量有相同的名稱,但是是互不相關(guān)的。通過(guò)一個(gè)例子會(huì)使這個(gè)易于理解。
例 11.4 使用類與對(duì)象的變量
#!/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.
它如何工作
這是一個(gè)很長(zhǎng)的例子,但是它有助于說(shuō)明類與對(duì)象的變量的本質(zhì)。這里,population 屬于 Person 類,因此是一個(gè)類的變量。name 變量屬于對(duì)象(它使用 self 賦值)因此是對(duì)象的變量。
觀察可以發(fā)現(xiàn) init 方法用一個(gè)名字來(lái)初始化 Person 實(shí)例。在這個(gè)方法中,我們讓 population 增加 1,這是因?yàn)槲覀冊(cè)黾恿艘粋€(gè)人。同樣可以發(fā)現(xiàn),self.name 的值根據(jù)每個(gè)對(duì)象指定,這表明了它作為對(duì)象的變量的本質(zhì)。
記住,你只能使用 self 變量來(lái)參考同一個(gè)對(duì)象的變量和方法。這被稱為 屬性參考 。
在這個(gè)程序中,我們還看到 docstring 對(duì)于類和方法同樣有用。我們可以在運(yùn)行時(shí)使用 Person.doc 和 Person.sayHi.doc 來(lái)分別訪問(wèn)類與方法的文檔字符串。
就如同 init 方法一樣,還有一個(gè)特殊的方法del,它在對(duì)象消逝的時(shí)候被調(diào)用。對(duì)象消逝即對(duì)象不再被使用,它所占用的內(nèi)存將返回給系統(tǒng)作它用。在這個(gè)方法里面,我們只是簡(jiǎn)單地把 Person.population 減 1。
當(dāng)對(duì)象不再被使用時(shí), del 方法運(yùn)行,但是很難保證這個(gè)方法究竟在 什么時(shí)候 運(yùn)行。如果你想要指明它的運(yùn)行,你就得使用 del 語(yǔ)句,就如同我們?cè)谝郧暗睦又惺褂玫哪菢印?/p>
給 C++/Java/C#程序員的注釋
Python 中所有的類成員(包括數(shù)據(jù)成員)都是 公共的 ,所有的方法都是 有效的 。 只有一個(gè)例外:如果你使用的數(shù)據(jù)成員名稱以 雙下劃線前綴 比如 privatevar,Python 的名稱管理體系會(huì)有效地把它作為私有變量。 這樣就有一個(gè)慣例,如果某個(gè)變量只想在類或?qū)ο笾惺褂?,就?yīng)該以單下劃線前綴。而其他的名稱都將作為公共的,可以被其他類/對(duì)象使用。記住這只是一個(gè)慣例,并不是 Python 所要求的(與雙下劃線前綴不同)。 同樣,注意 del__ 方法與 destructor 的概念類似。
面向?qū)ο蟮木幊處?lái)的主要好處之一是代碼的重用,實(shí)現(xiàn)這種重用的方法之一是通過(guò) 繼承 機(jī)制。繼承完全可以理解成類之間的 類型和子類型 關(guān)系。
假設(shè)你想要寫(xiě)一個(gè)程序來(lái)記錄學(xué)校之中的教師和學(xué)生情況。他們有一些共同屬性,比如姓名、年齡和地址。他們也有專有的屬性,比如教師的薪水、課程和假期,學(xué)生的成績(jī)和學(xué)費(fèi)。
你可以為教師和學(xué)生建立兩個(gè)獨(dú)立的類來(lái)處理它們,但是這樣做的話,如果要增加一個(gè)新的共有屬性,就意味著要在這兩個(gè)獨(dú)立的類中都增加這個(gè)屬性。這很快就會(huì)顯得不實(shí)用。
一個(gè)比較好的方法是創(chuàng)建一個(gè)共同的類稱為 SchoolMember 然后讓教師和學(xué)生的類 繼承 這個(gè)共同的類。即它們都是這個(gè)類型(類)的子類型,然后我們?cè)贋檫@些子類型添加專有的屬性。
使用這種方法有很多優(yōu)點(diǎn)。如果我們?cè)黾?改變了 SchoolMember 中的任何功能,它會(huì)自動(dòng)地反映到子類型之中。例如,你要為教師和學(xué)生都增加一個(gè)新的身份證域,那么你只需簡(jiǎn)單地把它加到 SchoolMember 類中。然而,在一個(gè)子類型之中做的改動(dòng)不會(huì)影響到別的子類型。另外一個(gè)優(yōu)點(diǎn)是你可以把教師和學(xué)生對(duì)象都作為 SchoolMember 對(duì)象來(lái)使用,這在某些場(chǎng)合特別有用,比如統(tǒng)計(jì)學(xué)校成員的人數(shù)。一個(gè)子類型在任何需要父類型的場(chǎng)合可以被替換成父類型,即對(duì)象可以被視作是父類的實(shí)例,這種現(xiàn)象被稱為多態(tài)現(xiàn)象。
另外,我們會(huì)發(fā)現(xiàn)在 重用 父類的代碼的時(shí)候,我們無(wú)需在不同的類中重復(fù)它。而如果我們使用獨(dú)立的類的話,我們就不得不這么做了。
在上述的場(chǎng)合中,SchoolMember 類被稱為 基本類 或 超類 。而 Teacher 和 Student 類被稱為 導(dǎo)出類 或 子類 。
現(xiàn)在,我們將學(xué)習(xí)一個(gè)例子程序。
例 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"
它如何工作
為了使用繼承,我們把基本類的名稱作為一個(gè)元組跟在定義類時(shí)的類名稱之后。然后,我們注意到基本類的 init 方法專門使用 self 變量調(diào)用,這樣我們就可以初始化對(duì)象的基本類部分。這一點(diǎn)十分重要——Python 不會(huì)自動(dòng)調(diào)用基本類的 constructor,你得親自專門調(diào)用它。
我們還觀察到我們?cè)诜椒ㄕ{(diào)用之前加上類名稱前綴,然后把 self 變量及其他參數(shù)傳遞給它。
注意,在我們使用 SchoolMember 類的 tell 方法的時(shí)候,我們把 Teacher 和 Student 的實(shí)例僅僅作為 SchoolMember 的實(shí)例。
另外,在這個(gè)例子中,我們調(diào)用了子類型的 tell 方法,而不是 SchoolMember 類的 tell 方法。可以這樣來(lái)理解,Python 總是首先查找對(duì)應(yīng)類型的方法,在這個(gè)例子中就是如此。如果它不能在導(dǎo)出類中找到對(duì)應(yīng)的方法,它才開(kāi)始到基本類中逐個(gè)查找?;绢愂窃陬惗x的時(shí)候,在元組之中指明的。
一個(gè)術(shù)語(yǔ)的注釋——如果在繼承元組中列了一個(gè)以上的類,那么它就被稱作 多重繼承 。
我們已經(jīng)研究了類和對(duì)象的多個(gè)內(nèi)容以及與它們相關(guān)的多個(gè)術(shù)語(yǔ)。通過(guò)本章,你已經(jīng)了解了面向?qū)ο蟮木幊痰膬?yōu)點(diǎn)和缺陷。Python 是一個(gè)高度面向?qū)ο蟮恼Z(yǔ)言,理解這些概念會(huì)在將來(lái)有助于你進(jìn)一步深入學(xué)習(xí) Python。
接下來(lái),我們將學(xué)習(xí)如何處理輸入/輸出已經(jīng)如何用 Python 訪問(wèn)文件。