今天我們繼續分享tips 11~15
。
當想尋找在a裡卻不在b裡的元素時,一般會寫成# 11a
。如果iterable_a
是有序的且順序需要維持的,或許這是個不錯的方法。
# 11a
iterable_a = range(1, 5)
iterable_b = range(3, 10)
if __name__ == '__main__':
result = [a
for a in iterable_a
if a not in iterable_b]
print(f'{result=}') # result=[1, 2]
如果iterable_b
的數量很大,或是有重複的元素,# 11a
可以改成# 11b
,這麼一來in
的速度可以加快,也不會有重複的元素。
# 11b
iterable_a = range(1, 5)
iterable_b = range(3, 10)
if __name__ == '__main__':
result = [a
for a in iterable_a
if a not in set(iterable_b)]
print(f'{result=}') # result=[1, 2]
不過大多數情況是,我們不在意兩邊的元素順序及是否有重複元素,這時可以直接用兩個set
相減,如# 11c
。
# 11c
iterable_a = range(1, 5)
iterable_b = range(3, 10)
if __name__ == '__main__':
result = set(iterable_a) - set(iterable_b)
print(f'{result=}') # result={1, 2}
當想找出一個集合內,其值為某幾個數時,一般會寫成# 12a
。如果if
後接的條件只有少數幾個,我們會覺得這是個ok的寫法。
# 12a
iterable = range(20)
if __name__ == '__main__':
result = [item
for item in iterable
if item == 3 or item == 7 or item == 15]
print(f'{result=}') # result=[3, 7, 15]
但是如果條件比較多的話,我們會試著看看能不能改成# 12b
的寫法。這麼一來,程式看起來就不會太冗長,而且in wanted
會比多個if
更有效率。
# 12b
iterable = range(20)
if __name__ == '__main__':
wanted = {3, 7, 15}
result = [item
for item in iterable
if item in wanted]
print(f'{result=}') # result=[3, 7, 15]
在比較舊版本的Python,當想要取得有些物件的type
時,需要先對該物件使用type
。例如想取得None
的type
,必須這麼做:
# 13
none_type1 = type(None)
雖然這方式可行,但總覺得好像有點旁門左道。這就像您會直接使用int
,而不會使用像int_type = type(1)
的語法來取得int
這個type
一樣。
新版Python的types
模組,支援了各式各樣的type
,讓我們可以直接取用。現在如果想要取得None
的type
,建議改成這麼做:
# 13
import types
none_type2 = types.NoneType
我們可以驗證兩種方法是一樣的:
# 13
print(none_type1 is none_type2) # True
當我們有很多筆資料想要整理成dict
,但其中作為key
的元素,卻可能重複時,我們一般會希望將value
作為一個container
,收集該key
的每筆資訊。
# 14a
是一個常見的寫法,但是我們覺得這樣的寫法不太好。原因是當if name not in dict1
時,總共做了兩件事,建立[]
以及append value
,而在else
裡,只做append value
。既然append value
是兩邊共同會做的事,為何不抽出來呢?
# 14a
data = [('a', 1), ('b', 2), ('c', 3), ('a', 4)]
if __name__ == '__main__':
d = {}
for name, value in data:
if name not in d:
d[name] = [value]
else:
d[name].append(value)
所以我們會傾向# 14b
這樣的寫法。
# 14b
data = [('a', 1), ('b', 2), ('c', 3), ('a', 4)]
if __name__ == '__main__':
d = {}
for name, value in data:
if name not in d:
d[name] = []
d[name].append(value)
此外Python的collections.defaultdict
似乎更適合處理這類問題。# 14c
也是屬於常見的作法。
# 14c
from collections import defaultdict
data = [('a', 1), ('b', 2), ('c', 3), ('a', 4)]
if __name__ == '__main__':
d = defaultdict(list)
for name, value in data:
d[name].append(value)
最後,我們想分享一個比較冷門的寫法,就是直接使用dict.setdefault
,如# 14d
。第一次用可能會覺得有點怪。但是寫久了之後,發現相比於defaultdict
要先import,再產生defaultdict
instance
才能使用,直接生成dict
然後連續兩個.(dot)
好像更一氣呵成。
# 14d
data = [('a', 1), ('b', 2), ('c', 3), ('a', 4)]
if __name__ == '__main__':
d = {}
for name, value in data:
d.setdefault(name, []).append(value)
Python提供了非常方便來取得attribute
的方法,就是我們習慣的.(dot)
。但是當這個attribute
名字是動態取得的,也就是無法在使用.(dot)
前就知道時,就必須使用getattr。
一個最常見的應用就是實作descriptor
。# 15a
中,__set_name__
會自動傳遞使用者於class
中命名此NonDataDesc
instance
的名字,這麼一來這個名字是動態產生的,我們必須使用getattr
,而無法使用類似instance.self._name
語法來取值。
# 15a
class NonDataDesc:
def __get__(self, instance, owner_cls):
if instance is None:
return self
return getattr(instance, self._name)
def __set_name__(self, owner_cls, name):
self._name = f'_{name}'
或許您會覺得這只是因為instance.self._name
中有兩個.(dot)
,但事實上,即便用一個中間變數來取代self._name
也不會成功。# 15b
中我們先將self._name
指給一個變數my_name
,然後返回instance.my_name
。這是一個錯誤的descriptor
實作,因為instance.my_name
只是請Python幫我們去找my_name
這個attribute
,而不是動態由使用者給定的名字。
# 15b WRONG CODE!!!
class NonDataDesc:
def __get__(self, instance, owner_cls):
if instance is None:
return self
my_name = self._name
return instance.my_name
def __set_name__(self, owner_cls, name):
self._name = f'_{name}'
此外,getattr
可以指定第三個參數,為找不到attribute
時的回傳值。