本章會(1)討論一些重要的魔法方法,(2)然后會討論兩個相關(guān)的主題:屬性和迭代器柿赊,(3)最后介紹一個相關(guān)的示例來處理一些稍微復(fù)雜的問題俩功。
魔法方法或者又稱為特殊方法(個人挺不習(xí)慣魔法方法的翻譯,叫法搞的跟拍科幻片似的碰声,因此后面統(tǒng)一叫做特殊方法)诡蜓,是指被包含下劃線的方法或者所能調(diào)用到的方法的統(tǒng)稱,這些通常會在特殊的情況下調(diào)用胰挑,并且基本沒有手動調(diào)用他們的必要蔓罚。
9.1 注備工作
Python的老版本和新版本在定義類的時候會有些差別椿肩,為了統(tǒng)一以新式類的方式來定義類,可以在文件首行添加一行代碼來來解決這個問題豺谈。個人在2.7的版本上進行學(xué)習(xí)暫時不存在這個問題郑象,跳過不深究。
9.2 構(gòu)造方法
構(gòu)造方法就是類似于init的初始化方法茬末,差別在于一個對象被構(gòu)建好以后會自動調(diào)用構(gòu)造方法厂榛。在Python中創(chuàng)建一個構(gòu)造方法如下(使用_init_):
class FooBar:
def __init__(self):
self.value = 42
9.2.1 重寫一般方法和特殊的構(gòu)造方法
如果類B繼承自類A,類B的某個方法被調(diào)用的時候會首先去B中尋找該方法丽惭,如果找不到則會到父類中去尋找:
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!
因為B類沒有hello方法击奶,所以當hello被調(diào)用時,原始信息就被打印出來≡鹛停現(xiàn)在類B重寫了該方法柜砾,再進行調(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!
重寫方法對于類很重要,尤其對構(gòu)造方法拷橘,但重寫父類的構(gòu)造方法需要調(diào)用超類的構(gòu)造方法局义,否則對象可能不能被正確的初始化∪叽看如下的例子:
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!
鳥類在吃過東西以后萄唇,就不再饑餓。現(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
就會產(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 成員訪問
序列和映射是對象的集合,為了實現(xiàn)它們的基本行為拔妥,我們要實現(xiàn)一些魔法方法忿危。如果集合中的對象是不可變的那么實現(xiàn)兩個魔方方法即可,否則要實現(xiàn)四個魔法方法没龙。
- __len__(self) 如果返回0铺厨,該對象被當做布爾變量使用時是假
- __getitem__(self, key) 如果是序列,key是0~n-1的整數(shù)硬纤,如果是映射解滓,key可能會是任何類型。
- __setitem__(self, key, value)
- __delitem__(self, key)
9.3.1 基本的序列和映射規(guī)則
讓我們實踐一把筝家,創(chuàng)建一個無窮序列:
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
因為序列是無窮的洼裤,所以沒有實現(xiàn)_len_方法,因此之前上面所說的實現(xiàn)兩個或者四個魔法方法是可選的溪王,甚至有些方法對于你實現(xiàn)數(shù)據(jù)結(jié)構(gòu)根本不需要腮鞍,因此不需要實現(xiàn)值骇。
9.3.2 子類化列表、字典和字符串
要模擬基本的序列/映射要實現(xiàn)其他的特殊方法和普通方法缕减,這是一項繁瑣的工作雷客,好在我們有繼承芒珠。如下實現(xiàn)一個和內(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參考手冊皱卓。
9.5 屬性
訪問器方法是一個簡單的方法裹芝,能夠使用類似于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)
我們知道方法的實現(xiàn)是在getSize里面娜汁,更好的做法是隱藏訪問方法嫂易。Python中有兩種機制,后面主要討論新式類中使用的property函數(shù)掐禁,以及類方法和靜態(tài)方法怜械,然后簡單介紹如何使用特殊方法實現(xiàn)屬性。
9.5.1 property屬性
property函數(shù)的使用很簡單傅事,只需要將訪問器函數(shù)用作參數(shù)將結(jié)果賦值給一個名為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個(此處只提及蹭越,使用查詢文檔)障本,在新式類中應(yīng)該使用property函數(shù)而不是訪問器方法。
9.5.2 靜態(tài)方法和類成員方法
靜態(tài)方法是創(chuàng)建時被裝入Staticmethod類型的特征响鹃,靜態(tài)方法沒有self屬性驾霜,能夠被類本身直接調(diào)用。
類成員方法是創(chuàng)建時被裝入Classmethod類型的特征买置,類方法在定義時需要cls(類似于self的參數(shù))粪糙,類成員方法可以直接用類的對象調(diào)用。
請看下面的例子:
__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)方法和類成員方法雖然目前還沒有大量運功過忿项,以后可以在工廠函數(shù)中用到蓉冈。
9.5.3 __getattr__、 __setattr__和它的朋友們
在不使用property屬性之前倦卖,如下四個方法提供了攔截對對象特性的功能:
- __getattribute__(self, name) 當name被訪問時自動被調(diào)用洒擦,只能在新式類中使用
- __getattr__(self, name) 當name被訪問時且對象沒有相應(yīng)的可直接訪問的特性時候調(diào)用
- __setattr__(self, name, value) 當試圖給特性賦值時候會自動調(diào)用
- __delattr__(self, name) 但試圖刪除特性name時自動調(diào)用
盡管相對property使用起來稍微復(fù)雜,但是還是很強大的:
__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 迭代器
此處只討論一個特殊的迭代器方法__iter__怕膛,這個方法是迭代器規(guī)則的基礎(chǔ)熟嫩。
9.6.1 迭代器的規(guī)則
迭代意思是重復(fù)做一件事情能夠很多次,到目前為止我們使用了for循環(huán)對序列和字典進行迭代褐捻,本章介紹的是對實現(xiàn)了__iter__方法的對象進行迭代掸茅。
__iter__方法會返回一個迭代器椅邓,迭代器是一個有next方法的對象,調(diào)用迭代器的next方法時會返回它的下一個值昧狮,如果沒有值返回就會引發(fā)一個StopIteration異常景馁。
使用迭代器的好處是什么? 相比較使用列表一次性獲得所有的元素的值占用大量內(nèi)存而言,迭代顯得更加簡單優(yōu)雅逗鸣,如下的例子“斐波拉契數(shù)列”是一個使用列表的例子合住,否則列表長度必須無限:
如下是一個使用迭代去的例子:
__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ù)可以從可迭代對象中直接獲得迭代器,比如:
>>> it = iter([1,2,3])
>>> it.next()
1
>>> it.next()
2
9.6.2 從迭代器得到序列
可以將迭代器和可迭代對象轉(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)雅的代碼,當然編程時不使用生成器也是可以的卿樱。下面先簡單介紹如何創(chuàng)建和使用生成器僚害,然后再理解下它的機制。
9.7.1 創(chuàng)建生成器
我們要創(chuàng)建展開嵌套列表并按照順序打印的函數(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ù)直接返回值蹄胰,而生成器每次生成一個值后函數(shù)會被凍結(jié)岳遥,等到再次被調(diào)用的時候又會被激活,被激活后函數(shù)會從之前被凍結(jié)的點開始執(zhí)行烤送。
9.7.2 遞歸生成器
上面的例子中嵌套有兩層寒随,因此使用兩個for循環(huán)。通過求助于遞歸帮坚,可以遍歷多層級的嵌套結(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)用時妻往,兩種情況:nested為一個元素,進而引發(fā)TypeError異常试和,生成器繼而生成一個元素讯泣;nested為一個列表,那么就會進入到第二個for循環(huán)阅悍,遍歷flatten(sublist)從而實現(xiàn)遞歸(這里面運用到了深度遍歷的思想)好渠。
有一種情況是需要特別進行處理的,如果遍歷的元素中包含了字符串节视,我們當然不希望對字符串進行遍歷拳锚。對一個元素是否是字符串的最好的檢測辦法是直接將元素和字符串拼接看是否發(fā)生錯誤。因此改寫了的代碼如下所示(頗有點巧妙):
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檢測中一定不會導(dǎo)致TypeError霍掺,因此在else中手動誘發(fā)TypeError好交給外層處理;如果不是字符串且不是普通的單個元素(不然進入不到第一個try子句里),那么會觸發(fā)內(nèi)層的TypeError杆烁,不需要進行處理而是直接跳過好讓后面的for中遞歸內(nèi)容執(zhí)行牙丽。
9.7.3 通用生成器
生成器是由兩部分組成的:生成器的函數(shù)和生成器的迭代器。用于def語句定義的包含yield部分的就是生成器的函數(shù)兔魂,生成器的迭代器則是這個函數(shù)的返回部分烤芦。兩個實體經(jīng)常被當做一個,合作生成器析校。如下一個簡單例子方便理解這兩個概念:
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中引入了屬性能夠為生成器提供值构罗,這樣生成器就能與外部進行交互:
- 外部作用域訪問生成器的send方法,就像使用next一樣勺良,只不過前者需要一個參數(shù)
- 當生成器運行時候绰播,返回通過send發(fā)送的值骄噪,如果next方法被使用則放回None
如下是一個簡單例子:
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é)會使用生成器來解決經(jīng)典的編程問題逻谦。
9.8.1 生成器和回溯
略。
9.8.2 問題
簡述:8*8的棋盤中陪蜻,放置八個皇后使得他們相互之間不會相互威脅(即不在同一條橫線豎線斜線上)邦马。
9.8.3 狀態(tài)表示
八個皇后的狀態(tài)可以用一個長度為8的元組表示,比如state[0]=3表示第1行的皇后放在第4列宴卖。
9.8.4 尋找沖突
可以把沖突的判定定義為一個函數(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為下一個皇后的豎直位置(nextX和nextY的含義理解了就容易了)滋将,然后遍歷所有已經(jīng)放置好的皇后,如果新的位置和放置好的皇后位置在同一個豎直位置上或者斜線上症昏,那么就返回True随闽,否則返回False。
9.8.5 基本情況
這里先把問題簡化為n皇后肝谭,假定前面n-1個皇后的位置都已經(jīng)找到了掘宪,你只需要處理最后一步,你現(xiàn)在需要做的是根據(jù)前面n-1個皇后的位置找到第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)層部分相當于我們之前所處理的情況魏滚,區(qū)別在于現(xiàn)在變成了元組,即該部分會獲得所有最后一個皇后的位置并返回給父級調(diào)用(內(nèi)層for循環(huán)中的queens)坟漱,并跟后面7皇后的位置結(jié)果進行合并鼠次。以此類推。
9.8.7 打包
可以將八皇后的結(jié)果輸出的稍微好理解一點,這樣也有有助于發(fā)現(xiàn)錯誤:
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í)什么
測試须眷、擴展竖瘾、打包和一些項目的具體實現(xiàn)。