標籤:

怎樣寫出Pythonic的代碼

我們周圍有很多資深的工程師,用其他語言寫過很多的代碼,做過很多的項目。但是,卻還是會被吐槽寫的Python代碼不夠Pythonic,這是為什麼呢?在Python這個圈子中,Python大牛們總是提到一個很時髦的詞,即Pythonic。但是,又很少有人能夠說得清楚它究竟是個什麼意思,搞的新同學一頭霧水。我今天就要回答,什麼是Pythonic,怎樣才能寫出Pythonic的代碼。

我們先來看一下,Python程序員每天津津樂道的the zen of python(Python之禪)

>>> import thisThe Zen of Python, by Tim PetersBeautiful is better than ugly.Explicit is better than implicit.Simple is better than complex.Complex is better than complicated.Flat is better than nested.Sparse is better than dense.Readability counts.Special cases arent special enough to break the rules.Although practicality beats purity.Errors should never pass silently.Unless explicitly silenced.In the face of ambiguity, refuse the temptation to guess.There should be one-- and preferably only one --obvious way to do it.Although that way may not be obvious at first unless youre Dutch.Now is better than never.Although never is often better than *right* now.If the implementation is hard to explain, its a bad idea.If the implementation is easy to explain, it may be a good idea.Namespaces are one honking great idea -- lets do more of those!

我簡單翻譯幾句:

優美勝於醜陋明了勝於晦澀簡介勝於複雜……可讀性很重要……

難道只有我一個人覺得這是正確的廢話嗎?

大家可以看到,Python之禪有點形而上學,並且,這裡的Python之禪,換成其他任何一門編程語言,似乎也是適用的。所以,Python程序員津津樂道的Python之禪,其實就是給出一個對代碼的評判標準,即什麼做法是好,什麼做法是不好。我們把好的那一部分做到,把不好的那一部分避免掉,就是所謂的Pythonic了。

《Pythonic之禪》只告訴你什麼是好,什麼是不好,但是,卻沒有告訴你通往成功彼岸的方法。從這個角度來說,我更推薦:

Pythonic一直以來都是只能意會,不能言傳的東西,然而,為了幫助新同學理解,我準備給出確切的定義:Pythonic就是以Python的方式寫出簡潔優美的代碼!

首先,不管用什麼語言,你都應該努力寫出簡潔優美的代碼,如果不能,那我推薦你看看《重構》和《代碼整潔之道》,雖然這兩本書使用的是java語言,但是,並不影響作者要傳遞的思想。

比如,我們知道,Python裡面的字元串、列表和元組,都稱之為序列,序列支持索引操作和切片操作,切片操作可以指定起點、終點和步長,步長也可以為負數。我們看下面的切片:

L = [1, 2, 3, 4, 5, 6, 7, 8, 9]L[-2::-2]L[-2:2:-2]L[2:2:-2]

誰能快速的回答我上面幾個切片的答案?沒有人!所以,Python也有很多語法,但是,並不能亂用。就這個例子來說:

  • 在同一個切片操作中,不要同時使用start、end和stride

  • 盡量使用stride為正數,且不要帶start和end索引的切割操作

在你已經能夠寫出簡潔優美的代碼的前提下,要寫出Pythonic的代碼,還需要對Python的語言有比較好的了解。我舉幾個在Python裡面與在C、C++和Java裡面有顯著差別的例子。

1. 交換兩個數字

在其他語言裡面

t = aa = bb = t

在Python語言裡面

a, b = b, a

2. 列表推導

列表推導是C、C++、Java裡面沒有的語法,但是,是Python裡面使用非常廣泛,是特別推薦的用法。

與列表推導對應的,還有集合推導和字典推導。我們來演示一下。

  • 列表:30~40 所有偶數的平方

[ i*i for i in range(30, 41) if i% 2 == 0 ]

  • 集合:1~20所有奇數的平方的集合

{ i*i for i in range(1, 21) if i % 2 != 0 }

  • 字典:30~40 所有奇數的平方

{ i:i*i for i in range(30, 40) if i% 2 != 0 }

再舉兩個實用的例子:

  • 當前用戶home目錄下所有的文件列表

[ item for item in os.listdir(os.path.expanduser(~)) if os.path.isfile(item) ]

  • 當前用戶home目錄下所有的目錄列表

[ item for item in os.listdir(os.path.expanduser(~)) if os.path.isdir(item) ]

  • 當前用戶home目錄下所有目錄的目錄名到絕對路徑之間的字典

{ item: os.path.realpath(item) for item in os.listdir(os.path.expanduser(~)) if os.path.isdir(item) }

3. 上下文管理器

我們要打開文件進行處理,在處理文件過程中可能會出錯,但是,我們需要在處理文件出錯的情況下,也順利關閉文件。

Java風格/C++風格的Python代碼:

myfile= open(rC:miscdata.txt)try: for line in myfile: ...use line here...finally: myfile.close()

Pythonic的代碼:

with open(rC:miscdata.txt) as myfile: for line in myfile: ...use line here...

這裡要說的是,上下文管理器是Python裡面比較推薦的方式,如果用try...finally而不用with,就會被認為不夠Pythonic。此外,上下文管理器還可以應用於鎖和其他很多類似必須需要關閉的地方。

4. 裝飾器

裝飾器並不是Python特有的,只是,在Python裡面應用非常廣泛,我們來看一個例子。

考慮這樣一組函數,它們在被調用時需要對某些參數進行檢查,在本例中,需要對用戶名進行檢查,以判斷用戶是否有相應的許可權進行某些操作。

class Store(object): def get_food(self, username, food): if username != admin: raise Exception("This user is not allowed to get food") return self.storage.get(food) def put_food(self, username, food): if username != admin: raise Exception("This user is not allowed to put food") self.storage.put(food)

顯然,代碼有重複,作為一個有追求的工程師,我們嚴格遵守DRY(Don』t repeat yourself)原則,於是,代碼被改寫成了這樣:

def check_is_admin(username): if username != admin: raise Exception("This user is not allowed to get food")class Store(object): def get_food(self, username, food): check_is_admin(username) return self.storage.get(food) def put_food(self, username, food): check_is_admin(username) return self.storage.put(food)

現在代碼整潔一點了,但是,有裝飾器能夠做的更好:

def check_is_admin(f): def wrapper(*args, **kwargs): if kwargs.get(username) != admin: raise Exception("This user is not allowed to get food") return f(*arg, **kargs) return wrapperclass Storage(object): @check_is_admin def get_food(self, username, food): return self.storage.get(food) @check_is_admin def put_food(self, username, food): return storage.put(food)

在這裡,我們使用裝飾器,就可以把參數檢查和業務邏輯完全分離開來,讓代碼顯得更加清晰。這也是比較Pythonic的代碼。

5. 動態類型語言

我們再來看一個例子,該例子充分演示了動態類型語言與靜態類型語言編程之間的差異。

在這個例子中,我們會收到很多不同的請求,對於不同的請求,調用不同的請求處理函數,這個需求如此常見,相信大家應該見過這樣的代碼:

if (cmd == a) processA()else if (cmd == b) processB()else if (cmd == c) processC()else if (cmd == d) processD()……else raise NotImplementException

在Python裡面,我們可以先判斷一個類,有沒有這個函數,如果有,則獲取這個函數,然後再調用。所以,我們的代碼可以寫成這樣:

class A: def fetch_func(self, action_name): func= getattr(self, action_name, None) return func def execute(self, action, msg): func= self.fetch_func(action) if func is None: return False, "Action not found" return func(action, msg)

結論:所謂的Pythonic,其實並沒有大家想的那麼神秘,最終目的都是寫出簡潔優美的代碼。寫出簡潔優美代碼的思想在各個語言中都是一樣的。如果你用其他編程語言寫不出簡潔優美的代碼,那麼,你也沒辦法用Python寫出簡介優美的代碼。如果你能用其他語言寫出很好的代碼,那麼,還是需要了解Python這門語言特有的一些語法和語言特性,充分利用Python裡面比較好語言特性。這樣,就能夠寫出Pythonic的代碼了。
推薦閱讀:

關於技術寫作的優秀 Blog
DL筆記:語言模型和 N-gram
掃地機器人、遞歸與科普
c++大作業怎麼用windows API 做個窗口程序?不用MFC或者是QT這些工具。
如何形象的描述反應式編程中的背壓(Backpressure)機制?

TAG:Python | 编程 |