Python學習筆記__7.3章定制類
看到類似__slots__這種形如__xxx__的變量或者函數名就要註意,這些在Python中是有特殊用途的。
__slots__我們已經知道怎麽用了,__len__()方法我們也知道是為了能讓class作用於len()函數。
除此之外,Python的class中還有許多這樣有特殊用途的函數,可以幫助我們定制類。
1.1、__str__() 和 __repr__()
1、__str__()
修改print(instance) 顯示的值
# 正常打印instance
>>> print (s)
<__main__.Student object at 0x00000005AD1EAAC8>
# 在類中加入__str__() 方法
... def __str__(self):
... return 'Student object (name: %s)' % self.name
# 再次打印
>>> print(s)
Student object (name: Bob)
2、__repr__()
修改 instance 顯示的值
# 正常instance 顯示
>>> s
<__main__.Student object at 0x00000005AD1EAAC8>
# 在類中加入__str__() 方法
... def __repr__(self):
... return 'Student object (name: %s)' % self.name
# 再次打印
>>>s
Student object (name: Bob)
__str__() 和 __repr__() 的區別
__str__()返回用戶看到的字符串,而__repr__()返回程序開發者看到的字符串
通常__str__()和__repr__()代碼都是一樣的。所以可以這樣寫
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
1.2、__iter__()
我們知道,只有 iterable對象可以進行 for…in… 循環。
如果想讓一個類被用於for循環,就需要__iter__() 方法。該方法返回一個iterable 對象。然後,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 # 返回斐波那契數列
# 調用
>>> for n in Fib(): # for 循環打印 Fib() 實例
... print(n)
1.3、__getitem__()
1、__getitem__()方法,可以實現 實例像list 一樣的下標取值
# 類的編寫
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] # 0 就是傳入的參數 n,返回的值是a的值
1
註:
雖然 f 可以像list一樣,進行下標取值,但不能進行切片。
原因是__getitem__()傳入的參數可能是一個int,也可能是一個切片對象slice,所以要做判斷:
2、__getitem__()方法,實現 實例的切片
# 類的編寫
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: # 判斷a值 是否應該加入L
L.append(a)
a, b = b, a + b # a的值一直再變。只是上面if 要判斷是否保存a值
return L
# 調用
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
總結
Fib(),現在可以下標取值 或s切片取值,但切片的處理沒有步長和負數,所以,要正確實現一個__getitem__()還是有很多工作要做的。
此外,如果把對象看成dict,__getitem__()的參數也可能是一個可以作key的object,例如str。
與之對應的是__setitem__()方法,把對象視作list或dict來對集合賦值。最後,還有一個__delitem__()方法,用於刪除某個元素。
1.3、關於切片對象slice的思考
slice一般是跟在list後面的。所以不能通過判斷切片對list的操作,如L[0:5] 這樣的表達式,來判斷slice的數據類型。
在Python中,有一個slice對象,它的類型就是 slice。所以猜想:
上面的代碼中的f[0:5],n==[0:5]。__getitem__(),會把[0:5],解釋成 slice(0,5,none)。從而判斷傳入的參數是 slice。
n是slice的實例,有start,stop屬性。所以通過 start = n.start ,stop = n.stop,來獲取 slice 開始和結束的範圍
1.4、__getattr__()
正常情況下,當我們調用類的方法或屬性時,如果不存在,就會報錯。那麽,我們可以通過__getattr__()方法,動態返回一個屬性。
__getattr__() 方法返回屬性的前提是,在__getattr__()的函數體裏,這個屬性符合你設置的條件
1、動態返回屬性
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
# 我們只定義了name屬性,如果嘗試獲取score屬性,就會交由__getattr__() 方法處理
>>> s.score
99
# 如果請求的屬性不符合__getattr__()方法的判斷條件呢
>>> s.gender # 無顯示。正常會報錯
>>> print(s.gender) # 顯示None,正常會報錯
None
2、動態返回函數
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
# 調用
>>> s.age()
25
3、拋出錯誤
使用__getattr__()方法後,如果 某屬性 class裏沒定義,__getattr__()裏也沒有,是不會拋出錯誤的。針對這些屬性,如果要正常拋出錯誤,需要在__getattr__() 方法裏定義
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
1.5、__call__()
我們調用實例方法時,我們用instance.method()來調用。而定義一個__call__()方法,就可以直接對實例進行調用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
# 調用
>>> s() # self參數不要傳入
My name is Michael.
__call__()還可以定義參數。對實例進行直接調用就好比對一個函數進行調用一樣,所以你完全可以把對象看成函數,把函數看成對象,因為這兩者之間本來就沒啥根本的區別。
callable()函數,可以判斷一個對象是否是“可調用”對象。
>>> callable(max)
True
>>> callable([1, 2, 3])
False
2、例題
1、利用完全動態的__getattr__,寫出一個鏈式調用。完全動態的生成URL
class Chain(object):
def __init__(self,path=""): # 初始化實例,Chain().path 為空
self._path=path
def __getattr__(self,path): # 使用類沒有定義的屬性,就調用
return Chain("%s/%s"%(self._path,path))
def __call__(self,path): # 直接對實例進行調用,將實例當作類似函數一樣調用
return Chain("%s/%s"%(self._path,path))
def __str__(self): # 實例顯示的值
return self._path
__repr__=__str__
# 調用
print(Chain().a.b.user("ChenTian").c.d)
/a/b/user/ChenTian/c/d
調用解析:
創建了一個實例Chain()。
Chain().a,類沒有a屬性,調用__getattr__() 方法,將 實例名和屬性名傳進去,返回一個Chain(/a)實例
Chain(/a).b,操作同上,返回一個Chain(/a/b)實例
Chain(/a/b).user("ChenTian"),先會執行getattr返回Chain實例,Chain(/a/b/user("ChenTian"))
然後由於有__call__方法,可以直接對實例調用。此時就會調用__call__方法。傳入的path="ChenTian"。
然後返回Chain(/a/b/user/ChenTian)
Chain(/a/b/user/ChenTian).c.d 操作同第2步
Python學習筆記__7.3章定制類