1. 程式人生 > >Python之禪 —— 每一個有追求的 Python 工程師都應該謹記於心

Python之禪 —— 每一個有追求的 Python 工程師都應該謹記於心

Python 之禪

在這裡插入圖片描述

在 Python 中有一個彩蛋 —— 我們在命令列中輸入 import this 即可獲得 Tim Peters 的《Python 之禪》,詩詞及其解釋翻譯如下:

 The Zen of Python, by Tim Peters

 "Beautiful is better than ugly."
 # 優美勝於醜陋(Python以編寫優美的程式碼為目標)

 "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 aren't 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 you're Dutch." # 雖然一開始這種方法並不是顯而易見的,但誰叫你不是Python之父呢 "Now is better than never." # 做比不做好(只要努力,成功也許會遲到但絕不會缺席) "Although never is often better than *right* now." # 但立馬去做有時還不如不做(動手之前要細思量) "If the implementation is hard to explain, it's a bad idea." # 如果實現很難說明,那它是個壞想法(如果你無法向別人描述你的方案,那肯定不是一個好方案) "If the implementation is easy to explain, it may be a good idea." # 如果實現容易解釋,那它有可能是個好想法 "Namespaces are one honking great idea -- let's do more of those!" # 名稱空間是個絕妙的想法,讓我們多多使用它們吧!

舉例說明

下面列舉一些 Python 程式設計技巧,這些技巧秉承上面的 Python 之禪,讀者朋友可以細細品讀。

列表推導式

有一個列表:bag = [1, 2, 3, 4, 5]

我們想讓所有元素翻倍,讓它看起來是這樣的:[2, 4, 6, 8, 10]

大多數初學者,根據之前語言的經驗會大概這樣做:

    bag = [1, 2, 3, 4, 5]
    for i in range(len(bag)):
        bag[i] = bag[i] * 2

但是 Python 有更好的方法,也就是列表推導式:

    bag = [elem * 2 for elem in bag]

遍歷列表

還是上面的列表,如果可能儘量避免這樣做:

    bag = [1, 2, 3, 4, 5]
    for i in range(len(bag)):
        bag[i] = bag[i] * 2

取而代之的應該是這樣:

    bag = [1, 2, 3, 4, 5]
    for i in bag:
        print(i) 

如果 x 是一個列表,你可以對它的元素進行迭代。多數情況下你不需要各元素的索引,但如果你非要這樣做,那就用 enumerate 函式。像這樣:

    bag = [1, 2, 3, 4, 5]
    for index, element in enumerate(bag):
        print(index, element)

非常直觀明瞭。

元素互換

如果你是從 java 或 C 語言轉到 Python 來的,可能會習慣於這樣:

    a = 5
    b = 10
    # 交換a和b
    tmp = a
    a = b
    b = tmp

但 Python 提供了一個更自然的方法:

    a = 5
    b = 10
    # 交換a和b
    a, b = b, a

初始化列表

假如你要一個是10個整數0的列表,你可能首先想到:

    bag = []
    for _ in range(10):
        bag.append(0)

換個方式吧:

    bag = [0] * 10

看,多優雅!

注意:如果列表中包含了列表,這樣做會產生淺拷貝。

舉個例子:

    bag_of_bags = [[0]] * 5  # [[0], [0], [0], [0], [0]]
    bag_of_bags[0][0] = 1    # [[1], [1], [1], [1], [1]]

Oops!所有的列表都改變了,而我們只想改變第一個列表。

    bag_of_bags = [[0] for _ in range(5)]  # [[0], [0], [0], [0], [0]]
    bag_of_bags[0][0] = 1                  # [[1], [0], [0], [0], [0]]

“過早優化是萬惡之源”。問問自己,初始化一個列表是必須的嗎?

構造字串

你會經常需要列印字串。要是有很多變數,避免下面這樣:

    name = 'Raymond'
    age = 22
    born_in = "Oakland, CA"
    string = "Hello my name is " + name + "and I'm " + str(age) + " years old. I was born in " + born_in + "."
    print(string)

額,這樣看起來多亂呀?你可以用個漂亮簡介的方法來代替,.format。

    name = 'Raymond'
    age = 22
    born_in = "Oakland, CA"
    string = "Hello my name is {0} and I'm {1} years old. I was born in {2}.".format(name, age, born_in)
    print(string)

返回元組

Python 允許你在一個函式中返回多個元素,這讓生活更簡單。但是在解包元組的時候出現這樣的常見錯誤:

    def binary():
        return 0, 1 
    
    result = binary() 
    zero = result[0] 
    one = result[1]

這是沒必要的,你完全可以這樣:

    def binary():
        return 0, 1 
    
    zero, one = binary()

要是你需要所有的元素被返回,用個下劃線 _:

    zero, _ = binary()

就是這麼高效率! 這個什麼意思?

按照習慣,在 Python 中用單獨一個下劃線用作變數名的,表示這個變數是臨時的或者無關緊要的。

訪問字典

你也會經常給 dicts 中寫入 key, value(鍵, 值)。如果你試圖訪問一個不存在的 key,可能會為了避免 KeyError 錯誤,你會這樣做:

    counter = {}
    bag = [2, 3, 1, 2, 5, 6, 7, 9, 2, 7]
    for i in bag:
        if i in counter:
            counter[i] += 1
        else:
            counter[i] = 1
            
    for i in range(10):
        if i in counter:
            print("Count of {}: {}".format(i, counter[i]))
        else:
            print("Count of {}: {}".format(i, 0))

但是,用 get() 是個更好的辦法:

    counter = {}
    bag = [2, 3, 1, 2, 5, 6, 7, 9, 2, 7]
    for i in bag:
        counter[i] = counter.get(i, 0) + 1
        
    for i in range(10):
        print("Count of {}: {}".format(i, counter.get(i, 0)))

當然,你也可以用 setdefault 來代替。

這裡還有一個更簡單但多花費點開銷的辦法:

    bag = [2, 3, 1, 2, 5, 6, 7, 9, 2, 7]
    counter = dict([(num, bag.count(num)) for num in bag])
    
    for i in range(10):
        print("Count of {}: {}".format(i, counter.get(i, 0)))

還可以用 dict 推導式:

    counter = {num: bag.count(num) for num in bag}

這兩種方法開銷大是因為它們在每次呼叫 count 時都會遍歷列表。

使用庫

現有的庫只需匯入,你就可以做你真正想做的了。

還是說前面的例子,我們想設計一個函式來數一個數字在列表中出現的次數。那麼,已經有一個庫可以做這樣的事情。

    from collections import Counter
    bag = [2, 3, 1, 2, 5, 6, 7, 9, 2, 7]
    counter = Counter(bag)
    for i in range(10):
        print("Count of {}: {}".format(i, counter[i]))

使用庫的一些理由:

  • 程式碼是正確而且經過測試的;
  • 它們的演算法可能會是最優的,這樣就跑地更快;
  • 抽象化:它們指向明確而且文件友好,你可以專注於那些還沒有被實現的;
  • 最後,它都已經在那兒了,你不用再造輪子了。

在列表中切片/步進

你可以指定 start 的點和 stop 的點,就像這樣 list[start:stop:step]。我們去除列表中前5個元素:

    bag = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    for elem in bag[:5]:
        print(elem)

這就是切片,我們指定 stop 點是5,在停止前就會從列表中取出5個元素。

要去最後5個元素怎麼做?

    bag = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    for elem in bag[-5:]:
        print(elem)

-5 意味著從列表末尾取出5個元素。

如果你想對列表中元素間隔操作,你可能會這樣做:

    bag = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    for index, elem in enumerate(bag):
        if index % 2 == 0:
            print(elem)

但是你應該這樣來做:

    bag = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    for elem in bag[::2]:
        print(elem)
    
    # 或者用 ranges
    bag = list(range(0, 10, 2))
    print(bag)

這就是列表中的步進,list[::2] 意思是遍歷列表同時兩步取出一個元素。

你可以用 list[::-1] 很酷地翻轉列表。


怎麼樣,是不是和我一樣感受到了 Python 程式碼的優美?