天天看點

Python中的特殊方法、屬性和疊代器

特殊方法

有的名稱前後都有兩個下劃線,如​

​__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)​

    ​:傳回集合中所含元素的數量
  • ​__getitem__(self,key)​

    ​:傳回與所給定鍵(序列:索引;映射:key)對應的值
  • ​__setitem__(self,key,value)​

    ​:存儲key和相關的value
  • ​__delitem__(self,key)​

    ​:在使用del時,同時删除key和value

我們來應用下這些規則:

# -*- 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__等方法

攔截對象的所有特性通路時可能的。為了在通路特性的時候可以執行代碼,必須使用一些魔法方法。

  • ​__getattribute__(self,name)​

    ​:當特性name被通路時自動被調用(新式類)
  • ​__getattr__(self,name)​

    ​:當特性name被通路且對象沒有相應的特性時被自動調用
  • ​__setattr__(self,name,value)​

    ​:當試圖給特性name指派時會被自動調用
  • ​__delattr__(self,name)​

    ​:當試圖删特性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'
>>>