日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

记录遇到的Python陷阱和注意点

發(fā)布時間:2024/7/23 python 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 记录遇到的Python陷阱和注意点 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

來源:http://www.cnblogs.com/wilber2013/p/5178620.html

最近使用Python的過程中遇到了一些坑,例如用datetime.datetime.now()這個可變對象作為函數(shù)的默認參數(shù),模塊循環(huán)依賴等等。

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

避免可變對象作為默認參數(shù)

在使用函數(shù)的過程中,經(jīng)常會涉及默認參數(shù)。在Python中,當使用可變對象作為默認參數(shù)的時候,就可能產(chǎn)生非預期的結(jié)果。

下面看一個例子:

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

結(jié)果為:

[1] [1, 3] [1, 3, 5]

從結(jié)果中可以看到,當后面兩次調(diào)用append_item函數(shù)的時候,函數(shù)參數(shù)b并沒有被初始化為[],而是保持了前面函數(shù)調(diào)用的值。

之所以得到這個結(jié)果,是因為在Python中,一個函數(shù)參數(shù)的默認值,僅僅在該函數(shù)定義的時候,被初始化一次

下面看一個例子證明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)

結(jié)果為:

Init Test 1 3 5

從這個例子的結(jié)果就可以看到,Test類僅僅被實例化了一次,也就是說默認參數(shù)跟函數(shù)調(diào)用次數(shù)無關(guān),僅僅在函數(shù)定義的時候被初始化一次。

可變默認參數(shù)的正確使用

對于可變的默認參數(shù),我們可以使用下面的模式來避免上面的非預期結(jié)果:

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

結(jié)果為:

[1] [3] [5]

Python中的作用域

Python的作用域解析順序為Local、Enclosing、Global、Built-in,也就是說Python解釋器會根據(jù)這個順序解析變量。

看一個簡單的例子:

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

結(jié)果為:

global_var is : 0 outer_var is : 1 inner_var is : 2

在Python中,關(guān)于作用域有一點需要注意的是,在一個作用域里面給一個變量賦值的時候,Python會認為這個變量是當前作用域的本地變量

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

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

問題一

但是,當我們通過下面的方式使用變量的時候,就會產(chǎn)生問題了:

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

結(jié)果如下,之所以產(chǎn)生這個錯誤,就是因為我們在var_func中給num變量進行了賦值,所以Python解釋器會認為num是var_func作用域的本地變量,但是當代碼執(zhí)行到print "num is :", num語句的時候,num還是未定義。

UnboundLocalError: local variable 'num' referenced before assignment

問題二

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

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

代碼的結(jié)果為:

[1, 2, 3, 4] UnboundLocalError: local variable 'li' referenced before assignment

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

對于bar函數(shù)的這個問題,可以通過global關(guān)鍵字。

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

類屬性隱藏

在Python中,有類屬性和實例屬性。類屬性是屬于類本身的,被所有的類實例共享。

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

看下面這個例子:

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

代碼的結(jié)果如下,起初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__屬性,如果沒有找到類屬性,就會繼續(xù)查找父類。但是,如果子類定義了跟父類同名的類屬性后,子類的類屬性就會隱藏父類的類屬性。

看一個例子:

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

結(jié)果如下,當類B定義了count這個類屬性之后,就會隱藏父類的count屬性:

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

一般來說,在Python中,類實例屬性的訪問規(guī)則算是比較直觀的。

? ? ? 但是,仍然存在一些不是很直觀的地方,特別是對C++和Java程序員來說,更是如此。

? ? ? 在這里,我們需要明白以下幾個地方:

? ? ? 1.Python是一門動態(tài)語言,任何實體都可以動態(tài)地添加或刪除屬性。

? ? ? 2.一個類定義了一個作用域。

? ? ? 3.類實例也引入了一個作用域,這與相應類定義的作用域不同。

? ? ? 4.在類實例中查找屬性的時候,首先在實例自己的作用域中查找,如果沒有找到,則再在類定義的作用域中查找。

? ? ? 5.在對類實例屬性進行賦值的時候,實際上會在類實例定義的作用域中添加一個屬性(如果還不存在的話),并不會影響到相應類中定義的同名屬性。

? ? ? 下面看一個例子,加深對上述幾點的理解:

class A:cls_i = 0cls_j = {}def __init__(self):self.instance_i = 0self.instance_j = {} 在這里,我們先定義類A的一個實例a,然后再看看類A的作用域和實例a的作用域中分別有什么:
>>> a = A() >>> a.__dict__ {'instance_j': {}, 'instance_i': 0} >>> A.__dict__ {'__init__': , '__module__': '__main__', 'cls_i': 0, 'cls_j': {}, '__doc__': None}

我們看到,a的作用域中有instance_i和instance_j,A的作用域中有cls_i和cls_j。

? ? ? 我們再來看看名字查找是如何發(fā)生的:

>>> a.cls_i 0 >>> a.instance_i 0 在查找cls_i的時候,實例a的作用域中是沒有它的,卻在A的作用域中找到了它;在查找instance_i的時候,直接可在a的作用域中找到它。如果我們企圖通過實例a來修改cls_i的值,那會怎樣呢: >>> a.cls_i = 1 >>> a.__dict__ {'instance_j': {}, 'cls_i': 1, 'instance_i': 0} >>> A.__dict__ {'__init__': , '__module__': '__main__', 'cls_i': 0, 'cls_j': {}, '__doc__': None}

我們可以看到,a的作用域中多了一個cls_i屬性,其值為1;同時,我們也注意到A作用域中的cls_i屬性的值仍然為0;在這里,我們其實是增加了一個實例屬性,并沒有修改到類屬性。

? ? ? ?如果我們通過實例a操縱cls_j中的數(shù)據(jù)(注意不是cls_j本身),又會怎么樣呢:

>>> a.cls_j['a'] = 'a' >>> a.__dict__ {'instance_j': {}, 'cls_i': 1, 'instance_i': 0} >>> A.__dict__ {'__init__': , '__module__': '__main__', 'cls_i': 0, 'cls_j': {'a': 'a'}, '__doc__': None}

我們可以看到a的作用域沒有發(fā)生什么變化,但是A的作用域發(fā)生了一些變化,cls_j中的數(shù)據(jù)發(fā)生了變化。

? ? ? 實例的作用域發(fā)生變化,并不會影響到該類的其它實例,但是類的作用域發(fā)生變化,則會影響到該類的所有實例,包括在這之前創(chuàng)建的實例:

>>> A.cls_k = 0 >>> i.cls_k 0http://www.cnblogs.com/frydsh/p/3194710.html

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 tpl print id(tpl) print id(tpl[3])

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

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

Python的深淺拷貝

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

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

  • 使用切片[:]操作
  • 使用工廠函數(shù)(如list/dir/set)
  • 使用copy模塊中的copy()函數(shù)

使用copy模塊里面的淺拷貝函數(shù)copy():

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

使用copy模塊里面的深拷貝函數(shù)deepcopy():

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

模塊循環(huán)依賴

在Python中使用import導入模塊的時候,有的時候會產(chǎn)生模塊循環(huán)依賴,例如下面的例子,module_x模塊和module_y模塊相互依賴,運行module_y.py的時候就會產(chǎn)生錯誤。

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

其實,在編碼的過程中就應當避免循環(huán)依賴的情況,或者代碼重構(gòu)的過程中消除循環(huán)依賴。

當然,上面的問題也是可以解決的,常用的解決辦法就是把引用關(guān)系搞清楚,讓某個模塊在真正需要的時候再導入(一般放到函數(shù)里面)。

對于上面的例子,就可以把module_x.py修改為如下形式,在函數(shù)內(nèi)部導入module_y:

# module_x.py def inc_count():import module_ymodule_y.count += 1print module_y.count


總結(jié)

以上是生活随笔為你收集整理的记录遇到的Python陷阱和注意点的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。