特殊方法
有的名稱前後都有兩個下劃線,如
__future__
,由這些名字組成的集合所包含的方法稱為特殊方法。
構造方法
構造方法是一個特殊方法。當一個對象被建立後,會立即調用構造方法。
建立構造方法:
class FooBar:
def __init__(self):
self.somevar = 42
f = FooBar()
print f.somevar #42
也可以在構造方法中傳入參數:
class FooBar:
def __init__(self,value=42):
self.somevar =
子類重寫父類的方法
如果一個方法在B類的一個執行個體中被調用(或一個屬性被通路),但在B類中沒有找到,那麼就會去超類A中去找:
class A:
def hello(self):
print "Hello,I'm A."
class B(A):
pass
if __name__ == '__main__':
a = A()
b = B()
a.hello() #Hello,I'm A.
b.hello() #Hello,I'm A.
也可重寫一些父類的方法:
class A:
def hello(self):
print "Hello,I'm A."
class B(A):
def hello(self):
print "Hello,I'm B."
if __name__ == '__main__':
a = A()
b = B()
a.hello() #Hello,I'm A.
b.hello() #Hello,I'm B.
如果一個類的構造方法被重寫,那麼就需要調用超類的構造方法。
# -*- coding: utf-8 -*
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print 'Aaaah...'
self.hungry = False
else:
print 'No,thanks!'
if __name__ == '__main__':
b = Bird()
b.eat() #Aaaah...
b.eat() #No,thanks!
這個類定義了所有鳥類都具有的基本能力——吃。
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print 'Aaaah...'
self.hungry = False
else:
print 'No,thanks!'
class SongBird(Bird):
def __init__(self):
self.sound = 'Squawk!'
def sing(self):
print self.sound
if __name__ == '__main__':
sb = SongBird()
sb.sing()# Squawk!
我們增加了一個子類,為它添加唱歌的行為。它繼承了eat方法:
Traceback (most recent call last):
File "D:/workspace/python/LearnPython/SpecialMethod/Bird.py", line 22, in <module>
sb.eat()
File "D:/workspace/python/LearnPython/SpecialMethod/Bird.py", line 7, in eat
if self.hungry:
AttributeError: SongBird instance has no attribute 'hungry'
但是如果調用會報錯。異常說SongBird沒有hungry特性(attribute)。
為了達到預期的效果,
SongBird
的構造方法必須調用其超類
Bird
的構造方法來確定進行基本的初始化。
可以通過調用超類構造方法的未綁定版本或使用super函數。
調用未綁定的超類構造方法
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print 'Aaah...'
self.hungry = False
else:
print 'No,thanks!'
class SongBird(Bird):
def __init__(self):
Bird.__init__(self) # 調用父類構造方法
self.sound = 'Squawk!'
def sing(self):
print self.sound
sb = SongBird()
sb.sing() #Squawk!
sb.eat() #Aaah...
sb.eat() #No,thanks!
調用一個執行個體的方法時,該方法的self參數會自動綁定到執行個體上,這稱為綁定方法。
如果直接調用類的方法,如
Bird.__inint__
,那麼就沒有執行個體會被綁定,就要提供需要的self參數,這樣的方法稱為未綁定方法。
使用super函數
在Python3.0中推薦使用super函數。
__metaclass__=type # super函數隻在新式類中起作用
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print 'Aaaah...'
self.hungry = False
else:
print 'No,thanks!'
class SongBird(Bird):
def __init__(self):
super(SongBird,self).__init__() # 注意super括号裡面是逗号
self.sound = 'Squawk!'
def sing(self):
print self.sound
if __name__ == '__main__':
sb = SongBird()
sb.sing()# Squawk!
sb.eat()
sb.eat()
成員通路
可以建立行為類似序列或映射的對象。
基本的序列和映射規則
序列和映射是對象的集合。為了實作它們的基本行為(規則),如果對象是不可變的,需要兩個魔法方法;可變的則需要四個。
-
:傳回集合中所含元素的數量__len__(selft)
-
:傳回與所給定鍵(序列:索引;映射:key)對應的值__getitem__(self,key)
-
:存儲key和相關的value__setitem__(self,key,value)
-
:在使用del時,同時删除key和value__delitem__(self,key)
我們來應用下這些規則:
# -*- coding: utf-8 -*
class ArithmeticSequence:
def __init__(self, start=0, step=1):
"""
初始化算術序列
:param start: 序列中的第一個值
:param step: 兩個相鄰值之間的內插補點
_changed : 修改的值的字典
"""
self._start = start
self._step = step
self._changed = {}
def __getitem__(self, key):
"""
從算術序列中傳回一項(key,value)
:param key:
:return:
"""
self.checkIndex(key)
try:
return self._changed[key] # 修改了則傳回
except KeyError:
return self._start + key * self._step # 計算值
def __setitem__(self, key, value):
"""
修改算術序列中的一個項
:param key:
:param value:
"""
self.checkIndex(key)
self._changed[key] = value
def checkIndex(self, key):
if not isinstance(key, (int, long)): raise TypeError
if key < 0: raise IndexError
if __name__ == '__main__':
s = ArithmeticSequence(1,2)
print s[4] # 需要實作__getitem__
s[4] = 2 #__setitem__
print s[4],s[5]
del s[4] # 報錯:ArithmeticSequence instance has no attribute '__delitem__'
這些魔法方法挺有用的,尤其是你想實作自己的集合類時。
子類化清單,字典和字元串
如果希望實作一個和内建清單行為相似的序列,可以繼承list來實作。
當繼承一個内建類型時,也就間接地繼承了object。是以該類就自動成為新式類,意味着可以使用super函數這樣的特性了
# -*- coding: utf-8 -*
class CounterList(list):
def __init__(self,*args):
super(CounterList,self).__init__(*args)
self.counter = 0 #增加counter特性
def __getitem__(self, index):
self.counter += 1
return super(CounterList,self).__getitem__(index)
if __name__ == '__main__':
cl = CounterList(range(10))
print cl #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
cl.reverse()
print cl #[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
del cl[3:6]
print cl #[9, 8, 7, 3, 2, 1, 0]
print cl[4] + cl[2] #9 這裡調用了兩次getitem
print cl.counter #2
CounterList
在很多方面和清單一樣,但它有一個
counter
特性,每次通路時都會自增。
屬性
如果在通路給定特性時徐必須要采取一些行動,那麼像這樣的封裝狀态變量(特性)就很重要,如下面的例子:
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self,size): # 通路器方法
self.width,self.height = size
def getSize(self): # 通路器方法
return self.width,self.height
if __name__ == '__main__':
r = Rectangle()
r.width = 10
r.height = 5
print r.getSize() #(10, 5)
r.setSize((150,100))
print r.width #150
上面的例子中,
getSize
和
setSize
是一個名為
size
的假想特性的通路器方法。
size
是由
width
和
height
構成的元組。這種方式有缺陷,當使用這個類時,不應該要考慮它是怎麼實作的(這裡是通過元組)。
Python能隐藏通路器方法,讓所有特性(attribute)看起來一樣。這些通過通路器定義的特性被稱為屬性(property)。
python中的類成員變量稱為特性,作為一Java開發人員有點不習慣啊
property函數
使用property函數可以建立屬性。
# -*- coding: utf-8 -*
__metaclass__ = type #property函數隻在新式類中使用,不加會最後的r.width列印10
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def setSize(self, size): # 通路器方法
self.width, self.height = size # 元組的指派
def getSize(self): # 通路器方法
return self.width, self.height
size = property(getSize, setSize)
if __name__ == '__main__':
r = Rectangle()
r.width = 10
r.height = 5
print r.size # (10, 5)
r.size = 150, 100
print r.width # 150
property函數建立了一個屬性,其中通路器函數被用作參數(先是取值,然後是指派),這個屬性命名為size。
property()函數中的兩個函數分别對應的是擷取屬性的方法、設定屬性的方法,這樣一來,外部的對象就可以通過通路size的方式,來達到擷取、設定的目的。
當需要更改上例中的getSize、setSize函數的名稱時,如果這些方法是作為接口讓使用者調用的,那麼對使用者而言就要修改自己調用的方法名,很麻煩,使用了proprty()後,使用者就不需擔心這種問題了。
靜态方法和類成員方法
靜态方法和類成員方法分别在建立時裝入Staticmethod類型和Classmethod類型的對象中。靜态方法的定義沒有self參數,能被類本身直接調用。類方法在定義時需要名為cls的參數,類成員方法可以直接用類的具體對象調用。
__metaclass__ = type
class MyClass:
@staticmethod
def smeth():
print 'This is a static method'
@classmethod
def cmeth(cls):
print 'This is a class method of',cls
MyClass.smeth() #This is a static method
MyClass.cmeth() #This is a class method of <class '__main__.MyClass'>
上面的代碼中沒有執行個體化類。
__getattr__、__setattr__等方法
攔截對象的所有特性通路時可能的。為了在通路特性的時候可以執行代碼,必須使用一些魔法方法。
-
:當特性name被通路時自動被調用(新式類)__getattribute__(self,name)
-
:當特性name被通路且對象沒有相應的特性時被自動調用__getattr__(self,name)
-
:當試圖給特性name指派時會被自動調用__setattr__(self,name,value)
-
:當試圖删特性name時被自動調用__delattr__(self,name)
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def __setattr__(self, name, value):
if name == 'size':
self.width,self.height = value
else:
self.__dict__[name] = value #__dict__中是所有執行個體的屬性
def __getattr__(self, name):
if name == 'size':
return self.width,self.height
else:
raise AttributeError
if __name__ == '__main__':
r = Rectangle()
r.size = 10,5
print r.size # (10, 5)
r.width = 100
r.height = 50
print r.size #(100, 50)
疊代器
這裡隻讨論一個特殊方法——
__iter__
疊代器規則
隻要對象實作了
__iter__
方法就可以進行疊代。該方法會傳回一個疊代器,可以用在for循環中。
使用疊代器實作的斐波那契數列:
class Fibs:
def __init__(self):
self.a = 0
self.b = 1
def next(self): #實作了next方法的對象是疊代器
self.a,self.b = self.b,self.a + self.b
return self.a
def __iter__(self): #實作了__iter__方法的對象是可疊代的
return
每次通路next的時候生成下一個值:
fibs = Fibs()
for f in fibs:
if f > 1000:
print f #1597
break
從疊代器得到序列
疊代器和可疊代對象還能轉換為序列,比如使用list構造方法顯示地将疊代器轉換為清單:
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]
生成器
生成器是一種用普通的函數文法定義的疊代器。
建立生成器
建立生成器和建立函數類似,任何包含yield(ES6中也有這個關鍵字)語句的函數稱為生成器。以一個例子來講解。
首先建立一個清單的清單:
nested = [[1,2,3],[4,5],[6]]
然後寫一個生成器:
def flatten(nested):
for sublist in nested:
for element in sublist:
yield element # 每産生一個值,函數就會被當機
每次通路到yield語句時,産生一個值,并且函數被當機,等待被重新喚醒。函數被重新喚醒後就從停止的那點開始執行。
下面在生成器上疊代來使用所有的值:
for num in flatten(nested):
print
輸出:
1
2
3
4
5
6
也可以通過list函數來通路:
print list(flatten(nested)) #[1, 2, 3, 4, 5, 6]
生成器推導式傳回的是生成器,并且不會立刻進行循環:
>>>g = ((i+2)**2 for i in range(2,27))
>>>g.next()
16
>>>g.next()
25
遞歸生成器
如果想要處理任意層的嵌套,應該使用遞歸。
def flatten(nested):
try:
for sublist in nested: #可疊代的情況
for element in flatten(sublist):
yield element
except TypeError: # 和不可疊代的情況
yield nested
print list(flatten([[[1],2],3,4,[5,[6,7,[8,9]]]])) #[1, 2, 3, 4, 5, 6, 7, 8, 9]
這樣做有一個問題:如果nested是一個類似于字元串的對象,那麼會導緻無窮遞歸。因為一個字元串的第一個元素是另一個長度為1的字元串,而長度為1的字元串的第一個元素就是字元串本身。
我試了一下,會抛出異常
RuntimeError: maximum recursion depth exceeded
def flatten(nested):
try:
#不要疊代類似字元串的對象
try: nested + ''
except TypeError: pass #nested + ''産生的異常被忽略,直接到for語句
else: raise TypeError
for sublist in nested: #可疊代的情況
for element in flatten(sublist):
yield element
except TypeError: # 和不可疊代的情況
yield nested
print list(flatten('123')) #['123']
如果表達式nested + ''引發了一個TypeError說明它不是類似字元串的序列,可以被疊代;否則如果沒有引發TypeError,那else子句會引發一個自己的TypeError,直接傳回這個類似字元串序列。這是檢查一個對象是不是類似于字元串的最簡單、最快速的方法。
通用生成器
生成器由兩部分組成:生成器函數和生成器的疊代器。
生成器的函數是用
def
語句定義的,包含
yield
的部分,
生成器的疊代器是這個函數傳回的部分。
>>>def simple_geneartor():
yield 1
>>>simple_geneartor
<function simple_geneartor at 0x03003770>
>>>simple_geneartor()
<generator object simple_geneartor at 0x02FEA7D8>
生成器方法
生成器的新特性(
send()
)是在開始運作後為生成器提供值的能力。
- 外部作用域通路該方法,需要和傳遞一個參數(作為值)
- 在内部挂起生成器,
現在作為表達式而不是語句使用,使用yield
方法隻有在生成器挂起後才有意義send()
>>> def repeater(value):
... while True:
... new = (yield value)
... if new is not None: value = new
...
>>> r = repeater(42)
>>> r.next()
42
>>> r.send("Hello")
'Hello'
>>>