Python程序員寫代碼時應該避免的16個「坑」

一、 不要使用可變對象做爲函數默認值

代碼以下:python

In [1]: def append_to_list(value, def_list=[]):算法

   ...:         def_list.append(value)編程

   ...:         return def_list緩存

   ...:閉包

In [2]: my_list = append_to_list(1)app

In [3]: my_list函數

Out[3]: [1]oop

In [4]: my_other_list = append_to_list(2)spa

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高級編程也說到了這個問題,這個是實際開發遇到的問題,好好檢查你學過的代碼,也許只是問題沒有暴露。

能夠這樣改,代碼以下:

def append_to_list(element, to=None):

    if to is None:

        to = []

    to.append(element)

    return to

二、 生成器不保留迭代事後的結果

代碼以下:

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

三、 lambda在閉包中會保存局部變量

代碼以下:

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會保留

可是能夠用生成器,代碼以下:

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,代碼以下:

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的另一個魔法,代碼以下:

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)]

一個分組的函數,看起來很很差懂,對吧?咱們來解析下這裏

代碼以下:

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>>] # 看懂了嗎?

四、 在循環中修改列表項

代碼以下:

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

代碼以下:

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)

五、  IndexError – 列表取值超出了他的索引數

代碼以下:

In [55]: my_list = [1, 2, 3, 4, 5]

In [56]: my_list[5] # 根本沒有這個元素

---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

&lt;ipython-input-56-037d00de8360&gt; in &lt;module&gt;()

----&gt; 1 my_list[5]

IndexError: list index out of range # 拋異常了

In [57]: my_list[5:] # 可是能夠這樣, 必定要注意, 用好了是trick,用錯了就是坑啊

Out[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就改變了全局變量

七、 拷貝可變對象

代碼以下:

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]]

八、 python多繼承

代碼以下:

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方法,返回了

看起來都是很簡單,有次序的從底向上,從前向後找,找到就返回。再看例子:

代碼以下:

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):

代碼以下:

In [10]: D.__mro__

Out[10]: (__main__.D, __main__.B, __main__.C, __main__.A, object)

MRO的算法有點小複雜,既不是深度優先,也不是廣度優先。

九、 列表的+和+=, append和extend

代碼以下:

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 也是在原有列表上添加

十、 datetime也有布爾值

這是一個坑,代碼以下:

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)

十一、  ‘==’ 和 is 的區別 

個人理解是「s」是判斷2個對象的身份, ==是判斷2個對象的值,代碼以下:

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) # 亮瞎我眼睛了~

十二、 淺拷貝和深拷貝

咱們在實際開發中均可以向對某列表的對象作修改,可是可能不但願改動原來的列表. 淺拷貝只拷貝父對象,深拷貝還會拷貝對象的內部的子對象,代碼以下:

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]]) # 深拷貝就不會影響

1三、 bool實際上是int的子類

代碼以下:

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

1四、 元組是否是真的不可變?

代碼以下:

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:

代碼以下:

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

1五、 python沒有私有方法/變量? 可是能夠有」僞」的

代碼以下:

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

1六、 異常處理加else

代碼以下:

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了

相關文章
相關標籤/搜索