命名空間是從名稱到對象的映射,Python中主要是經過字典實現的,主要有如下幾個命名空間:python
python解析變量名的時候,首先搜索局部命名空間。若是沒有找到匹配的名稱,它就會搜索全局命名空間。若是解釋器在全局命名空間中也找不到匹配值,最終會檢查內置命名空間。若是仍然找不到,就會引起NameError異常。編程
不一樣命名空間內的名稱絕對沒有任何關係,好比:bash
a = 42 def foo(): a = 13 print "globals: %s" % globals() print "locals: %s" % locals() return a foo() print "a: %d" % a
結果:閉包
globals: {'a': 42, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Users\\h\\Desktop\\test4.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x0000000002C17AC8>, '__doc__': None} locals: {'a': 13} a: 42
可見在函數中對變量a賦值會在局部做用域中建立一個新的局部變量a,外部具備相同命名的那個全局變量a不會改變。app
在Python中賦值操做老是在最裏層的做用域,賦值不會複製數據,只是將命名綁定到對象。刪除也是如此,好比在函數中運行del a,也只是從局部命名空間中刪除局部變量a,全局變量a不會發生任何改變。
函數式編程
若是使用局部變量時尚未給它賦值,就會引起UnboundLocalError異常:函數
a = 42 def foo(): a += 1 return a foo()
上述函數中定義了一個局部變量a,賦值語句a += 1會嘗試在a賦值以前讀取它的值,但全局變量a是不會給局部變量a賦值的。ui
要想在局部命名空間中對全局變量進行操做,可使用global語句,global語句明確地將變量聲明爲屬於全局命名空間:spa
a = 42 def foo(): global a a = 13 print "globals: %s" % globals() print "locals: %s" % locals() return a foo() print "a: %d" % a
輸出:code
globals: {'a': 13, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Users\\h\\Desktop\\test4.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x0000000002B87AC8>, '__doc__': None} locals: {} a: 13
可見全局變量a發生了改變。
Python支持嵌套函數(閉包),但python 2只支持在最裏層的做用域和全局命名空間中給變量從新賦值,內部函數是不能夠對外部函數中的局部變量從新賦值的,好比:
def countdown(start): n = start def display(): print n def decrement(): n -= 1 while n > 0: display() decrement() countdown(10)
運行會報UnboundLocalError異常,python 2中,解決這個問題的方法是把變量放到列表或字典中:
def countdown(start): alist = [] alist.append(start) def display(): print alist[0] def decrement(): alist[0] -= 1 while alist[0] > 0: display() decrement() countdown(10)
在python 3中可使用nonlocal語句解決這個問題,nonlocal語句會搜索當前調用棧中的下一層函數的定義。:
def countdown(start): n = start def display(): print n def decrement(): nonlocal n n -= 1 while n > 0: display() decrement() countdown(10)
閉包(closure)是函數式編程的重要的語法結構,Python也支持這一特性,舉例一個嵌套函數:
def foo(): x = 12 def bar(): print x return bar foo()()
輸出:12
能夠看到內嵌函數能夠訪問外部函數定義的做用域中的變量,事實上內嵌函數解析名稱時首先檢查局部做用域,而後從最內層調用函數的做用域開始,搜索全部調用函數的做用域,它們包含非局部但也非全局的命名。
組成函數的語句和語句的執行環境打包在一塊兒,獲得的對象就稱爲閉包。在嵌套函數中,閉包將捕捉內部函數執行所須要的整個環境。
python函數的code對象,或者說字節碼中有兩個和閉包有關的對象:
再看下上面的嵌套函數:
>>> def foo(): x = 12 def bar(): return x return bar >>> foo.func_code.co_cellvars ('x',) >>> bar = foo() >>> bar.func_code.co_freevars ('x',)
能夠看出外層函數的code對象的co_cellvars保存了內部嵌套函數須要引用的變量的名字,而內層嵌套函數的code對象的co_freevars保存了須要引用外部函數做用域中的變量名字。
在函數編譯過程當中內部函數會有一個閉包的特殊屬性__closure__(func_closure)。__closure__屬性是一個由cell對象組成的元組,包含了由多個做用域引用的變量:
>>> bar.func_closure (<cell at 0x0000000003512C78: int object at 0x0000000000645D80>,)
若要查看閉包中變量的內容:
>>> bar.func_closure[0].cell_contents 12
若是內部函數中不包含對外部函數變量的引用時,__closure__屬性是不存在的:
>>> def foo(): x = 12 def bar(): pass return bar >>> bar = foo() >>> print bar.func_closure None
當把函數看成對象傳遞給另一個函數作參數時,再結合閉包和嵌套函數,而後返回一個函數當作返回結果,就是python裝飾器的應用啦。
須要注意的一點是,python函數的做用域是由代碼決定的,也就是靜態的,但它們的使用是動態的,是在執行時肯定的。
>>> def foo(n): return n * i >>> fs = [foo for i in range(4)] >>> print fs[0](1)
當你期待結果是0的時候,結果倒是3。
這是由於只有在函數foo被執行的時候纔會搜索變量i的值, 因爲循環已結束, i指向最終值3, 因此都會獲得相同的結果。
在閉包中也存在相同的問題:
def foo(): fs = [] for i in range(4): fs.append(lambda x: x*i) return fs for f in foo(): print f(1)
返回:
3 3 3 3
解決方法,一個是爲函數參數設置默認值:
>>> fs = [lambda x, i=i: x * i for i in range(4)] >>> for f in fs: print f(1)
另外就是使用閉包了:
>>> def foo(i): return lambda x: x * i >>> fs = [foo(i) for i in range(4)] >>> for f in fs: print f(1)
或者:
>>> for f in map(lambda i: lambda x: i*x, range(4)): print f(1)
使用閉包就很相似於偏函數了,也可使用偏函數:
>>> fs = [functools.partial(lambda x, i: x * i, i) for i in range(4)] >>> for f in fs: print f(1)
這樣自由變量i都會優先綁定到閉包函數上。