最近使用Python的過程當中遇到了一些坑,例如用datetime.datetime.now()
這個可變對象做爲函數的默認參數,模塊循環依賴等等。python
在此記錄一下,方便之後查詢和補充。app
在使用函數的過程當中,常常會涉及默認參數。在Python中,當使用可變對象做爲默認參數的時候,就可能產生非預期的結果。函數
下面看一個例子:ui
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並無被初始化爲[]
,而是保持了前面函數調用的值。code
之因此獲得這個結果,是由於在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)
結果爲:ip
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的做用域解析順序爲Local、Enclosing、Global、Built-in,也就是說Python解釋器會根據這個順序解析變量。
看一個簡單的例子:
global_var = 0 def 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 = 0 def var_func(): num = 1 print "num is :", num var_func()
可是,當咱們經過下面的方式使用變量的時候,就會產生問題了:
num = 0 def var_func(): print "num is :", num num = 1 var_func()
結果以下,之因此產生這個錯誤,就是由於咱們在var_func
中給num
變量進行了賦值,因此Python解釋器會認爲num
是var_func
做用域的本地變量,可是當代碼執行到print "num is :", num
語句的時候,num
仍是未定義。
UnboundLocalError: local variable 'num' referenced before assignment
上面的錯誤仍是比較明顯的,還有一種比較隱蔽的錯誤形式以下:
li = [1, 2, 3] def foo(): li.append(4) print li foo() def bar(): li +=[5] print li bar()
代碼的結果爲:
[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.books print wilber.books wilber.books = ["HTML", "AngularJS"] print Student.books print wilber.books del wilber.books print Student.books print 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 = 2 print A.count, B.count, C.count A.count = 3 print A.count, B.count, C.count print B.__dict__ print C.__dict__
結果以下,當類B
定義了count
這個類屬性以後,就會隱藏父類的count
屬性:
1 1 1 1 2 1 3 2 3 {'count': 2, '__module__': '__main__', '__doc__': None} {'__module__': '__main__', '__doc__': None}
在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])
代碼結果以下,對於tpl
對象,它的每一個元素都是不可變的,可是tpl[3]
是一個list
對象。也就是說,對於這個tpl
對象,id(tpl[3])
是不可變的,可是tpl[3]
確是可變的。
36764576 38639896 (1, 2, 3, [4, 5, 6, 7, 8]) 36764576 38639896
在對Python對象進行賦值的操做中,必定要注意對象的深淺拷貝,一不當心就可能踩坑了。
當使用下面的操做的時候,會產生淺拷貝的效果:
使用copy模塊裏面的淺拷貝函數copy():
import copy will = ["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模塊裏面的深拷貝函數deepcopy():
import copy will = ["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]
在Python中使用import
導入模塊的時候,有的時候會產生模塊循環依賴,例以下面的例子,module_x
模塊和module_y
模塊相互依賴,運行module_y.py
的時候就會產生錯誤。
# module_x.py import module_y def inc_count(): module_y.count += 1 print module_y.count # module_y.py import module_x count = 10 def run(): module_x.inc_count() run()
其實,在編碼的過程當中就應當避免循環依賴的狀況,或者代碼重構的過程當中消除循環依賴。
固然,上面的問題也是能夠解決的,經常使用的解決辦法就是把引用關係搞清楚,讓某個模塊在真正須要的時候再導入(通常放到函數裏面)。
對於上面的例子,就能夠把module_x.py
修改成以下形式,在函數內部導入module_y
:
# module_x.py def inc_count(): import module_y module_y.count += 1 print module_y.count