1. 程式人生 > >新手學python(3):yield與序列化

新手學python(3):yield與序列化

1 Yield生成器

       Yield是我在其他語言中沒有見過的一個屬性,算是python的一大特色,用好之後可以使程式碼更簡潔。考慮一個簡單的例子,檔案的遍歷。要遍歷一個目錄下的所有檔案需要遞迴的操作。如果我們只是單純的列印檔名,我們可以在遞迴的過程中完成,每當發現一個非目錄就可以列印檔名。程式碼如下:

class TraverseDirectory(object):

    @staticmethod
    def traverse(dir):
        if os.path.isdir(dir):
            files=os.listdir(dir);
            for file in files[::-1]:
                full_name=os.path.join(dir,file);
                TraverseDirectory.traverse(full_name):
        else:
            print dir;

但是如果我們想儲存檔名或者對每一個檔案執行更復雜的操作返回一個結果,情況就稍微有些複雜。問題在於我們需要一個全域性變數儲存訪問的結果。例如在遍歷到一個檔案時獲取其檔案大小,則我們還需要一個dictionary結構:

class TraverseDirectory(object):

    fileSize=dict();

    @staticmethod
    def traverse(dir):
        if os.path.isdir(dir):
            files=os.listdir(dir);
            for file in files[::-1]:
                full_name=os.path.join(dir,file);
                TraverseDirectory.traverse(full_name);
        else:
            TraverseDirectory.fileSize[dir]=os.path.getsize(dir);

初看程式碼感覺其實也不復雜,只是多了一個變數而已。在多數情況下確實如此,但是從應用角度來看上述程式碼稍有不足:為了訪問遍歷的結果,我們需要訪問該類的全域性變數或者一個靜態變數,正如我們在其他語言中一樣,為此我們還需要一個get函式。但是,這還不是最嚴重的問題,fileSize佔用的空間才是問題!

       上述遞迴函式只有在遍歷完所有的檔案之後我們才能訪問fileSize結構。這就意味著如果目錄很大,則fileSize也會非常大,如果要控制記憶體佔用,上述方式會很不好。此時,就可以採用yield生成器完成遍歷。

       何謂生成器?其實很簡單,概念和迭代器類似,都是為了遍歷而存在。迭代器是為了遍歷變數,例如列表、元組、字典等等。生成器不是遍歷變數,而是函式。

簡而言之,包含yield語句的函式會被特地編譯成生成器。當函式被呼叫時,他們返回一個生成器物件,這個物件支援迭代器介面。不像一般的函式會生成值後退出,生成器函式在生成值後會自動掛起並暫停他們的執行和狀態,他的本地變數將儲存狀態資訊,這些資訊在函式恢復時將再度有效。我們可以將生成器理解成可以隨時獲得函式返回值的迭代器。可以將生成器與gdb偵錯程式對比,我們在每一步單步除錯的過程中,系統中總會有許多變數和其執行時值,yield就對應每一次的單步除錯,並獲取當前的變數值,不過其獲取返回值是在每一次遞迴結束時。將上述遍歷程式碼改成yield方式之後為:

class TraverseDirectory(object):

    @staticmethod
    def traverse(dir):
        if os.path.isdir(dir):
            files=os.listdir(dir);
            for file in files[::-1]:
                full_name=os.path.join(dir,file);
                for results in TraverseDirectory.traverse(full_name):
                    yield results;
        else:
           yield  {dir:os.path.getsize(dir)};

與之前的程式碼相比,就是少了一個變數,然後多了兩個yield。通過這個微小的變化,我們去掉了函式的記憶體大小限制,也使程式碼更簡潔。需要注意的一點是,呼叫過程也很簡單,因為函式是一個生成器,具有迭代器的功能,我們就可以利用for迴圈去遍歷函式的返回值。

       Yield這種具有中斷功能的設計使程式碼更加簡潔,但是對效率會有一定影響。瑕不掩瑜,建議大家熟練應用yield。

2 序列化

       在進行網路通訊的過程中,我們傳遞的是沒有含義的資料流,這就意味著我們無法直接傳遞list和dict之類的資料結構,而需要首先將它們序列化之後再進行傳遞。接收方收到資料流之後再發序列化獲得原始的資料結構。序列化的應用並不侷限於網路通訊,在持久化儲存中也需要用到序列化。

       Python中有兩個關於序列化的庫:json和cPickle,兩者用法相同,但是貌似json速度更快,因而json使用的次數更多。序列化呼叫dumps,反序列化呼叫loads,使用非常簡單。

       如果需要序列化的資料編碼方式不是預設方式,我們還可以指定編碼方式:

        result=json.dumps(data,ensure_ascii=True,encoding=’gbk’);

反序列化也需要指定編碼方式:

        result=json.loads(data, encoding=’gbk’);

由於序列化操作在python中非常簡單,在此不做更多介紹,更加深入的操作請參考:Json概述以及python對json的相關操作