看到相似的__slots__這種形如__xxx__的變量或者函數名就要注意,這些在Python中是有特殊用途的html
Python中還有許多有特殊用途的函數,能夠幫助咱們定製類python
__str__app
先定義一個Student類,打印一個實例函數
>>> class Student(object): ... def __init__(self,name): ... self.name=name ... >>> print(Student('Zhangsan')) <__main__.Student object at 0x7f8a4830a748>
打印出<__main__.Student object at 0x7f8a4830a748>很差看,不直觀spa
怎麼才能打印的好看呢?只須要定義好__str__()方法,返回一個好看的字符串就能夠了調試
>>> class Student(object): ... def __init__(self,name): ... self.name=name ... def __str__(self): ... return 'Student object(name:%s)' % self.name ... >>> >>> print(Student('Zhangsan')) Student object(name:Zhangsan)
這樣打印出來的實例,不但好看 ,並且容易看出實例內部重要的數據code
可是若是直接在終端敲變量而不用print,打印出來仍是同樣很差看htm
>>> Student('Zhangsan') <__main__.Student object at 0x7f8a4830a8d0>
這是由於直接顯示變量調用的不是__str__()
,而是__repr__()
,二者的區別是__str__()
返回用戶看到的字符串,而__repr__()
返回程序開發者看到的字符串,也就是說,__repr__()
是爲調試服務的。對象
解決辦法是再定義一個__repr__()
。可是一般__str__()
和__repr__()
代碼都是同樣的,因此,有個偷懶的寫法:blog
>>> class Student(object): ... def __init__(self,name): ... self.name=name ... def __str__(self): ... return 'Student object(name:%s)' % self.name ... __repr__=__str__ ... >>> >>> Student('Zhangsan') Student object(name:Zhangsan)
__iter__
若是一個類想被用於for..in循環,相似list或tuple那樣,就必須實現一個__iter__()方法,該方法返回一個迭代對象,而後,Python的for循環就會不斷調用該迭代對象的__next__()方法循環的拿到下一個值,直到遇到StopIteration錯誤退出循環
class Fib(object): def __init__(self): #初始化兩個計數器 self.a,self.b=0,1 def __iter__(self): #實例自己就是迭代對象,返回本身 return self def __next__(self): self.a,self.b=self.b,self.a+self.b #設置循環退出條件 if self.a>10000: raise StopIteration() return self.a for n in Fib(): print(n)
輸出
>>> for n in Fib(): ... print(n) ... 1 1 2 3 5 ... 46368 75025
__grtitem__
Fib實例雖然能做用於for循環,看起來和list有點像,可是,把它當成list來使用仍是不行,好比,取第5個元素
>>> Fib()[5] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Fib' object is not subscriptable
要表現的象list那樣按照下標取出元素,須要實現__getitem__()方法
special_getitem.py
class Fib(object): def __getitem__(self,n): a,b=1,1 for x in range(n): a,b=b,a+b return a
如今,就能夠按下標訪問數列的任意一項了
>>> f[0] 1 >>> f[1] 1 >>> f[100] 573147844013817084101
f[0]至關於把n=0參數傳遞給方法__getitem__(0),由於n=0,for循環不執行,返回a=1,
f[1]至關於把n=1參數傳遞給方法__getitem__(1),由於n=1,for循環執行一次a=b=1,b=a+b=2 返回值爲a=1
f[2]至關於把n=2參數傳遞給方法__geritem__(2),第一次循環,a=b=1,b=a+b=2 再次循環 a=b=2,b=a+b=1+2=3 兩次循環結束返回值a=2
以此類推
可是list有個切片的方法對應Fib確報錯
>>> f[1:5] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in __getitem__ TypeError: 'slice' object cannot be interpreted as an integer
緣由是__getitem__()傳入的參數多是一個int,也能夠是一個切片對象slice,因此要判斷
special_getitem.py
class Fib(object): def __getitem__(self,n): #n是整數 if isinstance(n,int): a,b=1,1 for x in range(n): a,b=b,a+b return a #n是切片相似[0:2] if isinstance(n,slice): 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]) print(f[1]) print(f[2]) print(f[3]) print(f[0:3])
若是傳遞的參數是整數則和上面的方法不變,一次返回一個整數
若是傳遞的參數是切片slice則從切片的start開始至stop結束返回一個列表
運行結果以下
1 1 2 3 [1, 1, 2]
若是傳遞參數是切片,執行過程分析f[0:1]切片取索引爲0及第一個元素的列表
if判斷[0:1]是slice start=n.start 因此start=None stop=n.stop stop=1 if判斷若是start爲None則把start置爲0 定義初始兩位數爲1,1 定義空列表L=[] 執行循環 for x in range(1): x=1執行第一次循環 if判斷1>=1知足條件 執行append語句後L=[1] 接着執行往下語句a,b=b,a+b a=b,b=a+b是同時執行執行完畢後 a=1 b=2 退出for循環返回列表L=[1]
若是傳遞的切片參數爲[0:2]
if判斷[0:2]是slice start=n.start 因此start=None stop=n.stop stop=2 if判斷若是start爲None則把start置爲0 定義初始兩位數爲1,1 定義空列表L=[] 執行循環 for x in range(1): x=0執行第一次循環 if判斷0>=0知足條件 執行append語句後L.append(a) 及L.append(1) L=[1] 接着執行往下語句a,b=b,a+b a=b,b=a+b是同時執行執行完畢後 a=1 b=2 for循環返回列表L=[1] x=1執行第二次循環 if判斷1>=1知足條件 執行append語句L.append(a)及L.append(1) L=[1,1]
執行a,b=b,a+b執行完畢後
a=2 b=3 由於stop=2執行兩次之後退出循環 返回列表L=[1,1]
若是傳遞是切片參數是[0:3]
if判斷[0:3]是slice start=n.start 因此start=None stop=n.stop stop=3 if判斷若是start爲None則把start置爲0 定義初始兩位數爲1,1 定義空列表L=[] 執行循環 for x in range(1): x=0執行第一次循環 if判斷0>=0知足條件 執行append語句後L.append(a) 及L.append(1) L=[1] 接着執行往下語句a,b=b,a+b a=b,b=a+b是同時執行執行完畢後 a=1 b=2 for循環返回列表L=[1] x=1執行第二次循環 if判斷1>=0知足條件 執行append語句L.append(a)及L.append(1) L=[1,1] 執行a,b=b,a+b執行完畢後 a=2 b=3 x=2執行第三次循環 if判斷2>=0知足條件 執行append語句L.append(a)及L.append(2) L=[1,1,2] 由於stop=3執行三次次之後退出循環 返回列表L=[1,1,2]
以此類推
若是start不是 0
for循環會繼續執行,可是尚未到start處由於不知足x>=start條件因此L.append(a)不會執行,不會往列表內追加元素 可是a,b=b,a+b會繼續執行
關於切片及切片函數slice參考:http://www.javashuo.com/article/p-oxwdyocv-ea.html
可是以上仍是有缺陷沒有對step參數作處理
print(f[0:10:2]) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
雖然加了參數:2步數仍是1
也沒有對負數進行處理,因此,要正確實現如下__getitem__()還有不少工做要作
此外,若是把對象當作dict,__getitem__()的參數也多是一個能夠作key的object,例如str。
與之對應的是__setitem__()
方法,把對象視做list或dict來對集合賦值。最後,還有一個__delitem__()
方法,用於刪除某個元素。
__getattr__
正常狀況下,當咱們調用類的方法或屬性時,若是不存在,就會報錯。好比定了Student類
class Student(object): def __init__(self): self.name="Zhangsan"
調用存在的屬性name沒有問題,可是調用不存在的score屬性就有問題了
>>> s.name 'Zhangsan' >>> s.score Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
錯誤信息很清楚地告訴咱們,沒有找到score這個attribute
要避免這個錯誤,除了能夠加上一個score屬性外,python還有另外一個機制,那就是寫一個__getattr__()方法,動態返回一個屬性
special_getattr.py
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 __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': return 99 def __getattr__(self,attr): if attr=='age': return lambda:25 s=Student() print(s.age())
調用方式改成s.age
注意,只有在沒有找到屬性的狀況下,才調用__getattr__,已有的屬性好比name,不會再__getattr__中查找
此外,注意到任意調用如s.abc
都會返回None
,這是由於咱們定義的__getattr__
默認返回就是None
。要讓class只響應特定的幾個屬性,咱們就要按照約定,拋出AttributeError
的錯誤:
>>> s=Student() >>> s.name 'Michael' >>> s.aba >>> print(s.aba) None
改爲以下
def __getattr__(self,attr): if attr=='age': return lambda:25 raise AttributeError('\'Student\' object has no attribute \'%s\'' %attr)
__call__一個對象實例能夠有本身是屬性和方法,當咱們調用實例方法時,咱們用instance.method()來調用,能不能直接在實例自己上調用呢?
任何類,只須要定義一個__call__()方法,就能夠直接對實例進行調用
special_call.py
class Student(object): def __init__(self,name): self.name=name def __call__(self): print('My name is %s.' % self.name)
調用方法以下
>>> s=Student('Zhansan') >>> s() My name is Zhansan.
__call__()
還能夠定義參數。對實例進行直接調用就比如對一個函數進行調用同樣,因此你徹底能夠把對象當作函數,把函數當作對象,由於這二者之間原本就沒啥根本的區別。
若是你把對象當作函數,那麼函數自己其實也能夠在運行期動態建立出來,由於類的實例都是運行期建立出來的,這麼一來,咱們就模糊了對象和函數的界限。
那麼,怎麼判斷一個變量是對象仍是函數呢?其實,更多的時候,咱們須要判斷一個對象是否能被調用,能被調用的對象就是一個Callable
對象,好比函數和咱們上面定義的帶有__call__()
的類實例:
>>> callable(Student('Zhangsan')) True >>> callable(max) True >>> callable([1,2,3]) False >>> callable(None) False >>> callable('str') False
經過callable()
函數,咱們就能夠判斷一個對象是不是「可調用」對象。