面向?qū)ο缶幊?/h3>
面向過程編程和面向?qū)ο缶幊蹋?br> 面向過程的程序設(shè)計(jì)把計(jì)算機(jī)程序視為一系列的命令集合,即一組函數(shù)的順序執(zhí)行。為了簡化程序設(shè)計(jì),面向過程把函數(shù)繼續(xù)切分為子函數(shù),即把大塊函數(shù)通過切割成小塊函數(shù)來降低系統(tǒng)的復(fù)雜度。
面向?qū)ο缶幊贪褜ο笞鳛槌绦虻幕締卧粋€對象包含了數(shù)據(jù)和操作數(shù)據(jù)的函數(shù)。
面向?qū)ο蟮某绦蛟O(shè)計(jì)把計(jì)算機(jī)程序視為一組對象的集合,而每個對象都可以接收其他對象發(fā)過來的消息,并處理這些消息,計(jì)算機(jī)程序的執(zhí)行就是一系列消息在各個對象之間傳遞。
在Python中,所有數(shù)據(jù)類型都可以視為對象;當(dāng)然也可以自定義對象,自定義的對象數(shù)據(jù)類型就是面向?qū)ο笾械念怌lass。
面向?qū)ο蟮脑O(shè)計(jì)思想是抽象出類class,根據(jù)class創(chuàng)建實(shí)例instance。
數(shù)據(jù)封裝、繼承和多態(tài)是面向?qū)ο蟮娜筇攸c(diǎn)。
Attribute
數(shù)據(jù):屬性(Property)
操作數(shù)據(jù)的函數(shù):方法(Method)
類和實(shí)例
面向?qū)ο笞钪匾母拍罹褪穷悾–lass)和實(shí)例(Instance),必須牢記類是抽象的模板,比如Student類,而實(shí)例是根據(jù)類創(chuàng)建出來的一個個具體的“對象”,每個對象都擁有相同的方法,但各自的數(shù)據(jù)可能不同。
class Student(object):
pass
class后面緊接著是類名,即Student,類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個類繼承下來的,繼承的概念我們后面再講,通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。
定義好了Student類,就可以根據(jù)Student類創(chuàng)建出Student的實(shí)例,創(chuàng)建實(shí)例是通過類名+()實(shí)現(xiàn)的:
bart = Student()
可以自由地給一個實(shí)例變量綁定屬性,比如,給實(shí)例bart綁定一個name屬性:
bart.name = 'Bart Simpson'
由于類可以起到模板的作用,因此,可以在創(chuàng)建實(shí)例的時候,把一些我們認(rèn)為必須綁定的屬性強(qiáng)制填寫進(jìn)去。通過定義一個特殊的__init__方法,在創(chuàng)建實(shí)例的時候,就把name,score等屬性綁上去:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
注意到__init__方法的第一個參數(shù)永遠(yuǎn)是self,表示創(chuàng)建的實(shí)例本身,因此,在__init__方法內(nèi)部,就可以把各種屬性綁定到self,因?yàn)?code>self就指向創(chuàng)建的實(shí)例本身。
有了__init__方法,在創(chuàng)建實(shí)例的時候,就不能傳入空的參數(shù)了,必須傳入與__init__方法匹配的參數(shù),但self不需要傳,Python解釋器自己會把實(shí)例變量傳進(jìn)去:
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
和普通的函數(shù)相比,在類中定義的函數(shù)只有一點(diǎn)不同,就是第一個參數(shù)永遠(yuǎn)是實(shí)例變量self,并且,調(diào)用時,不用傳遞該參數(shù)。除此之外,類的方法和普通函數(shù)沒有什么區(qū)別,所以,你仍然可以用默認(rèn)參數(shù)、可變參數(shù)、關(guān)鍵字參數(shù)和命名關(guān)鍵字參數(shù)。
數(shù)據(jù)封裝
既然Student實(shí)例本身就擁有這些數(shù)據(jù),要訪問這些數(shù)據(jù),就沒有必要從外面的函數(shù)去訪問,可以直接在Student類的內(nèi)部定義訪問數(shù)據(jù)的函數(shù),這樣,就把“數(shù)據(jù)”給封裝起來了。這些封裝數(shù)據(jù)的函數(shù)是和Student類本身是關(guān)聯(lián)起來的,我們稱之為類的方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
要定義一個方法,除了第一個參數(shù)是self外,其他和普通函數(shù)一樣。要調(diào)用一個方法,只需要在實(shí)例變量上直接調(diào)用,除了self不用傳遞,其他參數(shù)正常傳入:
>>> bart.print_score()
Bart Simpson: 59
和靜態(tài)語言不同,Python允許對實(shí)例變量綁定任何數(shù)據(jù),也就是說,對于兩個實(shí)例變量,雖然它們都是同一個類的不同實(shí)例,但擁有的變量名稱和個數(shù)都可能不同。
訪問限制
__xxx__特殊變量
__xxx私有變量
_xxx公有變量,但提醒你不要隨便使用
在Python中,變量名類似__xxx__的,也就是以雙下劃線開頭,并且以雙下劃線結(jié)尾的,是特殊變量,特殊變量是可以直接訪問的,不是private變量,所以,不能用__name__、__score__這樣的變量名。
如果要讓內(nèi)部屬性不被外部訪問,可以把屬性的名稱前加上兩個下劃線__,在Python中,實(shí)例的變量名如果以__開頭,就變成了一個私有變量(private),只有內(nèi)部可以訪問,外部不能訪問。
有些時候,你會看到以一個下劃線開頭的實(shí)例變量名,比如_name,這樣的實(shí)例變量外部是可以訪問的,但是,按照約定俗成的規(guī)定,當(dāng)你看到這樣的變量時,意思就是,“雖然我可以被訪問,但是,請把我視為私有變量,不要隨意訪問”。
雙下劃線開頭的實(shí)例變量是不是一定不能從外部訪問呢?其實(shí)也不是。不能直接訪問__name是因?yàn)镻ython解釋器對外把__name變量改成了_Student__name,所以,仍然可以通過_Student__name來訪問__name變量,但是強(qiáng)烈建議你不要這么干,因?yàn)椴煌姹镜腜ython解釋器可能會把__name改成不同的變量名。
但是如果外部代碼要獲取name和score怎么辦?可以給Student類增加get_name和get_score這樣的方法:
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
如果又要允許外部代碼修改score怎么辦?可以再給Student類增加set_score方法:
lass Student(object):
...
def set_score(self, score):
self.__score = score
你也許會問,原先那種直接通過bart.score = 99也可以修改啊,為什么要定義一個方法大費(fèi)周折?因?yàn)樵诜椒ㄖ?,可以對參?shù)做檢查,避免傳入無效的參數(shù):
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
繼承和多態(tài)
在OOP程序設(shè)計(jì)中,當(dāng)我們定義一個class的時候,可以從某個現(xiàn)有的class繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。
比如,我們已經(jīng)編寫了一個名為Animal的class,有一個run()方法可以直接打?。?/p>
class Animal(object):
def run(self):
print('Animal is running...')
當(dāng)我們需要編寫Dog和Cat類時,就可以直接從Animal類繼承:
class Dog(Animal):
pass
class Cat(Animal):
pass
對于Dog來說,Animal就是它的父類,對于Animal來說,Dog就是它的子類。Cat和Dog類似。當(dāng)然,也可以對子類增加一些方法,比如Dog類:
class Dog(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
- Aniamal
- Dog
- Cat
繼承
多態(tài)
當(dāng)子類和父類都存在相同的run()方法時,我們說,子類的run()覆蓋了父類的run(),在代碼運(yùn)行的時候,總是會調(diào)用子類的run()。這樣,我們就獲得了繼承的另一個好處,多態(tài)。
當(dāng)我們定義一個class的時候,我們實(shí)際上就定義了一種數(shù)據(jù)類型。我們定義的數(shù)據(jù)類型和Python自帶的數(shù)據(jù)類型,比如str、list、dict沒什么兩樣:
a = list() # a是list類型
b = Animal() # b是Animal類型
c = Dog() # c是Dog類型
在繼承關(guān)系中,如果一個實(shí)例的數(shù)據(jù)類型是某個子類,那它的數(shù)據(jù)類型也可以被看做是父類。但是,反過來就不行。
要理解多態(tài)的好處,我們還需要再編寫一個函數(shù),這個函數(shù)接受一個Animal類型的變量:
def run_twice(animal):
animal.run()
animal.run()
新增一個Animal的子類,不必對run_twice()做任何修改,實(shí)際上,任何依賴Animal作為參數(shù)的函數(shù)或者方法都可以不加修改地正常運(yùn)行,原因就在于多態(tài)。由于Animal類型有run()方法,因此,傳入的任意類型,只要是Animal類或者子類,就會自動調(diào)用實(shí)際類型的run()方法,這就是多態(tài)的意思。
著名的“開閉”原則:
對擴(kuò)展開放:允許新增Animal子類;
對修改封閉:不需要修改依賴Animal類型的run_twice()等函數(shù)。
靜態(tài)語言 vs 動態(tài)語言
對于靜態(tài)語言(例如Java)來說,如果需要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,否則,將無法調(diào)用run()方法。
對于Python這樣的動態(tài)語言來說,則不一定需要傳入Animal類型。我們只需要保證傳入的對象有一個run()方法就可以了:
動態(tài)語言的“鴨子類型”:
一個對象只要“看起來像鴨子,走起路來像鴨子”,那它就可以被看做是鴨子。
獲取對象信息
當(dāng)我們拿到一個對象的引用時,如何知道這個對象是什么類型、有哪些方法呢?
使用type()
基本類型都可以用type()判斷,
如果要判斷一個對象是否是函數(shù)怎么辦?可以使用types模塊中定義的常量。
import types
使用isinstance()
對于class的繼承關(guān)系來說,使用type()就很不方便。我們要判斷class的類型,可以使用isinstance()函數(shù)。
使用dir()
如果要獲得一個對象的所有屬性和方法,可以使用dir()函數(shù),它返回一個包含字符串的list。
僅僅把屬性和方法列出來是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個對象的狀態(tài)。
通過內(nèi)置的一系列函數(shù),我們可以對任意一個Python對象進(jìn)行剖析,拿到其內(nèi)部的數(shù)據(jù)。要注意的是,只有在不知道對象信息的時候,我們才會去獲取對象信息。
實(shí)例屬性和類屬性
由于Python是動態(tài)語言,根據(jù)類創(chuàng)建的實(shí)例可以任意綁定屬性。
給實(shí)例綁定屬性的方法是通過實(shí)例變量,或者通過self變量:
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
但是,如果Student類本身需要綁定一個屬性呢?可以直接在class中定義屬性,這種屬性是類屬性,歸Student類所有:
class Student(object):
name = 'Student'
當(dāng)我們定義了一個類屬性后,這個屬性雖然歸類所有,但類的所有實(shí)例都可以訪問到。來測試一下:
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 創(chuàng)建實(shí)例s
>>> print(s.name) # 打印name屬性,因?yàn)閷?shí)例并沒有name屬性,所以會繼續(xù)查找class的name屬性
Student
>>> print(Student.name) # 打印類的name屬性
Student
>>> s.name = 'Michael' # 給實(shí)例綁定name屬性
>>> print(s.name) # 由于實(shí)例屬性優(yōu)先級比類屬性高,因此,它會屏蔽掉類的name屬性
Michael
>>> print(Student.name) # 但是類屬性并未消失,用Student.name仍然可以訪問
Student
>>> del s.name # 如果刪除實(shí)例的name屬性
>>> print(s.name) # 再次調(diào)用s.name,由于實(shí)例的name屬性沒有找到,類的name屬性就顯示出來了
Student
從上面的例子可以看出,在編寫程序的時候,千萬不要對實(shí)例屬性和類屬性使用相同的名字,相同名稱的實(shí)例屬性將屏蔽掉類屬性。
實(shí)例屬性屬于各個實(shí)例所有,互不干擾;類屬性屬于類所有實(shí)例共享。
面向?qū)ο蟾呒壘幊?/h3>
數(shù)據(jù)封裝、繼承和多態(tài)只是面向?qū)ο蟪绦蛟O(shè)計(jì)中最基礎(chǔ)的3個概念。在Python中,面向?qū)ο筮€有很多高級特性,允許我們寫出非常強(qiáng)大的功能。
我們會討論多重繼承、定制類、元類等概念。
使用__slots__
我們可以給實(shí)例綁定一個屬性,
>>> s = Student()
>>> s.name = 'Michael' # 動態(tài)給實(shí)例綁定一個屬性
>>> print(s.name)
Michael
也可以綁定一個方法,
>>> def set_age(self, age): # 定義一個函數(shù)作為實(shí)例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 給實(shí)例綁定一個方法
>>> s.set_age(25) # 調(diào)用實(shí)例方法
>>> s.age # 測試結(jié)果
25
給一個實(shí)例綁定的方法,對另一個實(shí)例是不起作用的,為了給所有實(shí)例都綁定方法,可以給class綁定方法:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
通常情況下,上面的set_score方法可以直接定義在class中,但動態(tài)綁定允許我們在程序運(yùn)行的過程中動態(tài)給class加上功能,這在靜態(tài)語言中很難實(shí)現(xiàn)。
使用__slots__
為了限制實(shí)例的添加的屬性,Python允許在定義class的時候,定義一個特殊的__slots__變量,來限制該class實(shí)例能添加的屬性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱
__slots__定義的屬性僅對當(dāng)前類實(shí)例起作用,對繼承的子類是不起作用的。
除非在子類中也定義__slots__,這樣,子類實(shí)例允許定義的屬性就是自身的__slots__加上父類的__slots__。
使用@property
在綁定屬性時,如果我們直接把屬性暴露出去,雖然寫起來很簡單,但是,沒辦法檢查參數(shù),
為了限制score的范圍,可以通過一個set_score()方法來設(shè)置成績,再通過一個get_score()來獲取成績,這樣,在set_score()方法里,就可以檢查參數(shù):
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
但是,上面的調(diào)用方法又略顯復(fù)雜,沒有直接用屬性這么直接簡單。
有沒有既能檢查參數(shù),又可以用類似屬性這樣簡單的方式來訪問類的變量呢?對于追求完美的Python程序員來說,這是必須要做到的!
還記得裝飾器(decorator)可以給函數(shù)動態(tài)加上功能嗎?對于類的方法,裝飾器一樣起作用。Python內(nèi)置的@property裝飾器就是負(fù)責(zé)把一個方法變成屬性調(diào)用的:
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@property的實(shí)現(xiàn)比較復(fù)雜,我們先考察如何使用。把一個getter方法變成屬性,只需要加上@property就可以了,此時,@property本身又創(chuàng)建了另一個裝飾器@score.setter,負(fù)責(zé)把一個setter方法變成屬性賦值:
>>> s = Student()
>>> s.score = 60 # OK,實(shí)際轉(zhuǎn)化為s.set_score(60)
>>> s.score # OK,實(shí)際轉(zhuǎn)化為s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
還可以定義只讀屬性,只定義getter方法,不定義setter方法就是一個只讀屬性。
多重繼承
回憶一下Animal類層次的設(shè)計(jì),假設(shè)我們要實(shí)現(xiàn)以下4種動物:
- Dog - 狗
- Bat - 蝙蝠
- Parrot - 鸚鵡
- Ostrich - 鴕鳥
如果按照哺乳動物和鳥類歸類,我們可以設(shè)計(jì)出這樣的類的層次:
但是如果按照“能跑”和“能飛”來歸類,我們就應(yīng)該設(shè)計(jì)出這樣的類的層次:
如果要把上面的兩種分類都包含進(jìn)來,我們就得設(shè)計(jì)更多的層次:
- 哺乳類:能跑的哺乳類,能飛的哺乳類;
- 鳥類:能跑的鳥類,能飛的鳥類。
這么一來,類的層次就復(fù)雜了。如果要再增加“寵物類”和“非寵物類”,這么搞下去,類的數(shù)量會呈指數(shù)增長,很明顯這樣設(shè)計(jì)是不行的。
正確的做法是采用多重繼承。
1 首先,主要的類層次仍按照哺乳類和鳥類設(shè)計(jì):
class Animal(object):
pass
# 大類:
class Mammal(Animal):
pass
class Bird(Animal):
pass
# 各種動物:
class Dog(Mammal):
pass
class Bat(Mammal):
pass
class Parrot(Bird):
pass
class Ostrich(Bird):
pass
2 現(xiàn)在,我們要給動物再加上Runnable和Flyable的功能。先定義好Runnable和Flyable的類:
class Runnable(object):
def run(self):
print('Running...')
class Flyable(object):
def fly(self):
print('Flying...')
對于需要Runnable功能的動物,就多繼承一個Runnable,例如Dog:
對于需要Flyable功能的動物,就多繼承一個Flyable,例如Bat:
class Dog(Mammal, Runnable):
pass
class Bat(Mammal, Flyable):
pass
通過多重繼承,一個子類就可以同時獲得多個父類的所有功能。
MixIn
在設(shè)計(jì)類的繼承關(guān)系時,通常,主線都是單一繼承下來的,例如,Ostrich繼承自Bird。但是,如果需要“混入”額外的功能,通過多重繼承就可以實(shí)現(xiàn),比如,讓Ostrich除了繼承自Bird外,再同時繼承Runnable。這種設(shè)計(jì)通常稱之為MixIn。
為了更好地看出繼承關(guān)系,我們把Runnable和Flyable改為RunnableMixIn和FlyableMixIn。類似的,你還可以定義出肉食動物CarnivorousMixIn和植食動物HerbivoresMixIn,讓某個動物同時擁有好幾個MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass
MixIn的目的就是給一個類增加多個功能,這樣,在設(shè)計(jì)類的時候,我們優(yōu)先考慮通過多重繼承來組合多個MixIn的功能,而不是設(shè)計(jì)多層次的復(fù)雜的繼承關(guān)系。
Python自帶的很多庫也使用了MixIn。舉個例子,Python自帶了TCPServer和UDPServer這兩類網(wǎng)絡(luò)服務(wù),而要同時服務(wù)多個用戶就必須使用多進(jìn)程或多線程模型,這兩種模型由ForkingMixIn和ThreadingMixIn提供。通過組合,我們就可以創(chuàng)造出合適的服務(wù)來。
由于Python允許使用多重繼承,因此,MixIn就是一種常見的設(shè)計(jì)。
只允許單一繼承的語言(如Java)不能使用MixIn的設(shè)計(jì)。
定制類
看到類似__slots__這種形如__xxx__的變量或者函數(shù)名就要注意,這些在Python中是有特殊用途的:
__len__()方法是為了能讓class作用于len()函數(shù);
__slots__用來限制類的屬性;
除此之外,Python的class中還有許多這樣有特殊用途的函數(shù),可以幫助我們定制類。
__str__
print顯示變量調(diào)用 __str__
直接顯示變量調(diào)用__repr__
__iter__
如果一個類想被用于for ... in循環(huán),類似list或tuple那樣,就必須實(shí)現(xiàn)一個__iter__()方法,該方法返回一個迭代對象,然后,Python的for循環(huán)就會不斷調(diào)用該迭代對象的__next__()方法拿到循環(huán)的下一個值,直到遇到StopIteration錯誤時退出循環(huán)。
__getitem__
迭代對象:
下標(biāo)__getitem__
切片
1__getitem__()傳入的參數(shù)可能是一個int,也可能是一個切片對象slice,所以要做判斷;
__getitem__、__setitem__ 和 __delitem__
2沒有對step參數(shù)作處理
3沒有對負(fù)數(shù)作處理
如果把對象看成dict,__getitem__()的參數(shù)也可能是一個可以作key的object,與之對應(yīng)的是__setitem__()方法。
還有一個__delitem__()方法,用于刪除某個元素。
總之,通過上面的方法,我們自己定義的類表現(xiàn)得和Python自帶的list、tuple、dict沒什么區(qū)別,這完全歸功于動態(tài)語言的“鴨子類型”,不需要強(qiáng)制繼承某個接口。
__getattr__
正常情況下,當(dāng)我們調(diào)用類的方法或?qū)傩詴r,如果不存在,就會報錯。要避免這個錯誤,除了可以加上一個score屬性外,Python還有另一個機(jī)制,那就是寫一個__getattr__()方法,動態(tài)返回一個屬性。返回函數(shù)也是完全可以的。
注意,只有在沒有找到屬性的情況下,才調(diào)用__getattr__,已有的屬性,比如name,不會在__getattr__中查找。此外,注意到任意調(diào)用如s.abc都會返回None,這是因?yàn)槲覀兌x的__getattr__默認(rèn)返回就是None。
要讓class只響應(yīng)特定的幾個屬性,我們就要按照約定,拋出AttributeError的錯誤。
__call__
一個對象實(shí)例可以有自己的屬性和方法,當(dāng)我們調(diào)用實(shí)例方法時,我們用instance.method()來調(diào)用。能不能直接在實(shí)例本身上調(diào)用呢?在Python中,答案是肯定的。
任何類,只需要定義一個__call__()方法,就可以直接對實(shí)例進(jìn)行調(diào)用。__call__()還可以定義參數(shù)。
對實(shí)例進(jìn)行直接調(diào)用就好比對一個函數(shù)進(jìn)行調(diào)用一樣,所以你完全可以把對象看成函數(shù),把函數(shù)看成對象,因?yàn)檫@兩者之間本來就沒啥根本的區(qū)別。
如果你把對象看成函數(shù),那么函數(shù)本身其實(shí)也可以在運(yùn)行期動態(tài)創(chuàng)建出來,因?yàn)轭惖膶?shí)例都是運(yùn)行期創(chuàng)建出來的,這么一來,我們就模糊了對象和函數(shù)的界限。
那么,怎么判斷一個變量是對象還是函數(shù)呢?其實(shí),更多的時候,我們需要判斷的是一個對象是否能被調(diào)用。函數(shù)一定能被調(diào)用,對象可能能被調(diào)用。能被調(diào)用的對象就是一個`Callable對象,比如函數(shù)和我們上面定義的帶有call()的類實(shí)例:
使用枚舉類
當(dāng)我們需要定義常量時,一個辦法是用大寫變量通過整數(shù)來定義,例如月份:
JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12
好處是簡單,缺點(diǎn)是類型是int,并且仍然是變量。
更好的方法是為這樣的枚舉類型定義一個class類型,然后,每個常量都是class的一個唯一實(shí)例。
Python提供了Enum類來實(shí)現(xiàn)這個功能:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
這樣我們就獲得了Month類型的枚舉類,可以直接使用Month.Jan來引用一個常量,
rom enum import Enum
Months = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
print(Months.Jan)
Month.Jan
或者枚舉它的所有成員:
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12
value屬性是自動賦給成員的int常量,默認(rèn)從1開始計(jì)數(shù)。
如果需要更精確地控制枚舉類型,可以從Enum派生出自定義類:
from enum import Enum, unique
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被設(shè)定為0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
@unique裝飾器可以幫助我們檢查保證沒有重復(fù)值。
訪問這些枚舉類型可以有若干種方法:
>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(Weekday(1))
Weekday.Mon
>>> print(day1 == Weekday(1))
True
>>> Weekday(7)
Traceback (most recent call last):
...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
... print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
可見,既可以用成員名稱引用枚舉常量,又可以直接根據(jù)value的值獲得枚舉常量。
使用元類
type()
動態(tài)語言和靜態(tài)語言最大的不同,就是函數(shù)和類的定義,不是編譯時定義的,而是運(yùn)行時動態(tài)創(chuàng)建的。
type()函數(shù)可以查看一個類型或變量的類型;
我們說class的定義是運(yùn)行時動態(tài)創(chuàng)建的,而創(chuàng)建class的方法就是使用type()函數(shù)。
type()函數(shù)既可以返回一個對象的類型,又可以創(chuàng)建出新的類型。
要創(chuàng)建一個class對象,type()函數(shù)依次傳入3個參數(shù):
- class的名稱;
- 繼承的父類集合,注意Python支持多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
- class的方法名稱與函數(shù)綁定,這里我們把函數(shù)fn綁定到方法名hello上。
metaclass
除了使用type()動態(tài)創(chuàng)建類以外,要控制類的創(chuàng)建行為,還可以使用metaclass。
metaclass,直譯為元類,簡單的解釋就是:
先定義metaclass,就可以創(chuàng)建類,最后創(chuàng)建實(shí)例。
按照默認(rèn)習(xí)慣,metaclass的類名總是以Metaclass結(jié)尾。
metaclass是類的模板,所以必須從type類型派生。
SDK(Software Development Kit, 軟件開發(fā)工具包)
API(Application Programming Interface, 應(yīng)用程序編程接口)