Python裏的那些坑

Python裏的那些坑

  • Python是一門清晰簡潔的語言,若是你對一些細節不瞭解的話,就會掉入到那些深不見底的「坑」裏,下面,我就來總結一些Python裏常見的坑。html

列表建立和引用

嵌套列表的建立

  • 使用*號來建立一個嵌套的list:python

li = [[]] * 3
        print(li)
        # Out: [[], [], []]
  • 經過這個方法,能夠獲得一個包含3個list的嵌套list,咱們來給第一個list增長一個元素:緩存

li[0].append(1)
        print(li)
        # Out: [[1], [1], [1]]
  • 經過輸出的結果能夠看初,咱們只給第一元素增長元素,結果三個list都增長了一個元素。這是由於[[]]*3並非建立了三個不一樣list,而是建立了三個指向同一個list的對象,因此,當咱們操做第一個元素時,其餘兩個元素內容也會發生變化的緣由。效果等同於下面這段代碼:app

li = []
        element = [[]]
        li = element + element + element
        print(li)
        # Out: [[], [], []]
        element.append(1)
        print(li)
        # Out: [[1], [1], [1]]
  • 咱們能夠打印出元素的內存地址一探究竟:ide

li = [[]] * 3
        print([id(inner_list) for inner_list in li])
        # Out: [6830760, 6830760, 6830760]
  • 到這咱們能夠明白緣由了。那如何解決了?能夠這樣:函數

li = [[] for _ in range(3)]
  • 這樣咱們就建立了三個不一樣的list對象oop

print([id(inner_list) for inner_list in li])
        # Out: [6331048, 6331528, 6331488]

列表元素的引用

  • 不要使用索引方法遍歷list,例如:測試

for i in range(len(tab)):
            print(tab[i])

比較好的方法是:this

for elem in tab:
        print(elem)

for語句會自動生成一個迭代器。若是你須要索引位置和元素,使用enumerate函數:code

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

注意 == 符號的使用

if (var == True):
            # 當var是:True、一、 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

相關文章
相關標籤/搜索