Python3之定製類

  看到相似的__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()函數,咱們就能夠判斷一個對象是不是「可調用」對象。

相關文章
相關標籤/搜索