《Python基礎(chǔ)教程》第9章 魔方方法、屬性和迭代器

本章會(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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 我們?cè)趯W(xué)習(xí)web前端的路程起步時(shí)總是疑問,我們?nèi)绾胃玫谋闅v元素呢?迭代器和生成器是什么?今天為大家?guī)吓c精彩的E...
    儂姝沁兒閱讀 3,496評(píng)論 0 6
  • 本文翻譯自Functional Programming Howto 本文將介紹Python中函數(shù)式編程的特性。在對(duì)...
    大蟒傳奇閱讀 2,742評(píng)論 4 14
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,500評(píng)論 19 139
  • 你不知道JS:異步 第四章:生成器(Generators) 在第二章,我們明確了采用回調(diào)表示異步流的兩個(gè)關(guān)鍵缺點(diǎn):...
    purple_force閱讀 1,035評(píng)論 0 2
  • 感情從來是相互的,我失去的同樣你也失去,我有什么好值得難過的。 我們都在改變,你早就不是過去的你,我也不是曾經(jīng)的我...
    yf2880閱讀 259評(píng)論 0 1

友情鏈接更多精彩內(nèi)容