1. 程式人生 > >Python學習筆記__7.3章定制類

Python學習筆記__7.3章定制類

編程語言 Python

1、概覽

看到類似__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)

  1. __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錯誤時退出循環。

  1. 以斐波那契數列為例,寫一個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

調用解析

  1. 創建了一個實例Chain()。

  2. Chain().a,類沒有a屬性,調用__getattr__() 方法,將 實例名和屬性名傳進去,返回一個Chain(/a)實例

  3. Chain(/a).b,操作同上,返回一個Chain(/a/b)實例

  4. Chain(/a/b).user("ChenTian"),先會執行getattr返回Chain實例,Chain(/a/b/user("ChenTian"))

然後由於有__call__方法,可以直接對實例調用。此時就會調用__call__方法。傳入的path="ChenTian"。

然後返回Chain(/a/b/user/ChenTian)

  1. Chain(/a/b/user/ChenTian).c.d 操作同第2步


Python學習筆記__7.3章定制類