31. OOP-定制類(lèi)

__str__

我們先定義一個(gè)Student類(lèi),打印一個(gè)實(shí)例:
>>> class Student(object): ... def __init__(self, name): ... self.name = name ...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>

打印出一堆<main.Student object at 0x109afb190>,不好看。怎么才能打印得好看呢?只需要定義好__str__()方法,返回一個(gè)好看的字符串就可以了:
>>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ...
>>> print(Student('Michael')) Student object (name: Michael)

這樣打印出來(lái)的實(shí)例,不但好看,而且容易看出實(shí)例內(nèi)部重要的數(shù)據(jù)。但是細(xì)心的朋友會(huì)發(fā)現(xiàn)直接敲變量不用print,打印出來(lái)的實(shí)例還是不好看:
>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>

這是因?yàn)橹苯语@示變量調(diào)用的不是__str__(),而是__repr__(),兩者的區(qū)別是__str__()返回用戶(hù)看到的字符串,而__repr__()返回程序開(kāi)發(fā)者看到的字符串,也就是說(shuō),__repr__()是為調(diào)試服務(wù)的。解決辦法是再定義一個(gè)__repr__()

__iter__

如果一個(gè)類(lèi)想被用于for ... in循環(huán),類(lèi)似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)。
我們以斐波那契數(shù)列為例,寫(xiě)一個(gè)Fib類(lèi),可以作用于for循環(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 > 100000: # 退出循環(huán)的條件 raise StopIteration(); return self.a # 返回下一個(gè)值

現(xiàn)在,試試把Fib實(shí)例作用于for循環(huán):
>>> for n in Fib(): ... print(n) ... 1 1 2

__getitem__

Fib實(shí)例雖然能作用于for循環(huán),看起來(lái)和list有點(diǎn)像,但是,把它當(dāng)成list來(lái)使用還是不行,比如,取第5個(gè)元素Fib()[5], 要表現(xiàn)得像list那樣按照下標(biāo)取出元素,需要實(shí)現(xiàn)__getitem__()方法:
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a
現(xiàn)在,就可以按下標(biāo)訪(fǎng)問(wèn)數(shù)列的任意一項(xiàng)了

但是list有個(gè)神奇的切片方法對(duì)于Fib卻報(bào)錯(cuò)。原因是__getitem__()傳入的參數(shù)可能是一個(gè)int,也可能是一個(gè)切片對(duì)象slice,所以要做判斷:
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
但是沒(méi)有對(duì)step參數(shù)作處理,也沒(méi)有對(duì)負(fù)數(shù)作處理,所以,要正確實(shí)現(xiàn)一個(gè)__getitem__()還是有很多工作要做的
此外,如果把對(duì)象看成dict,__getitem__()的參數(shù)也可能是一個(gè)可以作key的object,例如str。與之對(duì)應(yīng)的是__setitem__()方法,把對(duì)象視作list或dict來(lái)對(duì)集合賦值。最后,還有一個(gè)__delitem__()方法,用于刪除某個(gè)元素。

總之,通過(guò)上面的方法,我們自己定義的類(lèi)表現(xiàn)得和Python自帶的list、tuple、dict沒(méi)什么區(qū)別,這完全歸功于動(dòng)態(tài)語(yǔ)言的“鴨子類(lèi)型”,不需要強(qiáng)制繼承某個(gè)接口。

__getattr__

正常情況下,當(dāng)我們調(diào)用類(lèi)的方法或?qū)傩詴r(shí),如果不存在,就會(huì)報(bào)錯(cuò)。要避免這個(gè)錯(cuò)誤,除了可以加上一個(gè)屬性外,Python還有另一個(gè)機(jī)制,那就是寫(xiě)一個(gè)__getattr__()方法,動(dòng)態(tài)返回一個(gè)屬性。
class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': return 99
當(dāng)調(diào)用不存在的屬性時(shí),比如score,Python解釋器會(huì)試圖調(diào)用__getattr__(self, 'score')來(lái)嘗試獲得屬性,這樣,我們就有機(jī)會(huì)返回score的值

返回函數(shù)也是完全可以的:
class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25
只是調(diào)用方式要變?yōu)椋?br> >>> s.age() 25

注意,只有在沒(méi)有找到屬性的情況下,才調(diào)用__getattr__,已有的屬性,比如name,不會(huì)在__getattr__中查找。

這實(shí)際上可以把一個(gè)類(lèi)的所有屬性和方法調(diào)用全部動(dòng)態(tài)化處理了,不需要任何特殊手段。這種完全動(dòng)態(tài)調(diào)用的特性有什么實(shí)際作用呢?作用就是,可以針對(duì)完全動(dòng)態(tài)的情況作調(diào)用。

現(xiàn)在很多網(wǎng)站都搞REST API,比如新浪微博、豆瓣啥的,調(diào)用API的URL類(lèi)似:
http://api.server/user/friends
http://api.server/user/timeline/list

如果要寫(xiě)SDK,給每個(gè)URL對(duì)應(yīng)的API都寫(xiě)一個(gè)方法,那得累死,而且,API一旦改動(dòng),SDK也要改。利用完全動(dòng)態(tài)的__getattr__,我們可以寫(xiě)出一個(gè)鏈?zhǔn)秸{(diào)用:
class Chain(object): def __init__(self, path=''): self._path = path def __getattr__(self, path): return Chain('%s/%s' % (self._path, path)) def __str__(self): return self._path __repr__ = __str__
試試:
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

這樣,無(wú)論API怎么變,SDK都可以根據(jù)URL實(shí)現(xiàn)完全動(dòng)態(tài)的調(diào)用,而且,不隨API的增加而改變!

__call__

一個(gè)對(duì)象實(shí)例可以有自己的屬性和方法,當(dāng)我們調(diào)用實(shí)例方法時(shí),我們用instance.method()
來(lái)調(diào)用。能不能直接在實(shí)例本身上調(diào)用呢?在Python中,答案是肯定的。
任何類(lèi),只需要定義一個(gè)__call__()方法,就可以直接對(duì)實(shí)例進(jìn)行調(diào)用。請(qǐng)看示例:
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name)

調(diào)用方式如下:
>>> s = Student('Michael')
>>> s() # self參數(shù)不要傳入
My name is Michael.

__call__()還可以定義參數(shù)。對(duì)實(shí)例進(jìn)行直接調(diào)用就好比對(duì)一個(gè)函數(shù)進(jìn)行調(diào)用一樣,所以你完全可以把對(duì)象看成函數(shù),把函數(shù)看成對(duì)象,因?yàn)檫@兩者之間本來(lái)就沒(méi)啥根本的區(qū)別。

如果你把對(duì)象看成函數(shù),那么函數(shù)本身其實(shí)也可以在運(yùn)行期動(dòng)態(tài)創(chuàng)建出來(lái),因?yàn)轭?lèi)的實(shí)例都是運(yùn)行期創(chuàng)建出來(lái)的,這么一來(lái),我們就模糊了對(duì)象和函數(shù)的界限。
那么,怎么判斷一個(gè)變量是對(duì)象還是函數(shù)呢?其實(shí),更多的時(shí)候,我們需要判斷一個(gè)對(duì)象是否能被調(diào)用,能被調(diào)用的對(duì)象就是一個(gè)Callable對(duì)象,比如函數(shù)和我們上面定義的帶有__call__()的類(lèi)實(shí)例:
>>> callable(Student())
True
>>> callable(max)
True

最后編輯于
?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 基礎(chǔ)1.r''表示''內(nèi)部的字符串默認(rèn)不轉(zhuǎn)義2.'''...'''表示多行內(nèi)容3. 布爾值:True、False(...
    neo已經(jīng)被使用閱讀 1,879評(píng)論 0 5
  • 教程地址:python進(jìn)階 - 慕課網(wǎng) python函數(shù)式編程 變量可以指向函數(shù): f = absprint(f)...
    竹口小生閱讀 675評(píng)論 0 2
  • 要點(diǎn): 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個(gè)“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍?、屬性?..
    victorsungo閱讀 1,695評(píng)論 0 6
  • 好久沒(méi)更新了呀,Python的學(xué)習(xí)可不能落下!?? 面向?qū)ο缶幊?面向?qū)ο笤O(shè)計(jì)思想:抽象出類(lèi)(Class),根據(jù)類(lèi)創(chuàng)...
    納尼2號(hào)閱讀 620評(píng)論 0 6
  • # 第一優(yōu)先級(jí)規(guī)則聲明: # 除了夢(mèng)境,每一個(gè)意識(shí)主進(jìn)程都必須與一個(gè)身體參與的機(jī)械進(jìn)程相匹配,否則結(jié)束意識(shí)主進(jìn)程。...
    李洞BarryLi閱讀 4,196評(píng)論 0 1

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