Python 的簡潔語法雖然方便,但容易造成過於複雜的單行表示式,降低可讀性。尤其在處理 URL 引數解析時,多值、單值、空值和缺失值的情況使得使用 get 方法和布林表示式變得難以維護。例如,處理 red=5&blue=0&green= 這樣的查詢字串時,直接使用 or 運算元來設定預設值,雖然簡潔,卻難以理解。即使用 if/else 條件表示式,在複雜的邏輯下也顯得不夠清晰。最佳的解決方案是編寫輔助函式,例如 get_first_int,將複雜的邏輯封裝起來,使呼叫程式碼更簡潔易懂。除了輔助函式,Python 的切片操作也是簡化程式碼的利器,可以有效地擷取序列的子集合,例如 somelist[start:end],其中 start 包含在內,end 不包含在內。合理使用切片,可以避免不必要的索引操作,提升程式碼效率和可讀性。
Pythonic之道:告別複雜表示式,善用輔助函式
Python以其簡潔的語法著稱,但也因此容易讓人寫出過於複雜的單行表示式,反而降低程式碼的可讀性。今天,玄貓想和大家聊聊,如何避免這種情況,並善用輔助函式來提升程式碼品質。
URL引數解析的陷阱
假設我們需要從URL中解析查詢字串,這些引數代表整數值。使用urllib.parse.parse_qs可以輕鬆完成:
from urllib.parse import parse_qs
my_values = parse_qs('red=5&blue=0&green=', keep_blank_values=True)
print(repr(my_values))
輸出結果會是:
{'red': ['5'], 'green': [''], 'blue': ['0']}
但問題來了,查詢字串中的引數可能有多個值、單個值、空白值,甚至完全缺失。使用get方法會因為這些情況而產生不同的結果。
print('Red: ', my_values.get('red'))
print('Green: ', my_values.get('green'))
print('Opacity: ', my_values.get('opacity'))
輸出:
Red: ['5']
Green: ['']
Opacity: None
理想情況下,我們希望在引數缺失或為空時,預設值為0。很多人會選擇使用布林表示式,因為感覺這種邏輯不值得寫一個完整的if陳述式或輔助函式。
or表示式的困境
Python的語法讓這種選擇變得太容易。空字串、空列表和0在布林上下文中都會被視為False。因此,以下表達式在第一個子表示式為False時,會傳回or運算元後面的子表示式。
# 查詢字串為 'red=5&blue=0&green='
red = my_values.get('red', [''])[0] or 0
green = my_values.get('green', [''])[0] or 0
opacity = my_values.get('opacity', [''])[0] or 0
print('Red: %r' % red)
print('Green: %r' % green)
print('Opacity: %r' % opacity)
結果如下:
Red: '5'
Green: 0
Opacity: 0
red的情況是因為my_values字典中存在red鍵,其值為包含字串'5'的列表。這個字串在布林上下文中為True,所以red被指定為'5'。
green的情況是因為green鍵的值為包含空字串的列表。空字串在布林上下文中為False,所以or表示式傳回0。
opacity的情況是因為my_values字典中完全沒有opacity鍵。get方法在鍵不存在時會傳回預設值,這裡預設值是包含空字串的列表。因此,opacity的處理方式與green相同。
然而,這種表示式難以閱讀,而與還沒完成所有需求。我們還需要確保所有引數值都是整數,才能用於數學運算。所以,我們需要用int函式包裹整個表示式:
red = int(my_values.get('red', [''])[0] or 0)
現在,程式碼變得極其難以閱讀,充斥著視覺噪音,讓人難以理解其功能。即使程式碼簡短,也不值得犧牲可讀性。
if/else條件表示式的權衡
Python 2.5 引入了if/else條件表示式(或稱三元運算元),可以在保持程式碼簡短的同時,提高可讀性:
red = my_values.get('red', [''])
red = int(red[0]) if red[0] else 0
這種方式有所改善。對於不太複雜的情況,if/else條件表示式可以使程式碼更清晰。但上述例子仍然不如完整的if/else陳述式清晰。將所有邏輯分散開來,反而讓原本密集的版本顯得更加複雜。
green = my_values.get('green', [''])
if green[0]:
green = int(green[0])
else:
green = 0
輔助函式的優雅
如果需要重複使用這種邏輯,最好的方法是編寫一個輔助函式:
def get_first_int(values, key, default=0):
found = values.get(key, [''])
if found[0]:
found = int(found[0])
else:
found = default
return found
現在,呼叫程式碼變得更加清晰:
green = get_first_int(my_values, 'green')
一旦表示式變得複雜,就應該考慮將其拆分成更小的部分,並將邏輯移至輔助函式中。可讀性的提升永遠勝過簡潔性所帶來的好處。不要讓Python簡潔的語法把你帶入複雜表示式的泥潭。
序列切片技巧
Python 包含了將序列分割成片段的語法,稱為切片(slicing)。透過切片,你可以輕鬆存取序列中專案的子集合。切片最常被應用於內建的 list、str 和 bytes 型別。此外,切片也可以擴充套件到任何實作了 __getitem__ 和 __setitem__ 特殊方法的 Python 類別。
切片語法的基本形式是 somelist[start:end],其中 start 是包含的,而 end 是不包含的。
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
print('First four:', a[:4])
print('Last four: ', a[-4:])
print('Middle two:', a[3:-3])
輸出結果:
First four: ['a', 'b', 'c', 'd']
Last four: ['e', 'f', 'g', 'h']
Middle two: ['d', 'e']
從列表開頭進行切片時,應省略零索引,以減少視覺上的雜亂。
assert a[:5] == a[0:5]
切片到列表結尾時,應省略最後一個索引,因為它是多餘的。
assert a[5:] == a[5:len(a)]
使用負數進行切片有助於執行相對於列表結尾的偏移。