1. 不要使用可變物件作為函式預設值
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
In [1]: def append_to_list(value, def_list=[]): ...: def_list.append(value) ...: return def_list ...: In [2]: my_list = append_to_list(1) In [3]: my_list Out[3]: [1] In [4]: my_other_list = append_to_list(2) In [5]: my_other_list Out[5]: [1, 2] # 看到了吧,其實我們本來只想生成[2] 但是卻把第一次執行的效果頁帶了進來 In [6]: import time In [7]: def report_arg(my_default=time.time()): ...: print(my_default) ...: In [8]: report_arg() # 第一次執行 1399562371.32 In [9]: time.sleep(2) # 隔了2秒 In [10]: report_arg() 1399562371.32 # 時間竟然沒有變 |
這2個例子說明了什麼? 字典,集合,列表等等物件是不適合作為函式預設值的. 因為這個預設值實在函式建立的時候就生成了, 每次呼叫都是用了這個物件的”快取”. 我在上段時間的分享python高階程式設計也說到了這個問題,這個是實際開發遇到的問題,好好檢查你學過的程式碼, 也許只是問題沒有暴露。
可以這樣改,程式碼如下:
1 2 3 4 5 |
def append_to_list(element, to=None): if to is None: to = [] to.append(element) return to |
2. 生成器不保留迭代過後的結果
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
In [12]: gen = (i for i in range(5)) In [13]: 2 in gen Out[13]: True In [14]: 3 in gen Out[14]: True In [15]: 1 in gen Out[15]: False # 1為什麼不在gen裡面了? 因為呼叫1->2,這個時候1已經不在迭代器裡面了,被按需生成過了 In [20]: gen = (i for i in range(5)) In [21]: a_list = list(gen) # 可以轉化成列表,當然a_tuple = tuple(gen) 也可以 In [22]: 2 in a_list Out[22]: True In [23]: 3 in a_list Out[23]: True In [24]: 1 in a_list # 就算迴圈過,值還在 Out[24]: True |
3. lambda在閉包中會儲存區域性變數
程式碼如下:
1 2 3 4 5 6 7 |
In [29]: my_list = [lambda: i for i in range(5)] In [30]: for l in my_list: ....: print(l()) ....: 4 4 |
這個問題還是上面說的python高階程式設計中說過具體原因. 其實就是當我賦值給my_list的時候,lambda表示式就執行了i會迴圈,直到 i =4,i會保留
但是可以用生成器,程式碼如下:
1 2 3 4 5 6 7 8 9 |
In [31]: my_gen = (lambda: n for n in range(5)) In [32]: for l in my_gen: ....: print(l()) ....: 1 2 3 4 |
也可以堅持用list,程式碼如下:
1 2 3 4 5 6 7 8 9 10 |
In [33]: my_list = [lambda x=i: x for i in range(5)] # 看我給每個lambda表示式賦了預設值 In [34]: for l in my_list: ....: print(l()) ....: 0 1 2 3 4 |
有點不好懂是吧,在看看python的另外一個魔法,程式碼如下:
1 2 3 4 5 6 |
In [35]: def groupby(items, size): ....: return zip(*[iter(items)]*size) ....: In [36]: groupby(range(9), 3) Out[36]: [(0, 1, 2), (3, 4, 5), (6, 7, 8)] |
一個分組的函式,看起來很不好懂,對吧? 我們來解析下這裡
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
In [39]: [iter(items)]*3 Out[39]: [<listiterator at 0x10e155fd0>, <listiterator at 0x10e155fd0>, <listiterator at 0x10e155fd0>] # 看到了吧, 其實就是把items變成可迭代的, 重複三回(同一個物件哦), 但是別忘了,每次都.next(), 所以起到了分組的作用 In [40]: [lambda x=i: x for i in range(5)] Out[40]: [<function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>] # 看懂了嗎? |
4. 在迴圈中修改列表項
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
In [44]: a = [1, 2, 3, 4, 5] In [45]: for i in a: ....: if not i % 2: ....: a.remove(i) ....: In [46]: a Out[46]: [1, 3, 5] # 沒有問題 In [50]: b = [2, 4, 5, 6] In [51]: for i in b: ....: if not i % 2: ....: b.remove(i) ....: In [52]: b Out[52]: [4, 5] # 本來我想要的結果應該是去除偶數的列表 |
思考一下,為什麼 – 是因為你對列表的remove,影響了它的index
程式碼如下:
1 2 3 4 5 6 7 8 9 10 |
In [53]: b = [2, 4, 5, 6] In [54]: for index, item in enumerate(b): ....: print(index, item) ....: if not item % 2: ....: b.remove(item) ....: (0, 2) # 這裡沒有問題 2被刪除了 (1, 5) # 因為2被刪除目前的列表是[4, 5, 6], 所以索引list[1]直接去找5, 忽略了4 (2, 6) |
5. IndexError – 列表取值超出了他的索引數
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
In [55]: my_list = [1, 2, 3, 4, 5] In [56]: my_list[5] # 根本沒有這個元素 --------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-56-037d00de8360> in <module>() ----> 1 my_list[5] IndexError: list index out of range # 拋異常了 In [57]: my_list[5:] # 但是可以這樣, 一定要注意, 用好了是trick,用錯了就是坑啊 Out[57]: [] |
6. 重用全域性變數
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
In [58]: def my_func(): ....: print(var) # 我可以先呼叫一個未定義的變數 ....: In [59]: var = 'global' # 後賦值 In [60]: my_func() # 反正只要呼叫函式時候變數被定義了就可以了 global In [61]: def my_func(): ....: var = 'locally changed' ....: In [62]: var = 'global' In [63]: my_func() In [64]: print(var) global # 區域性變數沒有影響到全域性變數 In [65]: def my_func(): ....: print(var) # 雖然你全域性設定這個變數, 但是區域性變數有同名的, python以為你忘了定義本地變數了 ....: var = 'locally changed' ....: In [66]: var = 'global' In [67]: my_func() --------------------------------------------------------------------------- UnboundLocalError Traceback (most recent call last) <ipython-input-67-d82eda95de40> in <module>() ----> 1 my_func() <ipython-input-65-0ad11d690936> in my_func() 1 def my_func(): ----> 2 print(var) 3 var = 'locally changed' 4 UnboundLocalError: local variable 'var' referenced before assignment In [68]: def my_func(): ....: global var # 這個時候得加全域性了 ....: print(var) # 這樣就能正常使用 ....: var = 'locally changed' ....: In [69]: var = 'global' In [70]: In [70]: my_func() global In [71]: print(var) locally changed # 但是使用了global就改變了全域性變數 |
7. 拷貝可變物件
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
In [72]: my_list1 = [[1, 2, 3]] * 2 In [73]: my_list1 Out[73]: [[1, 2, 3], [1, 2, 3]] In [74]: my_list1[1][0] = 'a' # 我只修改子列表中的一項 In [75]: my_list1 Out[75]: [['a', 2, 3], ['a', 2, 3]] # 但是都影響到了 In [76]: my_list2 = [[1, 2, 3] for i in range(2)] # 用這種迴圈生成不同物件的方法就不影響了 In [77]: my_list2[1][0] = 'a' In [78]: my_list2 Out[78]: [[1, 2, 3], ['a', 2, 3]] |
8. python多繼承
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
In [1]: class A(object): ...: def foo(self): ...: print("class A") ...: In [2]: class B(object): ...: def foo(self): ...: print("class B") ...: In [3]: class C(A, B): ...: pass ...: In [4]: C().foo() class A # 例子很好懂, C繼承了A和B,從左到右,發現A有foo方法,返回了 |
看起來都是很簡單, 有次序的從底向上,從前向後找,找到就返回. 再看例子:
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
In [5]: class A(object): ...: def foo(self): ...: print("class A") ...: In [6]: class B(A): ...: pass ...: In [7]: class C(A): ...: def foo(self): ...: print("class C") ...: In [8]: class D(B,C): ...: pass ...: In [9]: D().foo() class C # ? 按道理, 順序是 D->B->A,為什麼找到了C哪去了 |
這也就涉及了MRO(Method Resolution Order):
程式碼如下:
1 2 |
In [10]: D.__mro__ Out[10]: (__main__.D, __main__.B, __main__.C, __main__.A, object) |
MRO的演算法有點小複雜,既不是深度優先,也不是廣度優先
9. 列表的+和+=, append和extend
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
In [17]: print('ID:', id(a_list)) ('ID:', 4481323592) In [18]: a_list += [1] In [19]: print('ID (+=):', id(a_list)) ('ID (+=):', 4481323592) # 使用+= 還是在原來的列表上操作 In [20]: a_list = a_list + [2] In [21]: print('ID (list = list + ...):', id(a_list)) ('ID (list = list + ...):', 4481293056) # 簡單的+其實已經改變了原有列表 In [28]: a_list = [] In [29]: id(a_list) Out[29]: 4481326976 In [30]: a_list.append(1) In [31]: id(a_list) Out[31]: 4481326976 # append 是在原有列表新增 In [32]: a_list.extend([2]) In [33]: id(a_list) Out[33]: 4481326976 # extend 也是在原有列表上新增 |
10. datetime也有布林值
這是一個坑,程式碼如下:
1 2 3 4 5 6 7 |
In [34]: import datetime In [35]: print('"datetime.time(0,0,0)" (Midnight) ->', bool(datetime.time(0,0,0))) ('"datetime.time(0,0,0)" (Midnight) ->', False) In [36]: print('"datetime.time(1,0,0)" (1 am) ->', bool(datetime.time(1,0,0))) ('"datetime.time(1,0,0)" (1 am) ->', True) |
11. ‘==’ 和 is 的區別
我的理解是”is”是判斷2個物件的身份, ==是判斷2個物件的值,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
In [37]: a = 1 In [38]: b = 1 In [39]: print('a is b', bool(a is b)) ('a is b', True) In [40]: c = 999 In [41]: d = 999 In [42]: print('c is d', bool(c is d)) ('c is d', False) # 原因是python的記憶體管理,快取了-5 - 256的物件 In [43]: print('256 is 257-1', 256 is 257-1) ('256 is 257-1', True) In [44]: print('257 is 258-1', 257 is 258 - 1) ('257 is 258-1', False) In [45]: print('-5 is -6+1', -5 is -6+1) ('-5 is -6+1', True) In [46]: print('-7 is -6-1', -7 is -6-1) ('-7 is -6-1', False) In [47]: a = 'hello world!' In [48]: b = 'hello world!' In [49]: print('a is b,', a is b) ('a is b,', False) # 很明顯 他們沒有被快取,這是2個欄位串的物件 In [50]: print('a == b,', a == b) ('a == b,', True) # 但他們的值相同 # But, 有個特例 In [51]: a = float('nan') In [52]: print('a is a,', a is a) ('a is a,', True) In [53]: print('a == a,', a == a) ('a == a,', False) # 亮瞎我眼睛了~ |
12. 淺拷貝和深拷貝
我們在實際開發中都可以向對某列表的物件做修改,但是可能不希望改動原來的列表. 淺拷貝只拷貝父物件,深拷貝還會拷貝物件的內部的子物件,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
In [65]: list1 = [1, 2] In [66]: list2 = list1 # 就是個引用, 你操作list2,其實list1的結果也會變 In [67]: list3 = list1[:] In [69]: import copy In [70]: list4 = copy.copy(list1) # 他和list3一樣 都是淺拷貝 In [71]: id(list1), id(list2), id(list3), id(list4) Out[71]: (4480620232, 4480620232, 4479667880, 4494894720) In [72]: list2[0] = 3 In [73]: print('list1:', list1) ('list1:', [3, 2]) In [74]: list3[0] = 4 In [75]: list4[1] = 4 In [76]: print('list1:', list1) ('list1:', [3, 2]) # 對list3和list4操作都沒有對list1有影響 # 再看看深拷貝和淺拷貝的區別 In [88]: from copy import copy, deepcopy In [89]: list1 = [[1], [2]] In [90]: list2 = copy(list1) # 還是淺拷貝 In [91]: list3 = deepcopy(list1) # 深拷貝 In [92]: id(list1), id(list2), id(list3) Out[92]: (4494896592, 4495349160, 4494896088) In [93]: list2[0][0] = 3 In [94]: print('list1:', list1) ('list1:', [[3], [2]]) # 看到了吧 假如你操作其子物件 還是和引用一樣 影響了源 In [95]: list3[0][0] = 5 In [96]: print('list1:', list1) ('list1:', [[3], [2]]) # 深拷貝就不會影響 |
13. bool其實是int的子類
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
In [97]: isinstance(True, int) Out[97]: True In [98]: True + True Out[98]: 2 In [99]: 3 * True + True Out[99]: 4 In [100]: 3 * True - False Out[100]: 3 In [104]: True << 10 Out[104]: 1024 |
14. 元組是不是真的不可變?
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
In [111]: tup = ([],) In [112]: tup[0] += [1] --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-112-d4f292cf35de> in <module>() ----> 1 tup[0] += [1] TypeError: 'tuple' object does not support item assignment In [113]: tup Out[113]: ([1],) # 我靠 又是亮瞎我眼睛,明明拋了異常 還能修改? In [114]: tup = ([],) In [115]: tup[0].extend([1]) In [116]: tup[0] Out[116]: [1] # 好吧,我有點看明白了, 雖然我不能直接操作元組,但是不能阻止我操作元組中可變的子物件(list) |
這裡有個不錯的解釋Python’s += Is Weird, Part II :
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
In [117]: my_tup = (1,) In [118]: my_tup += (4,) In [119]: my_tup = my_tup + (5,) In [120]: my_tup Out[120]: (1, 4, 5) # ? 嗯 不是不能操作元組嘛? In [121]: my_tup = (1,) In [122]: print(id(my_tup)) 4481317904 In [123]: my_tup += (4,) In [124]: print(id(my_tup)) 4480606864 # 操作的不是原來的元組 所以可以 In [125]: my_tup = my_tup + (5,) In [126]: print(id(my_tup)) 4474234912 |
15. python沒有私有方法/變數? 但是可以有”偽”的
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
In [127]: class my_class(object^E): .....: def public_method(self): .....: print('Hello public world!') .....: def __private_method(self): # 私有以雙下劃線開頭 .....: print('Hello private world!') .....: def call_private_method_in_class(self): .....: self.__private_method() In [132]: my_instance = my_class() In [133]: my_instance.public_method() Hello public world! # 普通方法 In [134]: my_instance._my_class__private_method() Hello private world! # 私有的可以加"_ + 類名字 + 私有方法名字” In [135]: my_instance.call_private_method_in_class() Hello private world! # 還可以通過類提供的公有介面內部訪問 In [136]: my_instance._my_class__private_variable Out[136]: 1 |
16. 異常處理加else
程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
In [150]: try: .....: print('third element:', a_list[2]) .....: except IndexError: .....: print('raised IndexError') .....: else: .....: print('no error in try-block') # 只有在try裡面沒有異常的時候才會執行else裡面的表示式 .....: raised IndexError # 拋異常了 沒完全完成 In [153]: i = 0 In [154]: while i < 2: .....: print(i) .....: i += 1 .....: else: .....: print('in else') .....: 0 1 in else # while也支援哦~ In [155]: i = 0 In [156]: while i < 2: .....: print(i) .....: i += 1 .....: break .....: else: .....: print('completed while-loop') .....: 0 # 被break了 沒有完全執行完 就不執行else裡面的了 In [158]: for i in range(2): .....: print(i) .....: else: .....: print('completed for-loop') .....: 0 1 completed for-loop In [159]: for i in range(2): .....: print(i) .....: break .....: else: .....: print('completed for-loop') .....: 0 # 也是因為break了 |