Python的精華——dict

最近身邊很多同事、朋友打算學習Python,其實學了幾個月的Python,我思之再三,到底這門語言的吸引力在哪?很多的工具包?很多文檔?很通俗的語言?

後來我覺得,說語法,是看低了大家的水平,Python之所以成爲Python,我覺得很大程度取決於它的精華之一——dict。dict爲Python代碼的簡潔貢獻了很多力量,因此,我們編寫Python代碼的時候,便要多去想想,怎樣用dict使得代碼看起來更簡潔。

記得當初初學Python的時候,我爲了一個功能苦苦尋找Switch Case,結果顯而易見,被告知Python是沒有Switch Case的,爲什麼?因爲Python有很強大的一個數據結構——dict,當我們需要使用多重條件選擇的時候,用一個dict通俗、簡潔、高效,大家可以看看這段僞代碼:

switch(fruit)
{
case 'apple':print('apple is good for health')
....
case 'banana':print('John loves banana')
}
然後在看看Python可以做什麼:

fruit = {'apple': 'apple is good for health', 'banana': 'John loves banana'}

print(fruit['apple'])
my_fruit = input()
try:
    print(fruit[my_fruit])
except KeyError:
    pass
而dict還有一個強大的地方,就是調用函數:

class A:
    def __init__(self):
        pass

    @staticmethod
    def func1():
        print('func1')

    def func2(self):
        print('func2')

    def func3(self, *args):
        print('func3', args)

a = A()
order_dict = {
    'cmd1': A.func1,
    'cmd2': a.func2,
    'cmd3': a.func3,
    'exit': sys.exit
}

order_dict['cmd1']()
order_dict['cmd2']()
order_dict['cmd3']('hello', 1, 0.1)
order_dict['exit']()

大家發現這個功能有什麼用嗎?對了,就是用在controller裏面,接收用戶輸入的一段命令,然後自動查找業務邏輯執行操作。

基於Python的動態類型,dict還能用來保存全局變量:

hyper_para = {
    'learning_rate': 0.01,
    'batch_size': 128,
    'train_data': r'd:\data'
}
看到此處,大家有什麼感想?dict很強大?但如果我們只把dict用來簡單的查找,便太狹隘,事實上,當我們多去看看各種工具包的文檔、源碼,我們會發現dict被用來實現很多的設計模式:工廠模式、單例模式等等,這些工具包大多自己用字典維護了一張大表,保存我們通過工廠方法創建的各種實例,並且保證了每個命名對應實例的全局唯一性,然而網絡上大多數的工具包的使用案例並沒有很好地領悟到工具包設計者的意圖,總是使用本地變量以及構造函數來完成代碼,並不是說這樣不可以,但代碼的可讀性以及遷移性就差很多,也很可能因爲命名的不規範發生無法預知的問題。

這裏我用logging來舉個例子,大部分人用logging是這樣用的:

import logging
my_logger = logging.Logger('logger1')

這樣用有什麼問題?我們看看這段代碼:

import logging
my_logger1 = logging.Logger('logger1')
my_logger2 = logging.Logger('logger1')
print(my_logger1 == my_logger2)
# False
logger1 = logging.getLogger('logger2')
logger2 = logging.getLogger('logger2')
print(logger1 == logger2)
# True
我們可以看到,如果是用構造函數生成的logger,兩次構造同名的logger也是不同的實例,我們在不同函數或者不同module共用logger的時候,基本就取決於我們的變量名,除了保持全局唯一還得全局共享,這給編程帶來了很多麻煩。

而getLogger()返回的logger,是全局唯一的,其引用保存在logging.manager的一個字典裏面,當我們第一次get的時候,manager會新建一個logger並把名字-引用保存在字典裏面,當我們在同一進程(logging是線程安全的)再次get的時候,返回的便是之前新建的logger,保證了全局唯一。源碼如下:

        try:
            if name in self.loggerDict:
                rv = self.loggerDict[name]
                if isinstance(rv, PlaceHolder):
                    ph = rv
                    rv = (self.loggerClass or _loggerClass)(name)
                    rv.manager = self
                    self.loggerDict[name] = rv
                    self._fixupChildren(ph, rv)
                    self._fixupParents(rv)
            else:
                rv = (self.loggerClass or _loggerClass)(name)
                rv.manager = self
                self.loggerDict[name] = rv
                self._fixupParents(rv)
        finally:
            _releaseLock()
        return rv
我們看到,字典的使用大大簡化了維護表格的難度,同時我們應該注意一點,那就是要看到字典裏面是否有這個“key”的時候,請多用my_key in dict的做法,這樣的寫法更通俗、更簡潔、更Python!

這樣做還有什麼好處呢?大家可以看看下面這一行代碼:

logging.getLogger(__name__).info(MyInit.message_about(__file__, 'done_train'))

大家可以看到我把“__name__”作爲了logger的變量名,這也是logging的文檔裏面推薦的方式,這樣這段代碼就可以直接複製粘貼在不同的文件,而保證了每個模塊都共享自己模塊對應的logger,無論是代碼的編寫還是維護都非常方便。同時我們在logger輸出的時候,還可以定義把logger的name輸出,這樣我們在看日誌的時候還可以清晰看到這段信息是在哪個模塊發出來。

除了在logging模塊是通過字典實現了工廠方法和單例模式以外,我們可以看到Tensorflow裏面的變量也使用了該方法:

sample = tf.nn.xw_plus_b(sample, tf.get_variable(
                    'weight', [n_in, n_out], initializer=tf.random_normal_initializer), tf.get_variable(
                    'biases', [n_out], initializer=tf.zeros_initializer), name='fc')
因此,當我們需要寫一些全局有效的模塊的時候,便應該思考,如果用dict維護變量的全局唯一性以及提供方便的工廠方法。

說了那麼多dict的方法,大家可能覺得有一點不方便的地方,那就是用['key']的方式來選取屬性在編程過程中不方便,畢竟IDE是不會判斷你字典裏面有什麼的。爲此,collection模塊裏面有很多高級dict的class,例如nametuple,不要誤會,nametuple真不是tuple,它是繼承了dict並且實現了用類屬性訪問字典的方法,但需要先定義:

checkpoint_fixed_parameters = namedtuple('CheckpointFixedParameters', ['conv_pool_layers', fully_connected_layers',
                                                                           'length0', 'n_class', ])
self.fixed_para = self.checkpoint_fixed_parameters(conv_pool_layers, fully_connected_layers, length0, n_class)

然後我們就可以這樣使用:

for layer_index, n_out in enumerate(self.fixed_para.fully_connected_layers, start=1):
這樣做,在編寫代碼的時候IDE就會提示字典裏的key,這樣的做法只適用於我們實現已知字典裏面所有的key,例如全局變量表,又或者有多個具有相同結構的字典,我們便簡化了賦值時候的步驟,只需要按順序輸入value便可。

collection裏面還有另一個比較多用的高級字典就是defaultdict,其中一個特例就是Counter,它們主要是做了一件事情,那就是當my_key not in dict的時候使用dict[key]的時候會報錯,但用defaultdict會實例化一個default類型作爲value,例如Counter的int,代碼很簡單,可以參考官方文檔裏的例子,這裏不再詳述。

總而言之,Python的精髓就是簡,因爲簡單可以避免出錯,也方便其他人理解代碼,易於維護,而實現簡單又不失高效,dict是一個有力的工具。

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