Python函式的作用域規則和閉包

2022-07-04 00:00:16 字數 4295 閱讀 8799

命名空間是從名稱到物件的對映,python中主要是通過字典實現的,主要有以下幾個命名空間:

python解析變數名的時候,

首先搜尋區域性命名空間。如果沒有找到匹配的名稱,它就會搜尋全域性命名空間。如果直譯器在全域性命名空間中也找不到匹配值,最終會檢查內建命名空間。如果仍然找不到,就會引發nameerror異常。

不同命名空間內的名稱絕對沒有任何關係,比如:

a = 42

def foo():

a = 13

print "globals: %s" % globals()

print "locals: %s" % locals()

return a

foo()

print "a: %d" % a

結果:

globals: 

locals:

a: 42

可見在函式中對變數a賦值會在區域性作用域中建立乙個新的區域性變數a,外部具有相同命名的那個全域性變數a不會改變。

在python中賦值操作總是在最裡層的作用域,賦值不會複製資料,只是將命名繫結到物件。刪除也是如此,比如在函式中執行del a,也只是從區域性命名空間中刪除區域性變數a,全域性變數a不會發生任何改變。

如果使用區域性變數時還沒有給它賦值,就會引發unboundlocalerror異常:

a = 42

def foo():

a += 1

return a

foo()

上述函式中定義了乙個區域性變數a,賦值語句a += 1會嘗試在a賦值之前讀取它的值,但全域性變數a是不會給區域性變數a賦值的。

要想在區域性命名空間中對全域性變數進行操作,可以使用global語句,global語句明確地將變數宣告為屬於全域性命名空間:

a = 42

def foo():

global a

a = 13

print "globals: %s" % globals()

print "locals: %s" % locals()

return a

foo()

print "a: %d" % a

輸出:

globals: 

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 =

def display():

print alist[0]

def decrement():

alist[0] -= 1

while alist[0] > 0:

display()

decrement()

countdown(10)

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

(,)

若要檢視閉包中變數的內容:

>>> 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):

return fs

for f in foo():

print f(1)

返回:

333

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都會優先繫結到閉包函式上。

作用域規則

每個 塊都有自己的命名規則。外層 塊的名字總是有效的,除非內層的 塊對它進行了重新定義。如果內層 塊的名字進行了重新定義,那麼在這個內層 塊中,外層 塊的名字被隱藏或遮蔽。塊可以巢狀多層,其深度取決於系統限制。include int main printf 3d 3d 5.1f n a,b,c 4 ...

Python函式作用域和匿名函式

匿名函式的定義 全域性變數和區域性變數的概念 global 全域性變數 和 nonlocal 區域性變數 閉包 遞迴 匿名函式 匿名函式 lambda 語法規則 lambda 引數 表示式 lambda匿名函式的格式 冒號前是引數,可以有多個,用逗號隔開,冒號右邊的為表示式 lambda返回值是乙個...

函式作用域和作用域鏈

所謂作用域就是 變數在宣告它們的函式體以及這個函式體巢狀的任意函式體內都是有定義的。function scope while 1 function console.log foo,global a,i m if b,i m while c c is not defined scope 但是,在js中...