本章會(huì)(1)討論一些重要的魔法方法,(2)然后會(huì)討論兩個(gè)相關(guān)的主題:屬性和迭代器,(3)最后介紹一個(gè)相關(guān)的示例來處理一些稍微復(fù)雜的問題。
魔法方法或者又稱為特殊方法(個(gè)人挺不習(xí)慣魔法方法的翻譯,叫法搞的跟拍科幻片似的,因此后面統(tǒng)一叫做特殊方法),是指被包含下劃線的方法或者所能調(diào)用到的方法的統(tǒng)稱,這些通常會(huì)在特殊的情況下調(diào)用,并且基本沒有手動(dòng)調(diào)用他們的必要。
9.1 注備工作
Python的老版本和新版本在定義類的時(shí)候會(huì)有些差別,為了統(tǒng)一以新式類的方式來定義類,可以在文件首行添加一行代碼來來解決這個(gè)問題。個(gè)人在2.7的版本上進(jìn)行學(xué)習(xí)暫時(shí)不存在這個(gè)問題,跳過不深究。
9.2 構(gòu)造方法
構(gòu)造方法就是類似于init的初始化方法,差別在于一個(gè)對(duì)象被構(gòu)建好以后會(huì)自動(dòng)調(diào)用構(gòu)造方法。在Python中創(chuàng)建一個(gè)構(gòu)造方法如下(使用_init_):
class FooBar:
def __init__(self):
self.value = 42
9.2.1 重寫一般方法和特殊的構(gòu)造方法
如果類B繼承自類A,類B的某個(gè)方法被調(diào)用的時(shí)候會(huì)首先去B中尋找該方法,如果找不到則會(huì)到父類中去尋找:
class A():
def hello(self):
print "Hello, I am A!"
class B(A):
pass
a = A()
a.hello()
b = B()
b.hello()
$ python test.py
Hello, I am A!
Hello, I am A!
因?yàn)锽類沒有hello方法,所以當(dāng)hello被調(diào)用時(shí),原始信息就被打印出來。現(xiàn)在類B重寫了該方法,再進(jìn)行調(diào)用就能產(chǎn)生不一樣的效果:
class A():
def hello(self):
print "Hello, I am A!"
class B(A):
def hello(self):
print "Hello, I am !"
a = A()
a.hello()
b = B()
b.hello()
$ python test.py
Hello, I am A!
Hello, I am B!
重寫方法對(duì)于類很重要,尤其對(duì)構(gòu)造方法,但重寫父類的構(gòu)造方法需要調(diào)用超類的構(gòu)造方法,否則對(duì)象可能不能被正確的初始化。看如下的例子:
class Bird():
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print 'Aaaah...'
self.hungry = False
else:
print 'No, thanks!'
b = Bird()
b.eat()
b.eat()
$ python test.py
Aaaah...
No, thanks!
鳥類在吃過東西以后,就不再饑餓?,F(xiàn)在考慮添加SingBird,它添加了唱歌的行為:
class SingBird():
def __init__(self):
self.sound = 'Squawk'
def sing(self):
print self.sound
sb = SingBird()
sb.sing()
$ python test.py
Squawk
但是如果直接調(diào)用eat就會(huì)產(chǎn)生問題:
Traceback (most recent call last):
File "test.py", line 20, in <module>
sb.eat()
AttributeError: SingBird instance has no attribute 'eat'
為了解決該問題,必須調(diào)用超類的構(gòu)造方法保證正常的初始化,后面兩節(jié)分別介紹了兩種做法。
9.2.2 調(diào)用未綁定的超類構(gòu)造方法
如下為調(diào)用未綁定的超類構(gòu)造方法,屬于歷史遺留寫法:
class SingBird(Bird):
def __init__(self):
Bird.__init__(self)
self.sound = 'Squawk'
def sing(self):
print self.sound
9.2.3 使用super函數(shù)
使用super函數(shù)的寫法:
__metaclass__ = type#super類只能在新式類中使用
class SingBird(Bird):
def __init__(self):
super(SingBird, self).__init__()
self.sound = 'Squawk'
def sing(self):
print self.sound
9.3 成員訪問
序列和映射是對(duì)象的集合,為了實(shí)現(xiàn)它們的基本行為,我們要實(shí)現(xiàn)一些魔法方法。如果集合中的對(duì)象是不可變的那么實(shí)現(xiàn)兩個(gè)魔方方法即可,否則要實(shí)現(xiàn)四個(gè)魔法方法。
- __len__(self) 如果返回0,該對(duì)象被當(dāng)做布爾變量使用時(shí)是假
- __getitem__(self, key) 如果是序列,key是0~n-1的整數(shù),如果是映射,key可能會(huì)是任何類型。
- __setitem__(self, key, value)
- __delitem__(self, key)
9.3.1 基本的序列和映射規(guī)則
讓我們實(shí)踐一把,創(chuàng)建一個(gè)無窮序列:
def checkKey(key):
if not isinstance(key, (int, long)):
raise TypeError
elif key < 0:
raise IndexError
class InfinitSequence():
def __init__(self, start=0, step=1):
self.start = 0
self.step = 1
self.changed = {}
def __getitem__(self, key):
checkKey(key)
try:
return self.changed[key]
except KeyError:
return self.start + key * self.step
def __setitem__(self, key, value):
checkKey(key)
self.changed[key] = value
sequence = InfinitSequence(1, 2)
print sequence[4] #4
sequence[4] = 2
print sequence[4] #2
print sequence[5] #5
因?yàn)樾蛄惺菬o窮的,所以沒有實(shí)現(xiàn)_len_方法,因此之前上面所說的實(shí)現(xiàn)兩個(gè)或者四個(gè)魔法方法是可選的,甚至有些方法對(duì)于你實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)根本不需要,因此不需要實(shí)現(xiàn)。
9.3.2 子類化列表、字典和字符串
要模擬基本的序列/映射要實(shí)現(xiàn)其他的特殊方法和普通方法,這是一項(xiàng)繁瑣的工作,好在我們有繼承。如下實(shí)現(xiàn)一個(gè)和內(nèi)建列表行為相似的序列,可以使用子類內(nèi)建類型list:
__metaclass__=type
class CounterList(list):
def __init__(self, *args):
super(CounterList, self).__init__(*args)
self.counter = 0
def __getitem__(self, index):
self.counter += 1
return super(CounterList, self).__getitem__(index)
c1 = CounterList(range(10))
print c1 #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
c1.reverse()
print c1 #[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
del c1[3:6]
print c1 #[9, 8, 7, 3, 2, 1, 0]
print c1.counter #0
print c1[4] + c1[2] #9
print c1.counter #2
9.4 更多魔力
還有很多特殊方法不詳述,參考Python參考手冊(cè)。
9.5 屬性
訪問器方法是一個(gè)簡(jiǎn)單的方法,能夠使用類似于getHeight何setHeight這樣的名字綁定一些屬性,看如下例子中的Recantagle類:
__metaclass__=type
class Recantagle(list):
def __init__(self):
self.height = 0
self.width = 0
def getSize(self):
return self.width, self.height
def setSize(self, size):
self.width, self.height = size
r = Recantagle() #(0, 0)
print r.getSize()
r.setSize((200,400))
print r.getSize() #(200, 400)
我們知道方法的實(shí)現(xiàn)是在getSize里面,更好的做法是隱藏訪問方法。Python中有兩種機(jī)制,后面主要討論新式類中使用的property函數(shù),以及類方法和靜態(tài)方法,然后簡(jiǎn)單介紹如何使用特殊方法實(shí)現(xiàn)屬性。
9.5.1 property屬性
property函數(shù)的使用很簡(jiǎn)單,只需要將訪問器函數(shù)用作參數(shù)將結(jié)果賦值給一個(gè)名為size的屬性。可以用同樣的方式處理width和height:
__metaclass__=type
class Recantagle(list):
def __init__(self):
self.height = 0
self.width = 0
def getSize(self):
return self.width, self.height
def setSize(self, size):
self.width, self.height = size
size = property(getSize, setSize)
r = Recantagle()
print size
property的參數(shù)可以用0-4個(gè)(此處只提及,使用查詢文檔),在新式類中應(yīng)該使用property函數(shù)而不是訪問器方法。
9.5.2 靜態(tài)方法和類成員方法
靜態(tài)方法是創(chuàng)建時(shí)被裝入Staticmethod類型的特征,靜態(tài)方法沒有self屬性,能夠被類本身直接調(diào)用。
類成員方法是創(chuàng)建時(shí)被裝入Classmethod類型的特征,類方法在定義時(shí)需要cls(類似于self的參數(shù)),類成員方法可以直接用類的對(duì)象調(diào)用。
請(qǐng)看下面的例子:
__metaclass__=type
class MyClass():
def smeth():
print 'This is static method'
smeth = staticmethod(smeth)
def cmeth(cls):
print 'This is class method of ', cls
cmeth = classmethod(cmeth)
MyClass.cmeth() #This is class method of <class '__main__.MyClass'>
c = MyClass()
c.cmeth() #This is class method of <class '__main__.MyClass'>
MyClass.smeth() #This is static method
也可以用裝飾器來定義靜態(tài)方法和類成員方法:
__metaclass__=type
class MyClass():
@staticmethod
def smeth():
print 'This is static method'
@classmethod
def cmeth(cls):
print 'This is class method of ', cls
MyClass.cmeth() #This is class method of <class '__main__.MyClass'>
c = MyClass()
c.cmeth() #This is class method of <class '__main__.MyClass'>
MyClass.smeth() #This is static method
靜態(tài)方法和類成員方法雖然目前還沒有大量運(yùn)功過,以后可以在工廠函數(shù)中用到。
9.5.3 __getattr__、 __setattr__和它的朋友們
在不使用property屬性之前,如下四個(gè)方法提供了攔截對(duì)對(duì)象特性的功能:
- __getattribute__(self, name) 當(dāng)name被訪問時(shí)自動(dòng)被調(diào)用,只能在新式類中使用
- __getattr__(self, name) 當(dāng)name被訪問時(shí)且對(duì)象沒有相應(yīng)的可直接訪問的特性時(shí)候調(diào)用
- __setattr__(self, name, value) 當(dāng)試圖給特性賦值時(shí)候會(huì)自動(dòng)調(diào)用
- __delattr__(self, name) 但試圖刪除特性name時(shí)自動(dòng)調(diào)用
盡管相對(duì)property使用起來稍微復(fù)雜,但是還是很強(qiáng)大的:
__metaclass__=type
class Recantagle(list):
def __init__(self):
self.height = 0
self.width = 0
def __getattr__(self, name):
if name == 'size':
return self.width, self.height
else:
raise AttributeError
def __setattr__(self, name, value):
if name == 'size':
self.width, self.height = value
else:
self.__dict__[name] = value
r = Recantagle()
print r.size #(0, 0)
r.size = (100, 200)
print r.size #(100, 200)
9.6 迭代器
此處只討論一個(gè)特殊的迭代器方法__iter__,這個(gè)方法是迭代器規(guī)則的基礎(chǔ)。
9.6.1 迭代器的規(guī)則
迭代意思是重復(fù)做一件事情能夠很多次,到目前為止我們使用了for循環(huán)對(duì)序列和字典進(jìn)行迭代,本章介紹的是對(duì)實(shí)現(xiàn)了__iter__方法的對(duì)象進(jìn)行迭代。
__iter__方法會(huì)返回一個(gè)迭代器,迭代器是一個(gè)有next方法的對(duì)象,調(diào)用迭代器的next方法時(shí)會(huì)返回它的下一個(gè)值,如果沒有值返回就會(huì)引發(fā)一個(gè)StopIteration異常。
使用迭代器的好處是什么? 相比較使用列表一次性獲得所有的元素的值占用大量?jī)?nèi)存而言,迭代顯得更加簡(jiǎn)單優(yōu)雅,如下的例子“斐波拉契數(shù)列”是一個(gè)使用列表的例子,否則列表長(zhǎng)度必須無限:
如下是一個(gè)使用迭代去的例子:
__metaclass__=type
class Fibs():
def __init__(self):
self.a = 0
self.b = 1
def next(self):
self.a, self.b = self.b, self.a + self.b
return self.a
def __iter__(self):
return self
fibs = Fibs()
for f in fibs:
if f > 1000:
print f
break
內(nèi)建函數(shù)可以從可迭代對(duì)象中直接獲得迭代器,比如:
>>> it = iter([1,2,3])
>>> it.next()
1
>>> it.next()
2
9.6.2 從迭代器得到序列
可以將迭代器和可迭代對(duì)象轉(zhuǎn)化序列,如下是使用list方法顯式將迭代器轉(zhuǎn)化為列表的例子:
class TestIterator():
value = 0
def next(self):
self.value += 1
if self.value > 10:
raise StopIteration
return self.value
def __iter__(self):
return self
ti = TestIterator()
print list(ti) #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
9.7 生成器
生成器是一種普通的函數(shù)語法定義的迭代器,可以幫助我們寫出很優(yōu)雅的代碼,當(dāng)然編程時(shí)不使用生成器也是可以的。下面先簡(jiǎn)單介紹如何創(chuàng)建和使用生成器,然后再理解下它的機(jī)制。
9.7.1 創(chuàng)建生成器
我們要?jiǎng)?chuàng)建展開嵌套列表并按照順序打印的函數(shù),不適用生成器的時(shí)候代碼大概是這樣的:
nested = [[1,2], [3, 4], [5]]
def flatten(nested):
for sublist in nested:
for element in sublist:
print element
flatten(nested)
現(xiàn)在要把它改寫成生成器的方式:
nested = [[1,2], [3, 4], [5]]
def flatten(nested):
for sublist in nested:
for element in sublist:
yield element
for number in flatten(nested):
print number
唯一的區(qū)別在于yield語句,任何包含yield的語句被稱作生成器,它和普通函數(shù)的差別在于普通函數(shù)直接返回值,而生成器每次生成一個(gè)值后函數(shù)會(huì)被凍結(jié),等到再次被調(diào)用的時(shí)候又會(huì)被激活,被激活后函數(shù)會(huì)從之前被凍結(jié)的點(diǎn)開始執(zhí)行。
9.7.2 遞歸生成器
上面的例子中嵌套有兩層,因此使用兩個(gè)for循環(huán)。通過求助于遞歸,可以遍歷多層級(jí)的嵌套結(jié)構(gòu)(樹):
def flatten(nested):
try:
for sublist in nested:
for element in flatten(sublist):
yield element
except:
yield nested
print list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8])) # [1, 2, 3, 4, 5, 6, 7, 8]
flatten被調(diào)用時(shí),兩種情況:nested為一個(gè)元素,進(jìn)而引發(fā)TypeError異常,生成器繼而生成一個(gè)元素;nested為一個(gè)列表,那么就會(huì)進(jìn)入到第二個(gè)for循環(huán),遍歷flatten(sublist)從而實(shí)現(xiàn)遞歸(這里面運(yùn)用到了深度遍歷的思想)。
有一種情況是需要特別進(jìn)行處理的,如果遍歷的元素中包含了字符串,我們當(dāng)然不希望對(duì)字符串進(jìn)行遍歷。對(duì)一個(gè)元素是否是字符串的最好的檢測(cè)辦法是直接將元素和字符串拼接看是否發(fā)生錯(cuò)誤。因此改寫了的代碼如下所示(頗有點(diǎn)巧妙):
def flatten(nested):
try:
try:
nested + ''
except TypeError:
pass
else:
raise TypeError
for sublist in nested:
for element in flatten(sublist):
yield element
except:
yield nested
print list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8])) # [1, 2, 3, 4, 5, 6, 7, 8]
假定nested是字符串,那么在內(nèi)層try檢測(cè)中一定不會(huì)導(dǎo)致TypeError,因此在else中手動(dòng)誘發(fā)TypeError好交給外層處理;如果不是字符串且不是普通的單個(gè)元素(不然進(jìn)入不到第一個(gè)try子句里),那么會(huì)觸發(fā)內(nèi)層的TypeError,不需要進(jìn)行處理而是直接跳過好讓后面的for中遞歸內(nèi)容執(zhí)行。
9.7.3 通用生成器
生成器是由兩部分組成的:生成器的函數(shù)和生成器的迭代器。用于def語句定義的包含yield部分的就是生成器的函數(shù),生成器的迭代器則是這個(gè)函數(shù)的返回部分。兩個(gè)實(shí)體經(jīng)常被當(dāng)做一個(gè),合作生成器。如下一個(gè)簡(jiǎn)單例子方便理解這兩個(gè)概念:
def simple_generator():
yield 1
print simple_generator #<function simple_generator at 0x1004edaa0>
print simple_generator() #<generator object simple_generator at 0x1004eb140>
9.7.4 生成器方法
從2.5開始Python中引入了屬性能夠?yàn)樯善魈峁┲担@樣生成器就能與外部進(jìn)行交互:
- 外部作用域訪問生成器的send方法,就像使用next一樣,只不過前者需要一個(gè)參數(shù)
- 當(dāng)生成器運(yùn)行時(shí)候,返回通過send發(fā)送的值,如果next方法被使用則放回None
如下是一個(gè)簡(jiǎn)單例子:
def repeater(value):
while True:
new = (yield value)
if new is not None:
value = new
r = repeater(42)
print r.next()
9.7.5 模擬生成器
生成器在比較舊的版本中是不可用的,可以使用代碼來模擬生成器??紤]到目前還沒有使用到生成器的需求,如何模擬也暫且先跳過,有需要再回來翻看。
9.8 八皇后問題
該節(jié)會(huì)使用生成器來解決經(jīng)典的編程問題。
9.8.1 生成器和回溯
略。
9.8.2 問題
簡(jiǎn)述:8*8的棋盤中,放置八個(gè)皇后使得他們相互之間不會(huì)相互威脅(即不在同一條橫線豎線斜線上)。
9.8.3 狀態(tài)表示
八個(gè)皇后的狀態(tài)可以用一個(gè)長(zhǎng)度為8的元組表示,比如state[0]=3表示第1行的皇后放在第4列。
9.8.4 尋找沖突
可以把沖突的判定定義為一個(gè)函數(shù):
def conflict(state, nextX):
nextY = len(state) #2
for i in range(nextY): #2
if abs(nextX - state[i]) in (0, nextY - i):
return True
return False
nextY為下一個(gè)皇后的豎直位置(nextX和nextY的含義理解了就容易了),然后遍歷所有已經(jīng)放置好的皇后,如果新的位置和放置好的皇后位置在同一個(gè)豎直位置上或者斜線上,那么就返回True,否則返回False。
9.8.5 基本情況
這里先把問題簡(jiǎn)化為n皇后,假定前面n-1個(gè)皇后的位置都已經(jīng)找到了,你只需要處理最后一步,你現(xiàn)在需要做的是根據(jù)前面n-1個(gè)皇后的位置找到第n個(gè)皇后所能放置的所有位置。如下用代碼進(jìn)行表示:
def queens(nums, state):
if len(state) == nums - 1:
for pos in range(nums):
if not conflict(state, pos):
yield pos
list(queens(4, [1, 3, 0])) # [2]
/Users/appledev072/Desktop/Screen Shot 2015-01-30 at 2.50.19 PM.png
9.8.6 需要遞歸的情況
def conflict(state, nextX):
nextY = len(state) #2
for i in range(nextY): #2
if abs(nextX - state[i]) in (0, nextY - i):
return True
return False
def queens(nums = 8, state = ()):
for pos in range(nums):
if not conflict(state, pos):
if len(state) == nums - 1:
yield (pos, )
else:
for result in queens(nums, state + (pos,)):
yield (pos,) + result
print list(queens(3)) #[]
print list(queens(4)) #[(1, 3, 0, 2), (2, 0, 3, 1)]
print len(list(queens(8))) #92
內(nèi)層部分相當(dāng)于我們之前所處理的情況,區(qū)別在于現(xiàn)在變成了元組,即該部分會(huì)獲得所有最后一個(gè)皇后的位置并返回給父級(jí)調(diào)用(內(nèi)層for循環(huán)中的queens),并跟后面7皇后的位置結(jié)果進(jìn)行合并。以此類推。
9.8.7 打包
可以將八皇后的結(jié)果輸出的稍微好理解一點(diǎn),這樣也有有助于發(fā)現(xiàn)錯(cuò)誤:
import random
def prettyprint(solution):
def line(pos, length = len(solution)):
return '. ' * (pos) + 'X ' + '. ' * (length - pos - 1)
for pos in solution:
print line(pos)
prettyprint(random.choice(list(queens(8))))
9.9 小結(jié)
- 舊式類和新式類
- 魔法方法
- 構(gòu)造方法
- 重寫
- 序列和映射
- 迭代器
- 生成器
- 八皇后問題
9.9.1 本章的新函數(shù)
- iter(obj)
- property(fget, fset, fdel, doc)
- super(class, obj)
9.9.2 接下來學(xué)習(xí)什么
測(cè)試、擴(kuò)展、打包和一些項(xiàng)目的具體實(shí)現(xiàn)。