Python面向?qū)ο缶幊?二)

本文我們繼續(xù)介紹一些Python面向?qū)ο缶幊讨械母呒?jí)用法绅你,依然參考廖雪峰老師的Python教程。

教程地址:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143186739713011a09b63dcbd42cc87f907a778b3ac73000

1交煞、使用__slots__

正常情況下,當(dāng)我們定義了一個(gè)class,創(chuàng)建了一個(gè)class的實(shí)例后,我們可以給該實(shí)例綁定任何屬性和方法剪返,這就是動(dòng)態(tài)語言的靈活性,看下面的例子:

class Animal():
    pass

ani = Animal()
ani.name = 'hasky'

from types import MethodType
def set_age(self,age):
    self.age = age

ani.set_age = MethodType(set_age,ani)
ani.set_age(20)
print(ani.age) #20 

可以看到邓梅,我們可以直接通過賦值的方式給實(shí)例綁定屬性脱盲,同時(shí)可以通過MethodType方法給實(shí)例綁定方法。值得注意的是日缨,給一個(gè)實(shí)例綁定的屬性和方法钱反,對(duì)另一個(gè)實(shí)例是不起作用的。都會(huì)報(bào)AttributeError。

ani2 = Animal()
print(ani2.name) # AttributeError: 'Animal' object has no attribute 'name'
ani2.set_age(20) # AttributeError: 'Animal' object has no attribute 'set_age'

為了給所有實(shí)例都綁定方法面哥,那么我們給class綁定方法:

Animal.set_age = set_age
ani2.set_age(20)
print(ani2.age) # 20

但是哎壳,如果我們想要限制實(shí)例的屬性怎么辦?比如尚卫,只允許對(duì)Student實(shí)例添加name和age屬性归榕。

為了達(dá)到限制的目的,Python允許在定義class的時(shí)候焕毫,定義一個(gè)特殊的__slots__變量蹲坷,來限制該class實(shí)例能添加的屬性:

class Animal():
    __slots__ = ['name','weight']
    
ani = Animal()
ani.name = 'hasky'
ani.weight = 90
ani.height = 20 # AttributeError: 'Animal' object has no attribute 'height'

可以看到,由于height沒有在__slots__中邑飒,所以無法給實(shí)例綁定該屬性循签,報(bào) AttributeError。

使用__slots__要注意疙咸,__slots__定義的屬性僅對(duì)當(dāng)前類實(shí)例起作用县匠,對(duì)繼承的子類是不起作用的:

class Dog(Animal):
    pass
dog = Dog()
dog.height = 20
print(dog.height) # 20

2、使用@property

在綁定屬性時(shí)撒轮,如果我們直接把屬性暴露出去乞旦,雖然寫起來很簡單,但是题山,沒辦法檢查參數(shù)兰粉,導(dǎo)致可以把屬性值隨便改,為了避免這樣的情況顶瞳,我們可以使用getter和setter來限制對(duì)屬性值的修改玖姑,比如我們將動(dòng)物的體重限制1到200之間的整數(shù)。

class Animal(object):

    def get_weight(self):
         return self.weight

    def set_weight(self, value):
        if not isinstance(value, int):
            raise ValueError('weight must be an integer!')
        if value < 1 or value > 200:
            raise ValueError('weight must between 1 ~ 200!')
        self.weight = value
ani = Animal()
ani.set_weight(60)
print(ani.get_weight()) # 60
ani.set_weight(999) # ValueError: weight must between 1 ~ 200!

但是上面的調(diào)用方法太復(fù)雜慨菱,沒有直接用屬性這么簡單焰络,我們可以使用Python內(nèi)置的@property裝飾器來把一個(gè)方法變成屬性調(diào)用:

class Animal(object):
    @property
    def weight(self):
         return self._weight
        
    @weight.setter
    def weight(self, value):
        if not isinstance(value, int):
            raise ValueError('weight must be an integer!')
        if value < 1 or value > 200:
            raise ValueError('weight must between 1 ~ 200!')
        self._weight = value
        
ani = Animal()
ani.weight = 60
print(ani.weight) # 60
ani.weight = 999 #  ValueError: weight must between 1 ~ 200!

上面的代碼中,注意的一點(diǎn)是符喝,由于我們的方法名已經(jīng)是weight了
闪彼,所以屬性的名稱不能再是weight,我們使用的是_weight协饲。把一個(gè)getter方法變成屬性畏腕,只需要加上@property就可以了,此時(shí)茉稠,@property本身又創(chuàng)建了另一個(gè)裝飾器@weight.setter描馅,負(fù)責(zé)把一個(gè)setter方法變成屬性賦值,于是战惊,我們就擁有一個(gè)可控的屬性操作。

還可以定義只讀屬性,只定義getter方法吞获,不定義setter方法就是一個(gè)只讀屬性:

class Animal(object):
    @property
    def birth(self):
         return self._birth
        
    @birth.setter
    def birth(self, value):
        if not isinstance(value, int):
            raise ValueError('birth must be an integer!')
        if value < 1 or value > 2018:
            raise ValueError('birth must between 1 ~ 2018!')
        self._birth = value
    
    @property
    def age(self):
        return 2018 - self._birth
        
ani = Animal()
ani.birth = 1958
print(ani.age) # 60

3况凉、定制類

看到形如__xxx__的變量或者函數(shù)名就要注意,這些在Python中是有特殊用途的各拷。比如我們剛剛介紹的__slots__用于限制實(shí)例綁定屬性刁绒。除此之外,Python的class中還有許多這樣有特殊用途的函數(shù)烤黍,可以幫助我們定制類知市。

3.1 __str__

對(duì)于一個(gè)實(shí)例,我們直接打印的話速蕊,往往是下面的結(jié)果:

print(ani) # <__main__.Animal object at 0x104fccd68>

我們?nèi)绾伟汛蛴〗Y(jié)果變的好看一些呢嫂丙?__str__就派上用場(chǎng)了。

class Animal(object):
    def __init__(self,name):
        self.name = name
    def __str__(self):
        return 'Animal object with name : %s' % self.name
print(Animal('tt')) # Animal object with name : tt
a = Animal('tt')
a # <__main__.Animal at 0x104e8ecf8>

可是规哲,如果我們不用print而是直接敲變量跟啤,打印出來的結(jié)果還是不好看,這是因?yàn)橹苯语@示變量調(diào)用的不是__str__()唉锌,而是__repr__()隅肥,兩者的區(qū)別是__str__()返回用戶看到的字符串,而__repr__()返回程序開發(fā)者看到的字符串袄简,也就是說腥放,__repr__()是為調(diào)試服務(wù)的。我們只需要在類中增加一行__repr__ =__str__就可以讓直接敲變量打印出來的結(jié)果好看一些绿语。

3.2 __iter__

如果一個(gè)類想被用于for ... in循環(huán)秃症,類似list或tuple那樣,就必須實(shí)現(xiàn)一個(gè)__iter__()方法汞舱,該方法返回一個(gè)迭代對(duì)象伍纫,然后,Python的for循環(huán)就會(huì)不斷調(diào)用該迭代對(duì)象的__next__()方法拿到循環(huán)的下一個(gè)值昂芜,直到遇到StopIteration錯(cuò)誤時(shí)退出循環(huán)莹规。

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化兩個(gè)計(jì)數(shù)器a,b

    def __iter__(self):
        return self # 實(shí)例本身就是迭代對(duì)象泌神,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 計(jì)算下一個(gè)值
        if self.a > 10: # 退出循環(huán)的條件
            raise StopIteration()
        return self.a # 返回下一個(gè)值
for n in Fib():
    print(n)

這樣良漱,我們的輸出為:

3.3 __getitem__

使用__iter__()和__next__()雖然可以讓實(shí)例進(jìn)行循環(huán),但是并不能當(dāng)作一個(gè)list來使用欢际,比如想要獲取其中的某個(gè)元素或者進(jìn)行切片操作母市。要表現(xiàn)得像list那樣按照下標(biāo)取出元素,需要實(shí)現(xiàn)__getitem__()方法损趋。同時(shí)患久,__getitem__()傳入的參數(shù)可能是一個(gè)int,也可能是一個(gè)切片對(duì)象slice,可以獲得一個(gè)指定的元素或者一堆連續(xù)的元素:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L
f = Fib()
print(f[0:5]) # [1, 1, 2, 3, 5]
print(f[10]) # 89

3.4 __getattr__

正常情況下蒋失,當(dāng)我們調(diào)用類的方法或?qū)傩詴r(shí)返帕,如果不存在,就會(huì)報(bào)錯(cuò)篙挽。比如我們?cè)L問一個(gè)不存在的height屬性荆萤,就會(huì)報(bào)AttributeError。解決這個(gè)問題的方法一個(gè)是在類中加入一個(gè)height屬性铣卡,另一個(gè)就是寫一個(gè)__getattr__()方法链韭,動(dòng)態(tài)返回一個(gè)屬性:

class Animal():
    def __init__(self,name,weight):
        self.name = name
        self.weight = weight
    def __getattr__(self,attr):
        if attr == 'height':
            return 20
a = Animal("hasky",90)
print(a.height) # 20

上面的代碼中,當(dāng)調(diào)用不存在的屬性時(shí)height時(shí)煮落,Python解釋器會(huì)試圖調(diào)用__getattr__(self, 'height')來嘗試獲得屬性敞峭,這樣,我們就有機(jī)會(huì)返回height的值州邢。除了返回屬性外儡陨,返回函數(shù)也是可以的:

class Animal():
    def __init__(self,name,weight):
        self.name = name
        self.weight = weight
    def __getattr__(self,attr):
        if attr == 'height':
            return 20
        elif attr == 'age':
            return lambda: 25
a = Animal("hasky",90)
print(a.height) # 20
print(a.age()) # 25

注意,只有在沒有找到屬性的情況下量淌,才調(diào)用__getattr__骗村,已有的屬性,比如name呀枢,不會(huì)在__getattr__中查找胚股。同時(shí),如果定義了__getattr__方法裙秋,那么對(duì)于不存在的屬性琅拌,不會(huì)報(bào)錯(cuò),而是會(huì)統(tǒng)一返回None:

class Animal():
    def __init__(self,name,weight):
        self.name = name
        self.weight = weight
    def __getattr__(self,attr):
        if attr == 'height':
            return 20
        elif attr == 'age':
            return lambda: 25
a = Animal("hasky",90)
print(a.abc) # None

為了摘刑,讓程序拋出AttributeError的錯(cuò)誤进宝,我們需要對(duì)__getattr__()進(jìn)行修改:

class Animal():
    def __init__(self,name,weight):
        self.name = name
        self.weight = weight
    def __getattr__(self,attr):
        if attr == 'height':
            return 20
        elif attr == 'age':
            return lambda: 25
        raise AttributeError('Animal object has no attribute %s' % attr)
a = Animal("hasky",90)
print(a.abc) # AttributeError: Animal object has no attribute abc
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市枷恕,隨后出現(xiàn)的幾起案子党晋,更是在濱河造成了極大的恐慌,老刑警劉巖徐块,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件未玻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡胡控,警方通過查閱死者的電腦和手機(jī)扳剿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昼激,“玉大人庇绽,你說我怎么就攤上這事锡搜。” “怎么了瞧掺?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵余爆,是天一觀的道長。 經(jīng)常有香客問我夸盟,道長,這世上最難降的妖魔是什么像捶? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任上陕,我火速辦了婚禮,結(jié)果婚禮上拓春,老公的妹妹穿的比我還像新娘释簿。我一直安慰自己,他們只是感情好硼莽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布庶溶。 她就那樣靜靜地躺著,像睡著了一般懂鸵。 火紅的嫁衣襯著肌膚如雪偏螺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天匆光,我揣著相機(jī)與錄音套像,去河邊找鬼。 笑死终息,一個(gè)胖子當(dāng)著我的面吹牛夺巩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播周崭,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼柳譬,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了续镇?” 一聲冷哼從身側(cè)響起美澳,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎磨取,沒想到半個(gè)月后人柿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忙厌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年凫岖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逢净。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哥放,死狀恐怖歼指,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甥雕,我是刑警寧澤踩身,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站社露,受9級(jí)特大地震影響挟阻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜峭弟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一附鸽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞒瘸,春花似錦坷备、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俯在,卻和暖如春竟秫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跷乐。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工鸿摇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人劈猿。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓拙吉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親揪荣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子筷黔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345