看到相似__slots__
這種形如__xxx__
的變量或者函數名就要注意,這些在Python中是有特殊用途的。html
__slots__
咱們已經知道怎麼用了,__len__()
方法咱們也知道是爲了能讓class做用於len()
函數。python
除此以外,Python的class中還有許多這樣有特殊用途的函數,能夠幫助咱們定製類。api
咱們先定義一個Student
類,打印一個實例:app
>>> class Student(object): ... def __init__(self, name): ... self.name = name ... >>> print Student('Michael') <__main__.Student object at 0x109afb190>
打印出一堆<__main__.Student object at 0x109afb190>
,很差看。函數
怎麼才能打印得好看呢?只須要定義好__str__()
方法,返回一個好看的字符串就能夠了:網站
>>> 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)
這樣打印出來的實例,不但好看,並且容易看出實例內部重要的數據。調試
可是細心的朋友會發現直接敲變量不用print
,打印出來的實例仍是很差看:code
>>> s = Student('Michael') >>> s <__main__.Student object at 0x109afb310>
這是由於直接顯示變量調用的不是__str__()
,而是__repr__()
,二者的區別是__str__()
返回用戶看到的字符串,而__repr__()
返回程序開發者看到的字符串,也就是說,__repr__()
是爲調試服務的。server
解決辦法是再定義一個__repr__()
。可是一般__str__()
和__repr__()
代碼都是同樣的,因此,有個偷懶的寫法:htm
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__
若是一個類想被用於for ... in
循環,相似list或tuple那樣,就必須實現一個__iter__()
方法,該方法返回一個迭代對象,而後,Python的for循環就會不斷調用該迭代對象的next()
方法拿到循環的下一個值,直到遇到StopIteration錯誤時退出循環。
咱們以斐波那契數列爲例,寫一個Fib類,能夠做用於for循環:
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化兩個計數器a,b def __iter__(self): return self # 實例自己就是迭代對象,故返回本身 def next(self): self.a, self.b = self.b, self.a + self.b # 計算下一個值 if self.a > 100000: # 退出循環的條件 raise StopIteration(); return self.a # 返回下一個值
如今,試試把Fib實例做用於for循環:
>>> for n in Fib(): ... print n ... 1 1 2 3 5 ... 46368 75025
Fib實例雖然能做用於for循環,看起來和list有點像,可是,把它當成list來使用仍是不行,好比,取第5個元素:
>>> Fib()[5] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Fib' object does not support indexing
要表現得像list那樣按照下標取出元素,須要實現__getitem__()
方法:
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a
如今,就能夠按下標訪問數列的任意一項了:
>>> f = Fib() >>> f[0] 1 >>> f[1] 1 >>> f[2] 2 >>> f[3] 3 >>> f[10] 89 >>> f[100] 573147844013817084101
可是list有個神奇的切片方法:
>>> range(100)[5:10] [5, 6, 7, 8, 9]
對於Fib卻報錯。緣由是__getitem__()
傳入的參數多是一個int,也多是一個切片對象slice
,因此要作判斷:
class Fib(object): def __getitem__(self, n): if isinstance(n, int): a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): start = n.start stop = n.stop a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a + b return L
如今試試Fib的切片:
>>> f = Fib() >>> f[0:5] [1, 1, 2, 3, 5] >>> f[:10] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
可是沒有對step參數做處理:
>>> f[:10:2] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
也沒有對負數做處理,因此,要正確實現一個__getitem__()
仍是有不少工做要作的。
此外,若是把對象當作dict
,__getitem__()
的參數也多是一個能夠做key的object,例如str
。
與之對應的是__setitem__()
方法,把對象視做list或dict來對集合賦值。最後,還有一個__delitem__()
方法,用於刪除某個元素。
總之,經過上面的方法,咱們本身定義的類表現得和Python自帶的list、tuple、dict沒什麼區別,這徹底歸功於動態語言的「鴨子類型」,不須要強制繼承某個接口。
正常狀況下,當咱們調用類的方法或屬性時,若是不存在,就會報錯。好比定義Student
類:
class Student(object): def __init__(self): self.name = 'Michael'
調用name
屬性,沒問題,可是,調用不存在的score
屬性,就有問題了:
>>> s = Student() >>> print s.name Michael >>> print s.score Traceback (most recent call last): ... AttributeError: 'Student' object has no attribute 'score'
錯誤信息很清楚地告訴咱們,沒有找到score
這個attribute。
要避免這個錯誤,除了能夠加上一個score
屬性外,Python還有另外一個機制,那就是寫一個__getattr__()
方法,動態返回一個屬性。修改以下:
class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': return 99
當調用不存在的屬性時,好比score
,Python解釋器會試圖調用__getattr__(self, 'score')
來嘗試得到屬性,這樣,咱們就有機會返回score
的值:
>>> s = Student() >>> s.name 'Michael' >>> s.score 99
返回函數也是徹底能夠的:
class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25
只是調用方式要變爲:
>>> s.age() 25
注意,只有在沒有找到屬性的狀況下,才調用__getattr__
,已有的屬性,好比name
,不會在__getattr__
中查找。
此外,注意到任意調用如s.abc
都會返回None
,這是由於咱們定義的__getattr__
默認返回就是None
。要讓class只響應特定的幾個屬性,咱們就要按照約定,拋出AttributeError
的錯誤:
class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
這實際上能夠把一個類的全部屬性和方法調用所有動態化處理了,不須要任何特殊手段。
這種徹底動態調用的特性有什麼實際做用呢?做用就是,能夠針對徹底動態的狀況做調用。
舉個例子:
如今不少網站都搞REST API,好比新浪微博、豆瓣啥的,調用API的URL相似:
http://api.server/user/friends http://api.server/user/timeline/list
若是要寫SDK,給每一個URL對應的API都寫一個方法,那得累死,並且,API一旦改動,SDK也要改。
利用徹底動態的__getattr__
,咱們能夠寫出一個鏈式調用:
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
試試:
>>> Chain().status.user.timeline.list '/status/user/timeline/list'
這樣,不管API怎麼變,SDK均可以根據URL實現徹底動態的調用,並且,不隨API的增長而改變!
還有些REST API會把參數放到URL中,好比GitHub的API:
GET /users/:user/repos
調用時,須要把:user
替換爲實際用戶名。若是咱們能寫出這樣的鏈式調用:
Chain().users('michael').repos
就能夠很是方便地調用API了。有興趣的童鞋能夠試試寫出來。
一個對象實例能夠有本身的屬性和方法,當咱們調用實例方法時,咱們用instance.method()
來調用。能不能直接在實例自己上調用呢?相似instance()
?在Python中,答案是確定的。
任何類,只須要定義一個__call__()
方法,就能夠直接對實例進行調用。請看示例:
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name)
調用方式以下:
>>> s = Student('Michael') >>> s() My name is Michael.
__call__()
還能夠定義參數。對實例進行直接調用就比如對一個函數進行調用同樣,因此你徹底能夠把對象當作函數,把函數當作對象,由於這二者之間原本就沒啥根本的區別。
若是你把對象當作函數,那麼函數自己其實也能夠在運行期動態建立出來,由於類的實例都是運行期建立出來的,這麼一來,咱們就模糊了對象和函數的界限。
那麼,怎麼判斷一個變量是對象仍是函數呢?其實,更多的時候,咱們須要判斷一個對象是否能被調用,能被調用的對象就是一個Callable
對象,好比函數和咱們上面定義的帶有__call()__
的類實例:
>>> callable(Student()) True >>> callable(max) True >>> callable([1, 2, 3]) False >>> callable(None) False >>> callable('string') False
經過callable()
函數,咱們就能夠判斷一個對象是不是「可調用」對象。
Python的class容許定義許多定製方法,可讓咱們很是方便地生成特定的類。
本節介紹的是最經常使用的幾個定製方法,還有不少可定製的方法,請參考Python的官方文檔。