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 程式碼的優美?