面向?qū)ο缶幊?/h2>

面向?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_nameget_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)我們需要編寫DogCat類時,就可以直接從Animal類繼承:

class Dog(Animal):
    pass

class Cat(Animal):
    pass

對于Dog來說,Animal就是它的父類,對于Animal來說,Dog就是它的子類。CatDog類似。當(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)在,我們要給動物再加上RunnableFlyable的功能。先定義好RunnableFlyable的類:

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)系,我們把RunnableFlyable改為RunnableMixInFlyableMixIn。類似的,你還可以定義出肉食動物CarnivorousMixIn和植食動物HerbivoresMixIn,讓某個動物同時擁有好幾個MixIn:

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass

MixIn的目的就是給一個類增加多個功能,這樣,在設(shè)計(jì)類的時候,我們優(yōu)先考慮通過多重繼承來組合多個MixIn的功能,而不是設(shè)計(jì)多層次的復(fù)雜的繼承關(guān)系。
Python自帶的很多庫也使用了MixIn。舉個例子,Python自帶了TCPServerUDPServer這兩類網(wǎng)絡(luò)服務(wù),而要同時服務(wù)多個用戶就必須使用多進(jìn)程或多線程模型,這兩種模型由ForkingMixInThreadingMixIn提供。通過組合,我們就可以創(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)用程序編程接口)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容