python 設計模式之 -- 迭代模式

前言

去年買了一本《圖解設計模式》,是用 java 寫的,今年不寫 java了,已經入了python 的坑,現在打算把書拿起來研讀,把裏面的 java 代碼全部轉成 python,這樣設計模式基本可以入門了。如果大家對設計模式感興趣的話,可以買一本看看,不過程序員的經典之作還是 GoF(the gang of four),翻譯成中文 “四人幫”,最開始就是四個人的論文整合的,不得不說老外起名字很有特點。

1.爲啥用迭代

萌新程序猿一般都對設計模式不大感冒,項目不大的話壓根就用不上。迭代模式的意義就是降低內存,你建了10個對象都存 list,一點問題沒有,1000萬個對象貌似對內存有點壓力了,所以迭代一般都涉及稍微大點的項目。

2.初識迭代

迭代就是建一個對象,然後不斷的用 next 方法獲取裏面的數據,聽起來和列表很像的,但是列表是佔內存的,可以改造一下列表讓它變成迭代器,如下:

import sys

if __name__ == '__main__':
    list_ = []
    list_iter = iter(list_)
    for i in xrange(100000):
        list_.append(i)
    print 'list size:', sys.getsizeof(list_)
    print type(list_)
    print 'list iter size:', sys.getsizeof(list_iter)
    print type(list_iter)
    count = 0
    for i in list_iter:
        print i
        count = count + 1
        if count == 2:
            break

結果:

list size: 824464
<type 'list'>
list iter size: 56
<type 'listiterator'>
0
1

首先說一下 sys.getsizeof() 函數(單位byte),它是獲取對象佔用內存的(計算方式有不精確的地方,但是整體上沒有大問題)
我們創建了10萬個數字放到列表裏面,總共佔內存 824464,把列表轉成迭代器以後佔比只有 56,不及萬分之一。
這是調用 python 已有的方式轉成迭代器,如果對象比較複雜不能用已有的方法怎麼辦,就需要自己建一個迭代器。

2.實現迭代器

python 中迭代器有兩個關鍵函數,如下,實現這兩個函數就可以了

__iter__	用於生成迭代器
next		用於獲取遍歷獲取內容

給大家舉一個普遍的例子,幾乎在所有的迭代模式裏面都會有這個例子

import sys

class MyIter(object):
    def __init__(self, n):
        self.index = 0
        self.n = n

    def __iter__(self):
        return self

    def next(self):
        if self.index < self.n:
            value = self.index ** 2
            self.index += 1
            return value
        else:
            raise StopIteration()

if __name__ == '__main__':
    my_iter = MyIter(100000000)
    print 'my_iter size:', sys.getsizeof(my_iter)
    print type(my_iter)
    print my_iter.next()
    print my_iter.next()

結果:

my_iter size: 56
<class '__main__.MyIter'>
0
1

我們創建了一個有1億數據的迭代對象,其內存佔比只有 56 (其實不止56,可能400左右,具體可以把迭代類的隱藏函數內存佔比都加起來,但是400仍然很小),如果建1億的對象存起來,內存肯定達到 M 單位,所以迭代類很省空間。

3.迭代類

其實看上面那個例子是有點懵的,例子很簡單,有時候搞不懂咋變換使用,所以下面建一個類,然後把類封裝成迭代類,這樣好看懂一些。
首先,建一個 Book 類,裏面寫一些需要的操作函數,get 等。

class Book(object):
    def __init__(self, name, page):
        self.name = name
        self.page = page

    def get_name(self):
        return self.name

    def get_page(self):
        return self.page

把Book類當內容主體,實現迭代有兩種方式:
第一種藉助已有的迭代生成器工具類,例如1中的 listiterator,另外還有 itertools、enumerate、map、filter等

if __name__ == '__main__':
    list_ = []
    list_iter = iter(list_)
    for i in xrange(100000):
        list_.append(Book(name=str(i), page=10))
    print type(list_iter)
    print list_iter.next().get_name()
    print list_iter.next().get_name()

結果:

<type 'listiterator'>
0
1

第二種是自己實現迭代類,下面這個是我仿照 java 的迭代設計模式寫的:

import sys

class BookShelf(object):
    book_shelf = []
    length = 0

    def add(self, name, page):
        self.book_shelf.append(Book(name=name, page=page))
        self.length = self.length + 1

    def __iter__(self):
        return self

    def next(self):
        if self.length > 0:
            return self.book_shelf.pop(0)
        else:
            raise StopIteration()

if __name__ == '__main__':
    book_iter = BookShelf()
    for i in xrange(1000000):
        book_iter.add(name=str(i), page=11)
    print sys.getsizeof(book_iter)
    print type(book_iter)
    print book_iter.next().get_name()
    print book_iter.next().get_name()

結果:

56
<class '__main__.BookShelf'>
0
1

4.note

爲了節約內存有些函數建議少用,上面在生成列表的時候用的是 xrange,爲啥呢,因爲xrange是一個生成器,range少用,它生成的是列表,舉例說明:

import sys

if __name__ == '__main__':
    a = xrange(1, 100000000, 2)
    print sys.getsizeof(a)
    print type(a)
    b = range(1, 100000000, 2)
    print sys.getsizeof(b)
    print type(b)

結果:

32
<type 'xrange'>
400000064
<type 'list'>

另外,上面巴拉巴拉說那麼多,其實python最好的迭代方式是用yield生成器,仔細研究一下yield用法你就會發現它存在的意義就是爲了實現迭代。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章