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

采用面向?qū)ο蟮某绦蛟O(shè)計(jì)思想,我們首選思考的不是程序的執(zhí)行流程,而是Student這種數(shù)據(jù)類型應(yīng)該被視為一個(gè)對(duì)象,這個(gè)對(duì)象擁有namescore這兩個(gè)屬性Property)。如果要打印一個(gè)學(xué)生的成績(jī),首先必須創(chuàng)建出這個(gè)學(xué)生對(duì)應(yīng)的對(duì)象,然后,給對(duì)象發(fā)一個(gè)print_score消息,讓對(duì)象自己把自己的數(shù)據(jù)打印出來(lái)。

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))

給對(duì)象發(fā)消息實(shí)際上就是調(diào)用對(duì)象對(duì)應(yīng)的關(guān)聯(lián)函數(shù),我們稱之為對(duì)象的方法(Method)。面向?qū)ο蟮某绦驅(qū)懗鰜?lái)就像這樣:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

類和實(shí)例

面向?qū)ο笞钪匾母拍罹褪?code>類(Class)和實(shí)例(Instance),必須牢記類是抽象的模板,比如Student類,而實(shí)例是根據(jù)類創(chuàng)建出來(lái)的一個(gè)個(gè)具體的“對(duì)象”,每個(gè)對(duì)象都擁有相同的方法,但各自的數(shù)據(jù)可能不同。

仍以Student類為例,在Python中,定義類是通過(guò)class關(guān)鍵字:

class Student(object):
    pass

class后面緊接著是類名,即Student,類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個(gè)類繼承下來(lái)的,繼承的概念我們后面再講,通常,如果沒(méi)有合適的繼承類,就使用object類,這是所有類最終都會(huì)繼承的類。

定義好了Student類,就可以根據(jù)Student類創(chuàng)建出Student的實(shí)例,創(chuàng)建實(shí)例是通過(guò)類名+()實(shí)現(xiàn)的:

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

可以看到,變量bart指向的就是一個(gè)Student的實(shí)例,后面的0x10a67a590是內(nèi)存地址,每個(gè)object的地址都不一樣,而Student本身則是一個(gè)類。

可以自由地給一個(gè)實(shí)例變量綁定屬性,比如,給實(shí)例bart綁定一個(gè)name屬性:

>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

由于類可以起到模板的作用,因此,可以在創(chuàng)建實(shí)例的時(shí)候,把一些我們認(rèn)為必須綁定的屬性強(qiáng)制填寫進(jìn)去。通過(guò)定義一個(gè)特殊的__init__方法,在創(chuàng)建實(shí)例的時(shí)候,就把name,score等屬性綁上去:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

注意到__init__方法的第一個(gè)參數(shù)永遠(yuǎn)是self,表示創(chuàng)建的實(shí)例本身,因此,在__init__方法內(nèi)部,就可以把各種屬性綁定到self,因?yàn)?code>self就指向創(chuàng)建的實(shí)例本身。

有了__init__方法,在創(chuàng)建實(shí)例的時(shí)候,就不能傳入空的參數(shù)了,必須傳入與__init__方法匹配的參數(shù),但self不需要傳,Python解釋器自己會(huì)把實(shí)例變量傳進(jìn)去:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

和普通的函數(shù)相比,在類中定義的函數(shù)只有一點(diǎn)不同,就是第一個(gè)參數(shù)永遠(yuǎn)是實(shí)例變量self,并且,調(diào)用時(shí),不用傳遞該參數(shù)。除此之外,類的方法和普通函數(shù)沒(méi)有什么區(qū)別,所以,你仍然可以用默認(rèn)參數(shù)可變參數(shù)、關(guān)鍵字參數(shù)命名關(guān)鍵字參數(shù)。

  • 數(shù)據(jù)封裝

面向?qū)ο缶幊痰囊粋€(gè)重要特點(diǎn)就是數(shù)據(jù)封裝。在類的內(nèi)部做輸出處理的操作:

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))

在類的內(nèi)部要定義一個(gè)方法,除了第一個(gè)參數(shù)是self外,其他和普通函數(shù)一樣。要調(diào)用一個(gè)方法,只需要在實(shí)例變量上直接調(diào)用,除了self不用傳遞,其他參數(shù)正常傳入:

>>> bart.print_score()
Bart Simpson: 59

這樣一來(lái),我們從外部看Student類,就只需要知道,創(chuàng)建實(shí)例需要給出namescore,而如何打印,都是在Student類的內(nèi)部定義的,這些數(shù)據(jù)和邏輯被“封裝”起來(lái)了,調(diào)用很容易,但卻不用知道內(nèi)部實(shí)現(xiàn)的細(xì)節(jié)。

訪問(wèn)限制

從安全性來(lái)講,外部最好不能修改類內(nèi)部的一些參數(shù).如果要讓內(nèi)部屬性不被外部訪問(wèn),可以把屬性的名稱前加上兩個(gè)下劃線__,在Python中,實(shí)例的變量名如果以__開頭,就變成了一個(gè)私有變量(private),只有內(nèi)部可以訪問(wèn),外部不能訪問(wèn),所以,我們把Student類改一改:

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))

改完后,對(duì)于外部代碼來(lái)說(shuō),沒(méi)什么變動(dòng),但是已經(jīng)無(wú)法從外部訪問(wèn)實(shí)例變量.__name實(shí)例變量.__score了:
這樣就確保了外部代碼不能隨意修改對(duì)象內(nèi)部的狀態(tài),這樣通過(guò)訪問(wèn)限制的保護(hù),代碼更加健壯。
如果想要訪問(wèn)內(nèi)部的參數(shù)怎么辦?
可以給Student類增加get_nameget_score這樣的方法:

#類似OC的get方法
class Student(object):
    ...

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score

如果又要允許外部代碼修改score怎么辦?可以再給Student類增加set_score方法:

#類似OC的set方法
class Student(object):
    ...

    def set_score(self, score):
        self.__score = score

為什么要定義一個(gè)方法大費(fèi)周折?因?yàn)樵诜椒ㄖ校梢詫?duì)參數(shù)做檢查,避免傳入無(wú)效的參數(shù):

class Student(object):
    ...

    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')

繼承和多態(tài)

靜態(tài)語(yǔ)言 vs 動(dòng)態(tài)語(yǔ)言

對(duì)于靜態(tài)語(yǔ)言(例如Java)來(lái)說(shuō),如果需要傳入Animal類型,則傳入的對(duì)象必須是Animal類型或者它的子類,否則,將無(wú)法調(diào)用run()方法。

對(duì)于Python這樣的動(dòng)態(tài)語(yǔ)言來(lái)說(shuō),則不一定需要傳入Animal類型。我們只需要保證傳入的對(duì)象有一個(gè)run()方法就可以了:

class Animal(object):
    def run(self):
        print('Animal is running...')
class tiwce(object):
    def run(self):
        print('123')
def run_twice(animal):#靜態(tài)語(yǔ)言必須傳入Animal的類型或者它的子類型,但是動(dòng)態(tài)語(yǔ)言傳入有run()方法的類型就可以
    animal.run()
    animal.run()

run_twice(tiwce())

這就是動(dòng)態(tài)語(yǔ)言的“鴨子類型”,它并不要求嚴(yán)格的繼承體系,一個(gè)對(duì)象只要“看起來(lái)像鴨子,走起路來(lái)像鴨子”,那它就可以被看做是鴨子。

獲取對(duì)象信息

當(dāng)我們拿到一個(gè)對(duì)象的引用時(shí),如何知道這個(gè)對(duì)象是什么類型、有哪些方法呢?

  • 使用type()
    首先,我們來(lái)判斷對(duì)象類型,使用type()函數(shù):
    基本類型都可以用type()判斷:
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>
  • 使用isinstance()
    對(duì)于class的繼承關(guān)系來(lái)說(shuō),使用type()就很不方便。我們要判斷class的類型,可以使用isinstance()函數(shù)。
object -> Animal -> Dog -> Husky

那么,isinstance()就可以告訴我們,一個(gè)對(duì)象是否是某種類型。先創(chuàng)建3種類型的對(duì)象:

>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
>>> isinstance(h, Husky)
True
>>> isinstance(h, Dog)
True
>>> isinstance(h, Animal)
True
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
  • 使用dir()

如果要獲得一個(gè)對(duì)象的所有屬性和方法,可以使用dir()函數(shù),它返回一個(gè)包含字符串的list,比如,獲得一個(gè)str對(duì)象的所有屬性和方法:

>>> dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

僅僅把屬性和方法列出來(lái)是不夠的,配合getattr()、setattr()以及hasattr(),我們可以直接操作一個(gè)對(duì)象的狀態(tài):

>>> class MyObject(object):
...     def __init__(self):
...         self.x = 9
...     def power(self):
...         return self.x * self.x
...
>>> obj = MyObject()

緊接著,可以測(cè)試該對(duì)象的屬性:

>>> hasattr(obj, 'x') # 有屬性'x'嗎?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
False
>>> setattr(obj, 'y', 19) # 設(shè)置一個(gè)屬性'y'
>>> hasattr(obj, 'y') # 有屬性'y'嗎?
True
>>> getattr(obj, 'y') # 獲取屬性'y'
19
>>> obj.y # 獲取屬性'y'
19

如果試圖獲取不存在的屬性,會(huì)拋出AttributeError的錯(cuò)誤:

>>> getattr(obj, 'z') # 獲取屬性'z'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'

可以傳入一個(gè)default參數(shù),如果屬性不存在,就返回默認(rèn)值:

>>> getattr(obj, 'z', 404) # 獲取屬性'z',如果不存在,返回默認(rèn)值404
404

也可以獲得對(duì)象的方法:

>>> hasattr(obj, 'power') # 有屬性'power'嗎?
True
>>> getattr(obj, 'power') # 獲取屬性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 獲取屬性'power'并賦值到變量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 調(diào)用fn()與調(diào)用obj.power()是一樣的
81

要注意的是,只有在不知道對(duì)象信息的時(shí)候,我們才會(huì)去獲取對(duì)象信息。如果知道就直接寫:

sum = obj.x + obj.y

一個(gè)正確的用法的例子如下:

def readImage(fp):
    if hasattr(fp, 'read'):
        return readData(fp)
    return None

假設(shè)我們希望從文件流fp中讀取圖像,我們首先要判斷該fp對(duì)象是否存在read方法,如果存在,則該對(duì)象是一個(gè)流,如果不存在,則無(wú)法讀取。hasattr()就派上了用場(chǎng)。

實(shí)例屬性和類屬性

給實(shí)例綁定屬性的方法是通過(guò)實(shí)例變量,或者通過(guò)self變量:

class Student(object):
    def __init__(self, name):
        self.name = name
s = Student('Bob')
s.score = 90

但是,如果Student類本身需要綁定一個(gè)屬性呢?可以直接在class中定義屬性,這種屬性是類屬性,歸Student類所有:

class Student(object):
    name = 'Student'

當(dāng)我們定義了一個(gè)類屬性后,這個(gè)屬性雖然歸類所有,但類的所有實(shí)例都可以訪問(wèn)到。來(lái)測(cè)試一下

>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 創(chuàng)建實(shí)例s
>>> print(s.name) # 打印name屬性,因?yàn)閷?shí)例并沒(méi)有name屬性,所以會(huì)繼續(xù)查找class的name屬性
Student
>>> print(Student.name) # 打印類的name屬性
Student
>>> s.name = 'Michael' # 給實(shí)例綁定name屬性
>>> print(s.name) # 由于實(shí)例屬性優(yōu)先級(jí)比類屬性高,因此,它會(huì)屏蔽掉類的name屬性
Michael
>>> print(Student.name) # 但是類屬性并未消失,用Student.name仍然可以訪問(wèn)
Student
>>> del s.name # 如果刪除實(shí)例的name屬性
>>> print(s.name) # 再次調(diào)用s.name,由于實(shí)例的name屬性沒(méi)有找到,類的name屬性就顯示出來(lái)了
Student

從上面的例子可以看出,在編寫程序的時(shí)候,千萬(wàn)不要把實(shí)例屬性和類屬性使用相同的名字,因?yàn)橄嗤Q的實(shí)例屬性將屏蔽掉類屬性,但是當(dāng)你刪除實(shí)例屬性后,再使用相同的名稱,訪問(wèn)到的將是類屬性。

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

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

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