開發中常遇到的Python陷阱和注意點

來源:互聯網
上載者:User
最近使用Python的過程中遇到了一些坑,例如用datetime.datetime.now()這個可變對象作為函數的預設參數,模組循環相依性等等。

在此記錄一下,方便以後查詢和補充。

避免可變對象作為預設參數

在使用函數的過程中,經常會涉及預設參數。在Python中,當使用可變對象作為預設參數的時候,就可能產生非預期的結果。

下面看一個例子:

def append_item(a = 1, b = []):    b.append(a)    print b     append_item(a=1)append_item(a=3)append_item(a=5)


結果為:

[1]

[1, 3]

[1, 3, 5]


從結果中可以看到,當後面兩次調用append_item函數的時候,函數參數b並沒有被初始化為[],而是保持了前面函數調用的值。

之所以得到這個結果,是因為在Python中,一個函數參數的預設值,僅僅在該函數定義的時候,被初始化一次。

下面看一個例子證明Python的這個特性:


class Test(object):      def __init__(self):          print("Init Test")             def arg_init(a, b = Test()):      print(a)  arg_init(1)  arg_init(3)  arg_init(5)


結果為:

Init Test

1

3

5


從這個例子的結果就可以看到,Test類僅僅被執行個體化了一次,也就是說預設參數跟函數調用次數無關,僅僅在函數定義的時候被初始化一次。

可變預設參數的正確使用

對於可變的預設參數,我們可以使用下面的模式來避免上面的非預期結果:

def append_item(a = 1, b = None):    if b is None:        b = []    b.append(a)    print b     append_item(a=1)append_item(a=3)append_item(a=5)


結果為:

[1]

[3]

[5]


Python中的範圍

Python的範圍解析順序為Local、Enclosing、Global、Built-in,也就是說Python解譯器會根據這個順序解析變數。

看一個簡單的例子:

global_var = 0def outer_func():    outer_var = 1         def inner_func():        inner_var = 2                 print "global_var is :", global_var        print "outer_var is :", outer_var        print "inner_var is :", inner_var             inner_func()     outer_func()


結果為:


global_var is : 0

outer_var is : 1

inner_var is : 2


在Python中,關於範圍有一點需要注意的是,在一個範圍裡面給一個變數賦值的時候,Python會認為這個變數是當前範圍的本地變數。


對於這一點也是比較容易理解的,對於下面代碼var_func中給num變數進行了賦值,所以此處的num就是var_func範圍的本地變數。


num = 0def var_func():    num = 1    print "num is :", num     var_func()


問題一

但是,當我們通過下面的方式使用變數的時候,就會產生問題了:


num = 0def var_func():    print "num is :", num    num = 1     var_func()


結果如下:


UnboundLocalError: local variable 'num' referenced before assignment


之所以產生這個錯誤,就是因為我們在var_func中給num變數進行了賦值,所以Python解譯器會認為num是var_func範圍的本地變數,但是當代碼執行到print "num is :", num語句的時候,num還是未定義。


問題二

上面的錯誤還是比較明顯的,還有一種比較隱形錯誤形式如下:


   li = [1, 2, 3]def foo():    li.append(4)    print lifoo()def bar():    li +=[5]    print libar()


代碼的結果為:



[1, 2, 3, 4]

UnboundLocalError: local variable 'li' referenced before assignment


在foo函數中,根據Python的範圍解析順序,該函數中使用了全域的li變數;但是在bar函數中,對li變數進行了賦值,所以li會被當作bar範圍中的變數。


對於bar函數的這個問題,可以通過global關鍵字。

li = [1, 2, 3]def foo():    li.append(4)    print li     foo()def bar():    global li    li +=[5]    print li     bar()


類屬性隱藏

在Python中,有類屬性和執行個體屬性。類屬性是屬於類本身的,被所有的類執行個體共用。

類屬性可以通過類名訪問和修改,也可以通過類執行個體進行訪問和修改。但是,當執行個體定義了跟類同名的屬性後,類屬性就被隱藏了。

看下面這個例子:

class Student(object):    books = ["Python", "JavaScript", "CSS"]    def __init__(self, name, age):        self.name = name        self.age = age    pass     wilber = Student("Wilber", 27)print "%s is %d years old" %(wilber.name, wilber.age)print Student.booksprint wilber.bookswilber.books = ["HTML", "AngularJS"]print Student.booksprint wilber.booksdel wilber.booksprint Student.booksprint wilber.books


代碼的結果如下,起初wilber執行個體可以直接存取類的books屬性,但是當執行個體wilber定義了名稱為books的執行個體屬性之後,wilber執行個體的books屬性就“隱藏”了類的books屬性;當刪除了wilber執行個體的books屬性之後,wilber.books就又對應類的books屬性了。

Wilber is 27 years old['Python', 'JavaScript', 'CSS']['Python', 'JavaScript', 'CSS']['Python', 'JavaScript', 'CSS']['HTML', 'AngularJS']['Python', 'JavaScript', 'CSS']['Python', 'JavaScript', 'CSS']


當在Python值使用繼承的時候,也要注意類屬性的隱藏。對於一個類,可以通過類的__dict__屬性來查看所有的類屬性。

當通過類名來訪問一個類屬性的時候,會首先尋找類的__dict__屬性,如果沒有找到類屬性,就會繼續尋找父類。但是,如果子類定義了跟父類同名的類屬性後,子類的類屬性就會隱藏父類的類屬性。

看一個例子:

class A(object):    count = 1     class B(A):    pass         class C(A):    pass             print A.count, B.count, C.count      B.count = 2print A.count, B.count, C.count      A.count = 3print A.count, B.count, C.count     print B.__dict__print C.__dict__


結果如下,當類B定義了count這個類屬性之後,就會隱藏父類的count屬性

1 1 11 2 13 2 3{'count': 2, '__module__': '__main__', '__doc__': None}{'__module__': '__main__', '__doc__': None}


tuple是“可變的”

在Python中,tuple是不可變對象,但是這裡的不可變指的是tuple這個容器總的元素不可變(確切的說是元素的id),但是元素的值是可以改變的。

tpl = (1, 2, 3, [4, 5, 6])print id(tpl)print id(tpl[3])tpl[3].extend([7, 8])print tplprint id(tpl)print id(tpl[3])

代碼結果如下,對於tpl對象,它的每個元素都是不可變的,但是tpl[3]是一個list對象。也就是說,對於這個tpl對象,id(tpl[3])是不可變的,但是tpl[3]確是可變的。

3676457638639896(1, 2, 3, [4, 5, 6, 7, 8])3676457638639896

Python的深淺拷貝

在對Python對象進行賦值的操作中,一定要注意對象的深淺拷貝,一不小心就可能踩坑了。

當使用下面的操作的時候,會產生淺拷貝的效果:

使用切片[:]操作

使用工廠函數(如list/dir/set)

使用copy模組中的copy()函數

使用copy模組裡面的淺拷貝函數copy():

import copywill = ["Will", 28, ["Python", "C#", "JavaScript"]]wilber = copy.copy(will)print id(will)print willprint [id(ele) for ele in will]print id(wilber)print wilberprint [id(ele) for ele in wilber]will[0] = "Wilber"will[2].append("CSS")print id(will)print willprint [id(ele) for ele in will]print id(wilber)print wilberprint [id(ele) for ele in wilber]


使用copy模組裡面的深拷貝函數deepcopy():

import copywill = ["Will", 28, ["Python", "C#", "JavaScript"]]wilber = copy.deepcopy(will)print id(will)print willprint [id(ele) for ele in will]print id(wilber)print wilberprint [id(ele) for ele in wilber]will[0] = "Wilber"will[2].append("CSS")print id(will)print willprint [id(ele) for ele in will]print id(wilber)print wilberprint [id(ele) for ele in wilber]


模組循環相依性

在Python中使用import匯入模組的時候,有的時候會產生模組循環相依性,例如下面的例子,module_x模組和module_y模組相互依賴,運行module_y.py的時候就會產生錯誤。

# module_x.pyimport module_y     def inc_count():    module_y.count += 1    print module_y.count          # module_y.pyimport module_xcount = 10def run():    module_x.inc_count()     run()

其實,在編碼的過程中就應當避免循環相依性的情況,或者代碼重構的過程中消除循環相依性。

當然,上面的問題也是可以解決的,常用的解決辦法就是把參考關聯性搞清楚,讓某個模組在真正需要的時候再匯入(一般放到函數裡面)。

對於上面的例子,就可以把module_x.py修改為如下形式,在函數內部匯入module_y:

# module_x.pydef inc_count():    import module_y    module_y.count += 1
  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.