天天看點

Python裡的那些坑

Python是一門清晰簡潔的語言,如果你對一些細節不了解的話,就會掉入到那些深不見底的“坑”裡,下面,我就來總結一些Python裡常見的坑。

清單建立和引用

嵌套清單的建立

  • 使用*号來建立一個嵌套的list:
    li = [[]] * 3
    print(li)
    # Out: [[], [], []]
               
  • 通過這個方法,可以得到一個包含3個list的嵌套list,我們來給第一個list增加一個元素:
    li[0].append(1)
    print(li)
    # Out: [[1], [1], [1]]
               
  • 通過輸出的結果可以看初,我們隻給第一進制素增加元素,結果三個list都增加了一個元素。這是因為[[]]*3并不是建立了三個不同list,而是建立了三個指向同一個list的對象,是以,當我們操作第一個元素時,其他兩個元素内容也會發生變化的原因。效果等同于下面這段代碼:
    li = []
    element = [[]]
    li = element + element + element
    print(li)
    # Out: [[], [], []]
    element.append(1)
    print(li)
    # Out: [[1], [1], [1]]
               
  • 我們可以列印出元素的記憶體位址一探究竟:
    li = [[]] * 3
    print([id(inner_list) for inner_list in li])
    # Out: [6830760, 6830760, 6830760]
               
  • 到這我們可以明白原因了。那如何解決了?可以這樣:
    li = [[] for _ in range(3)]
               
  • 這樣我們就建立了三個不同的list對象
    print([id(inner_list) for inner_list in li])
    # Out: [6331048, 6331528, 6331488]
               

清單元素的引用

  • 不要使用索引方法周遊list,例如:
    for i in range(len(tab)):
        print(tab[i])
               

比較好的方法是:

for elem in tab:
    print(elem)
           

for語句會自動生成一個疊代器。如果你需要索引位置和元素,使用enumerate函數:

for i, elem in enumerate(tab):
        print((i, elem))
           

注意 == 符号的使用

if (var == True):
        # 當var是:True、1、 1.0、 1L時if條件成立

    if (var != True):
        # 當var不是 True 和 1 時if條件成立

    if (var == False):
        # 當var是 False 或者 0 (or 0.0, 0L, 0j) if條件成立

    if (var == None):
        # var是None if條件成立

    if var:
        # 當var非空(None或者大小為0)對象 string/list/dictionary/tuple, non-0等if條件成立

    if not var:
        # 當var空(None或者大小為0)對象 string/list/dictionary/tuple, non-0等if條件成立


    if var is True:
        # 隻有當var時True時 if條件成立 1也不行

    if var is False:
        # 隻有當var時False時 if條件成立 0也不行

    if var is None:
    # 和var == None 一緻
           

捕獲異常由于提前檢查

  • 不夠優雅的代碼:
    if os.path.isfile(file_path):
        file = open(file_path)
    else:
        # do something
               

比較好的做法:

try:
        file = open(file_path)
    except OSError as e:
        # do something
           

在python2.6+的裡面可以更簡潔:

with open(file_path) as file:
           

之是以這麼用,是這麼寫更加通用,比如file_path給你傳個None就瞎了,還得判斷是不是None,如果不判斷,就又得抓異常,判斷的話,代碼有多寫了很多。

類變量初始化

  • 不要在對象的init函數之外初始化類屬性,主要有兩個問題
    • 如果類屬性更改,則初始值更改。
    • 如果将可變對象設定為預設值,您将獲得跨執行個體共享的相同對象。
    錯誤示範(除非你想要靜态變量)
    class Car(object):
        color = "red"
        wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
               
    正确的做法:
    class Car(object):
        def __init__(self):
            self.color = "red"
            self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
               

函數預設參數

def foo(li=[]):
    li.append(1)
    print(li)

foo([2])
# Out: [2, 1]
foo([3])
# Out: [3, 1]
           

該代碼的行為與預期的一樣,但如果我們不傳遞參數呢?

foo()
# Out: [1] As expected...

foo()
# Out: [1, 1]  Not as expected...
           

這是因為函數參數類型是定義是确認的而不是運作時,是以在兩次函數調用時,li指向的是同一個list對象,如果要解決這個問題,可以這樣:

def foo(li=None):
    if not li:
        li = []
    li.append(1)
    print(li)

foo()
# Out: [1]

foo()
# Out: [1]
           

這雖然解決了上述的問題,但,其他的一些對象,比如零長度的字元串,輸出的結果就不是我們想要的。

x = []
foo(li=x)
# Out: [1]

foo(li="")
# Out: [1]

foo(li=0) 
# Out: [1]
           

最常用的辦法是檢查參數是不是None

def foo(li=None):
    if li is None:
        li = []
    li.append(1)
    print(li)

foo()
# Out: [1]
           

在周遊時修改

  • for語句在周遊對象是會生成一個疊代器,如果你在周遊的過程中修改對象,會産生意想不到的結果:
    alist = [0, 1, 2]
    for index, value in enumerate(alist):
        alist.pop(index)
    print(alist)
    # Out: [1]
               
    第二個元素沒有被删除,因為疊代按順序周遊索引。上述循環周遊兩次,結果如下:
    # Iteration #1
    index = 0
    alist = [0, 1, 2]
    alist.pop(0) # removes '0'
    
    # Iteration #2
    index = 1
    alist = [1, 2]
    alist.pop(1) # removes '2'
    
    # loop terminates, but alist is not empty:
    alist = [1]
               
    如果避免這個問題了,可以建立另外一個list
    alist = [1,2,3,4,5,6,7]
    for index, item in reversed(list(enumerate(alist))):
        # delete all even items
        if item % 2 == 0:
            alist.pop(index)
    print(alist)
    # Out: [1, 3, 5, 7]
               

整數和字元串定義

  • python預先緩存了一個區間的整數用來減少記憶體的操作,但也正是如此,有時候會出很奇特的錯誤,例如:
    >>> -8 is (-7 - 1)
    False
    >>> -3 is (-2 - 1)
    True
               
    另外一個例子
    >>> (255 + 1) is (255 + 1)
    True
    >>> (256 + 1) is (256 + 1)
    False
               
    通過不斷的測試,會發現(-3,256)這區間的整數都傳回True,有的甚至是(-8,257)。預設情況下,[-5,256]會在解釋器第一次啟動時建立并緩存,是以才會有上面的奇怪的行為。這是個很常見但很容易被忽略的一個坑。解決方案是始終使用equality(==)運算符而不是 identity(is)運算符比較值。
  • Python還保留對常用字元串的引用,并且可以在比較is字元串的身份(即使用)時産生類似的混淆行為。
    >>> 'python' is 'py' + 'thon'
    True
               
    python字元串被緩存了,所有python字元串都是該對象的引用,對于不常見的字元串,即使字元串相等,比較身份也會失敗。
    >>> 'this is not a common string' is 'this is not' + ' a common string'
    False
    >>> 'this is not a common string' == 'this is not' + ' a common string'
    True
               
    是以,就像整數規則一樣,總是使用equal(==)運算符而不是 identity(is)運算符比較字元串值。

清單推導和循環中的變量洩漏

  • 有個例子:
    i = 0
    a = [i for i in range(3)]
    print(i) # Outputs 2
               
    python2中清單推導改變了i變量的值,而python3修複了這個問題:
    i = 0
    a = [i for i in range(3)]
    print(i) # Outputs 0
               
    類似地,for循環對于它們的疊代變量沒有私有的作用域
    i = 0
    for i in range(3):
        pass
    print(i) # Outputs 2
               

    這種行為發生在Python 2和Python 3中。

    為了避免洩漏變量的問題,請在清單推導和for循環中使用新的變量。

or操作符

  • 例如
    if a == 3 or b == 3 or c == 3:
               
    這個很簡單,但是,再看一個:
    if a or b or c == 3: # Wrong
               
    這是由于or的優先級低于==,是以表達式将被評估為if (a) or (b) or (c == 3):。正确的方法是明确檢查所有條件:
    if a == 3 or b == 3 or c == 3:  # Right Way
               
    或者,可以使用内置函數any()代替連結or運算符:
    if any([a == 3, b == 3, c == 3]): # Right
               
    或者,為了使其更有效率:
    if any(x == 3 for x in (a, b, c)): # Right
               
    更加簡短的寫法:
    if 3 in (a, b, c): # Right
               

    轉載自我的部落格:http://www.bugcode.cn/Python%20Pitfalls.html