類與對(duì)象
Python是面向?qū)ο?/strong>的語(yǔ)言(object-oriented),同樣面向?qū)ο蟮恼Z(yǔ)言還有C++歉嗓,Java等赫段;與之相對(duì)的是面向過程的語(yǔ)言(procedural),例如C語(yǔ)言榜贴。前面的教程中豌研,我們也主要是采用了面向過程的編程方式妹田,在這一節(jié)中,將為大家介紹面向?qū)ο蟮木幊谭椒ň楣玻鋵?shí)現(xiàn)途徑就是使用類(class)和對(duì)象(object)鬼佣。
在面向?qū)ο蟮木幊讨校覀冎饕幚淼牟皇乔懊娼榻B的那樣傳統(tǒng)的一行行的代碼霜浴,而是來(lái)模擬現(xiàn)實(shí)世界中的不同的事物晶衷,即對(duì)象。一個(gè)對(duì)象可以有屬性和行為阴孟,也可以和其他對(duì)象相聯(lián)系和發(fā)生相互作用晌纫。例如,一只小狗可以有屬性(年齡永丝、體重等)和行為(吠锹漱、跑、打滾等)慕嚷。面向?qū)ο蟮木幊谭绞交谝韵滤膫€(gè)核心特征:
- 多態(tài)(Polymorphism)
- 繼承(Inheritance)
- 抽象(Abstraction)
- 封裝(Encapsulation)
核心概念
這里我們簡(jiǎn)要介紹一下上述這些重要的概念凌蔬。
- 類:類是創(chuàng)建對(duì)象的藍(lán)圖,我們可以把類理解為一個(gè)生產(chǎn)對(duì)象的工廠闯冷。類提供了創(chuàng)建對(duì)象的模板砂心,通過方法來(lái)指定對(duì)象的行為,通過屬性來(lái)指定其狀態(tài)蛇耀。
- 對(duì)象:對(duì)象就是類的一個(gè)實(shí)例辩诞,一個(gè)對(duì)象可以有狀態(tài)(屬性)和行為。
- 繼承:在面向?qū)ο蟮木幊讨蟹牡樱宇惪梢詮母割惱^承屬性和方法译暂。繼承可以分為單一繼承和多重繼承。單一繼承是指一個(gè)子類只繼承一個(gè)父類撩炊,而多重繼承指一個(gè)子類可以繼承多個(gè)父類外永。
- 多態(tài):同一操作作用于不同的對(duì)象,可以有不同的解釋拧咳,產(chǎn)生不同的執(zhí)行結(jié)果伯顶,這就是多態(tài)性。簡(jiǎn)單的說:就是用基類的引用指向子類的對(duì)象骆膝。在這里我們可以理解為祭衩,同一個(gè)方法有不同的輸出。
- 抽象:這個(gè)不知道具體該怎么表達(dá)阅签,直接引用一段"Learn Python in 7 Days (Mohit, Bhaskar N. Das)"里的原文“Here, we hide the necessary details and are only interested in showing the relevant details to the other intended user. Here, by other intended user we mean another software application, or another class, or other client who will be the end users of the program.”
- 封裝:封裝是指將類的方法的細(xì)節(jié)進(jìn)行隱藏掐暮,對(duì)外界來(lái)說,類就像一個(gè)“黑盒子”政钟。封裝是保證軟件部件具有優(yōu)良的模塊性的基礎(chǔ)路克,封裝的目標(biāo)就是要實(shí)現(xiàn)軟件部件的“高內(nèi)聚樟结、低耦合”,防止程序相互依賴性而帶來(lái)的變動(dòng)影響精算。
本節(jié)的部分內(nèi)容直接參考了菜鳥教程瓢宦。
創(chuàng)建類
Python中創(chuàng)建類的語(yǔ)法非常簡(jiǎn)單:
class <class name >(<parent class name>):
'''statements'''
<method definition-1>
<method definition-n>
空類
空類是最簡(jiǎn)單的類,我們?cè)谶@里創(chuàng)建一個(gè)空類:
class Student():
'''An empty class.'''
pass
Student
<class __main__.Student at 0x7f1e7803c668>
上面這個(gè)類中沒有任何內(nèi)容殖妇,用pass
關(guān)鍵字來(lái)起到一個(gè)占位的作用刁笙,Student后面的括號(hào)也可以省略,第一行三撇號(hào)'''
里面的語(yǔ)句為類的文檔(可選)谦趣。類可以看作是創(chuàng)建實(shí)例的藍(lán)圖疲吸,即使是空類也可以。實(shí)際上前鹅,空類在我們后面做科研數(shù)據(jù)處理的過程中有很重要的應(yīng)用摘悴,它可以作為將要分析的數(shù)據(jù)的一個(gè)容器,我們后面會(huì)講到舰绘。下面我們就用空類Student
來(lái)創(chuàng)建實(shí)例:
student1 = Student()
student2 = Student()
print student1
print student2
<__main__.Student instance at 0x7fe3507474d0>
<__main__.Student instance at 0x7fe3507472d8>
從上面的輸出結(jié)果我們看到所創(chuàng)建的兩個(gè)實(shí)例(instance)蹂喻,而且這兩個(gè)實(shí)例在內(nèi)存中存儲(chǔ)在不同的地址上。
實(shí)例變量
實(shí)例變量是指某個(gè)具體實(shí)例(對(duì)象)特有的變量捂寿。例如口四,針對(duì)上面創(chuàng)建的空類的兩個(gè)實(shí)例,我們可以分別賦予它們一些變量:
# define variables for L_obj1
student1.name = 'da zhuang'
student1.gender = 'male'
# define variables for L_obj2
student2.name = 'xiao hong'
student2.gender = 'female'
student2.age = 20
print student1.__dict__.keys()
print student2.__dict__.keys()
['gender', 'name']
['gender', 'age', 'name']
在上面的例子中秦陋,我們分別為student1
和student2
兩個(gè)實(shí)例定義了變量蔓彩,其中student1
有兩個(gè)變量name
和gender
,而student2
還多了age
變量驳概。上面代碼中的__dict__
是Python類的內(nèi)置屬性赤嚼,它是一個(gè)存儲(chǔ)了類的所有方法或?qū)ο蟮乃袑傩枣I值對(duì)的字典。
雖然上面的例子顯示了Python定義實(shí)例變量的靈活性顺又,但是這種方式?jīng)]有充分利用到面向?qū)ο缶幊痰膬?yōu)勢(shì)更卒。實(shí)際上,我們可以在定義類的時(shí)候給定變量稚照,從而使得所有的實(shí)例都同時(shí)具有這些變量蹂空,我們將在下面介紹。
類的變量和方法
__init__
方法
__init__
方法是類的一種特殊方法锐锣,被陳為類的構(gòu)造函數(shù)或初始化方法腌闯,當(dāng)創(chuàng)建這個(gè)類的實(shí)例時(shí)就會(huì)自動(dòng)調(diào)用該方法。下面我們重新定義一下Student
類雕憔,以演示__init__
方法的使用:
class Student:
'''所有學(xué)生的基類'''
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
student1 = Student('da zhuang', 'male', 21)
student2 = Student('xiao hong', 'famale', 20)
print student1.__dict__.keys()
print student2.__dict__.keys()
['gender', 'age', 'name']
['gender', 'age', 'name']
在上面的代碼中,我們利用__init__
方法定義了類的三個(gè)變量name
糖声,gender
和age
斤彼,這些變量被自動(dòng)應(yīng)用于類的所有實(shí)例分瘦。self
代表類的實(shí)例,是在定義類方法時(shí)必須的琉苇,雖然在調(diào)用時(shí)不必為self
傳入任何參數(shù)嘲玫。
當(dāng)然,與普通的函數(shù)類似并扇,我們也可以為這些變量指定初始值去团,例如:
class Student:
'''所有學(xué)生的基類'''
def __init__(self, name, gender, age=23):
self.name = name
self.gender = gender
self.age = age
student1 = Student('da zhuang', 'male')
print student1.__dict__
{'gender': 'male', 'age': 23, 'name': 'da zhuang'}
自定義方法
類的方法與普通函數(shù)類似,只有一個(gè)特別之處穷蛹,即需要一個(gè)額外的第一個(gè)參數(shù)土陪,就是我們上面講的self
參數(shù)(按慣例使用這個(gè)名稱,但不是必須的肴熏,可以隨意指定其他名稱)鬼雀。例如,我們將上述Student
類加入一個(gè)自定義的方法:
class Student:
'''所有學(xué)生的基類'''
def __init__(self, name, gender, age=23):
self.name = name
self.gender = gender
self.age = age
def report(self):
print "%s is a %d years old %s student." % (self.name.title(), self.age, self.gender)
student1 = Student('da zhuang', 'male', 21)
student2 = Student('xiao hong', 'female', 20)
student1.report()
student2.report()
Da Zhuang is a 21 years old male student.
Xiao Hong is a 20 years old female student.
類的屬性
上面類中定義的類變量即為類的實(shí)例的屬性蛙吏,可以通過點(diǎn)號(hào).
訪問源哩,我們上面的例子中已經(jīng)用到了。這里介紹一下類的內(nèi)置屬性鸦做,一共有一下幾個(gè):
-
__dict__
: 類的屬性(包含一個(gè)字典励烦,由類的數(shù)據(jù)屬性組成) -
__doc__
:類的文檔字符串 -
__name__
: 類名 -
__module__
: 類定義所在的模塊(類的全名是'main.className',如果類位于一個(gè)導(dǎo)入模塊mymod中泼诱,那么className.module 等于 mymod) -
__bases__
: 類的所有父類構(gòu)成元素(包含了一個(gè)由所有父類組成的元組)
例如:
print "Student.__doc__:", Student.__doc__
print "Student.__name__:", Student.__name__
print "Student.__module__:", Student.__module__
print "Student.__bases__:", Student.__bases__
print "Student.__dict__:", Student.__dict__
Student.__doc__: 所有學(xué)生的基類
Student.__name__: Student
Student.__module__: __main__
Student.__bases__: ()
Student.__dict__: {'report': <function report at 0x7f1e70f29aa0>, '__module__': '__main__', '__doc__': '\xe6\x89\x80\xe6\x9c\x89\xe5\xad\xa6\xe7\x94\x9f\xe7\x9a\x84\xe5\x9f\xba\xe7\xb1\xbb', '__init__': <function __init__ at 0x7f1e70f29938>}
注意對(duì)類調(diào)用__dict__
屬性和對(duì)類的實(shí)例調(diào)用__dict__
效果是不一樣的坛掠。對(duì)類調(diào)用時(shí)會(huì)列出類的方法和屬性,但是對(duì)實(shí)例調(diào)用時(shí)坷檩,只會(huì)列出實(shí)例的屬性却音,而不會(huì)列出方法。
print Student.__dict__
print student1.__dict__
{'report': <function report at 0x7f1e70f29aa0>, '__module__': '__main__', '__doc__': '\xe6\x89\x80\xe6\x9c\x89\xe5\xad\xa6\xe7\x94\x9f\xe7\x9a\x84\xe5\x9f\xba\xe7\xb1\xbb', '__init__': <function __init__ at 0x7f1e70f29938>}
{'gender': 'male', 'age': 21, 'name': 'da zhuang'}
私有屬性和方法
類的私有屬性
__private_attrs
:兩個(gè)下劃線開頭矢炼,聲明該屬性為私有系瓢,不能在類的外部被使用或直接訪問。在類內(nèi)部的方法中使用時(shí) self.__private_attrs
句灌。
類的私有方法
__private_method
:兩個(gè)下劃線開頭夷陋,聲明該方法為私有方法,不能在類的外部調(diào)用胰锌。在類的內(nèi)部調(diào)用self.__private_methods
骗绕。
單下劃線、雙下劃線资昧、頭尾雙下劃線說明:
-
__foo__
: 定義的是特殊方法酬土,一般是系統(tǒng)定義名字 ,類似__init__()
之類的格带。 -
_foo
: 以單下劃線開頭的表示的是protected類型的變量撤缴,即保護(hù)類型只能允許其本身與子類進(jìn)行訪問刹枉,不能用于from module import *
-
__foo
: 雙下劃線的表示的是私有類型(private)的變量, 只能是允許這個(gè)類本身進(jìn)行訪問了。
類的繼承
面向?qū)ο蟮木幊處?lái)的主要好處之一是代碼的重用屈呕,實(shí)現(xiàn)這種重用的方法之一是通過繼承機(jī)制微宝。通過繼承創(chuàng)建的新類稱為子類或派生類,被繼承的類稱為基類虎眨、父類或超類蟋软。通過繼承,子類自動(dòng)繼承父類的所有方法和屬性嗽桩。創(chuàng)建子類的語(yǔ)法如下:
class DerivedClassName(BaseCalssName):
<statement-1>
...
...
<statement-N>
單一繼承
只從一個(gè)父類來(lái)繼承岳守,例如:
class Student:
'''所有學(xué)生的基類'''
def __init__(self, name, gender, age=23):
self.name = name
self.gender = gender
self.age = age
def report(self):
print "%s is a %d years old %s student." % (self.name.title(), self.age, self.gender)
class Boy(Student):
'''Boy類繼承自Student類'''
def play(self):
print '%s is playing soccer.' % self.name.title()
boy1 = Boy('da zhuang', 'male', 12)
boy1.report()
boy1.play()
Da Zhuang is a 12 years old male student.
Da Zhuang is playing soccer.
多重繼承
也可以從多個(gè)父類繼承,例如:
class A:
def a_print(self):
print 'This is class A.'
class B:
def b_print(self):
print 'This is class B.'
class C(A, B):
def c_print(self):
print 'This is class C.'
c = C()
c.a_print()
c.b_print()
c.c_print()
This is class A.
This is class B.
This is class C.
方法重寫
如果你的父類方法的功能不能滿足你的需求涤躲,你可以在子類重寫你父類的方法:
class Parent: # 定義父類
def myMethod(self):
print '調(diào)用父類方法'
class Child(Parent): # 定義子類
def myMethod(self):
print '調(diào)用子類方法'
c = Child() # 子類實(shí)例
c.myMethod() # 子類調(diào)用重寫方法
調(diào)用子類方法
運(yùn)算符重載(operator overloading)
運(yùn)算符重載有時(shí)被稱為魔術(shù)方法(magic methods)棺耍,指的是利用一些特殊方法來(lái)改變Python運(yùn)算符的行為。特殊方法由連續(xù)的兩組雙下劃線(__
)包圍种樱,有的人也將其稱為dunder方法蒙袍。例如,運(yùn)算符加號(hào)(+
)嫩挤,對(duì)不同的操作變量有不同的行為:
4 + 6, 'M' + 'R'
(10, 'MR')
從輸出結(jié)果我們看到了加號(hào)運(yùn)算的不同行為:對(duì)于數(shù)字害幅,加號(hào)代表兩數(shù)相加;而對(duì)于字符串岂昭,加號(hào)代表連接兩個(gè)字符串以现。根據(jù)操作對(duì)象的不同,加號(hào)運(yùn)算有著不同的行為约啊,實(shí)際上邑遏,在后臺(tái)運(yùn)行的就是特殊方法。對(duì)兩個(gè)整數(shù)相加恰矩,+
調(diào)用的是int.__add__()
方法记盒;而對(duì)于字符串,則調(diào)用str.__add__()
方法外傅。例如上面代碼也可以寫成:
int.__add__(4, 6), str.__add__('M', 'R')
(10, 'MR')
因此纪吮,我們可以用__add__()
方法自定義加法運(yùn)算。下面我們將前面定義的Student
類的兩個(gè)實(shí)例進(jìn)行相加萎胰,看看會(huì)出現(xiàn)什么結(jié)果碾盟?
class Student:
'''所有學(xué)生的基類'''
def __init__(self, name, gender, age=23):
self.name = name
self.gender = gender
self.age = age
def report(self):
print "%s is a %d years old %s student." % (self.name.title(), self.age, self.gender)
student1 = Student('da zhuang', 'male', 21)
student2 = Student('xiao hong', 'female', 20)
student1 + student2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-44-c9dbee72f74f> in <module>()
12 student2 = Student('xiao hong', 'female', 20)
13
---> 14 student1 + student2
TypeError: unsupported operand type(s) for +: 'instance' and 'instance'
我們看到錯(cuò)誤提示unsupported operand type(s) for+: 'instance' and 'instance'
,即加號(hào)運(yùn)算符不支持兩個(gè)實(shí)例來(lái)進(jìn)行運(yùn)算技竟。這時(shí)冰肴,我們就可以在類中對(duì)加號(hào)運(yùn)算符進(jìn)行重載,以使其支持兩個(gè)實(shí)例的相加。比如嚼沿,我們可以定義兩個(gè)實(shí)例相加的運(yùn)算為兩個(gè)實(shí)例中age
屬性的相加估盘。
class Student:
'''所有學(xué)生的基類'''
def __init__(self, name, gender, age=23):
self.name = name
self.gender = gender
self.age = age
def __add__(self, other):
'''重載加法運(yùn)算'''
return self.age + other.age
def report(self):
print "%s is a %d years old %s student." % (self.name.title(), self.age, self.gender)
student1 = Student('da zhuang', 'male', 21)
student2 = Student('xiao hong', 'female', 20)
student1 + student2
41
從輸出可見瓷患,加號(hào)實(shí)現(xiàn)了對(duì)兩個(gè)實(shí)例的相加骡尽。
以上是關(guān)于類的一些基本內(nèi)容,當(dāng)然類的應(yīng)用還有很多知識(shí)需要學(xué)習(xí)擅编,這是面向?qū)ο缶幊痰暮诵呐氏福绻蠹以谝院蟮氖褂弥杏龅絾栴},可以從網(wǎng)上尋找對(duì)應(yīng)的解決方法爱态。