python函式作用域與閉包
函式的定義
在python中,是用def來建立一個函式,實際上def只是完成了一個類似與賦值的操作———把一個函式物件賦值給一個變數名,還記得我們之前說過在python中變數名只是一個識別符號,相當於起到了一個指標的作用,它沒有型別(明確這一點是很重要的),又因為python中的一切皆物件,函式當然也不例外,所以,函式被建立後就可以賦值給任意的變數名,也可以作為引數傳遞給另外一個函式,也可以作為函式的返回值。下面是相應的程式碼演示
函式賦值給任意變數名:
函式作為引數傳遞:
def fun(test):
print 'Test will execute'
test()
def test():
print 'hello,I am test'
fun(test)
函式物件作為返回值:
def test():
def fun():
print 'I am fun'
return fun
a = test()
a()
函式作用域
python中有三種(或四種)域作用域相關的作用域。本地變數、(外層函式的本地變數)、全域性變數、內建變數
本地變數就是在一個函式內部的變數,全域性變數就是不在特定的函式內的,內建變數比較特殊,它是python在開發時就被設計好的一些變數,我們可以通過builtins模組檢視。
可以看到,其實這些內建的變數就是寫進了builtins這個檔案裡而已,但是這個檔案裡沒有寫builtins,所以我們需要匯入builtins模組,才能檢視它。
這裡還有一個奇怪的變數,我把它單獨拿出來說————–外層函式的本地變數,它是伴隨著函式嵌套出現的。我們舉一個例子來說明:
def test():
x = 1
def fun():
y = 2
上面的x就是所謂的外層函式的本地變數,當然它是相對於內層函式fun而言的,它也是本地變數(test的),但是它所處的作用域又不同於fun中的作用域,所以,如果現在fun中再建立一個x變數,他們是不衝突的。
LEGB規則
談完了函式的作用域,我們就來談一談python中變數名的解析規則。對於一個def語句:
- 變數名分為三個作用域查詢:首先是本地(L),之後是函式內(E)(如果有的話),之後是全域性(G),最後是內建(B)
在預設情況下,變數名賦值會建立或改變本地變數
LEGB圖示:
因為變數名賦值會建立本地變數,所以我們在函式內部想要改變全域性變數的值的時候就不能直接給它賦值了(不考慮全域性變數作為引數傳遞進函式),必須要用到global語句來宣告這是一個全域性變數:
#! /usr/bin/env python
#-*- coding:utf-8 -*-
x = 1
def test():
global x #在函式內宣告x為全域性變數
x = 2
test()
print 'x=%d'%x
#結果:x=2
global用於宣告一個變數是全域性變數。但是有一點小細節需要注意,當全域性變數是一個可變物件時,例如一個列表,我們可以直接在函式內部對它進行修改,而不是賦值
a = [1,2,3]
def test():
a.append(4)
print a
test()
#結果:[1,2,3,4]
這裡時對可變物件在原處的修改,而不是賦值!!
閉包
首先還得從基本概念說起,什麼是閉包呢?來看下維基上的解釋:
在電腦科學中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱,是引用了自由變數的函式。這個被引用的自由變數將和這個函式一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函式和與其相關的引用環境組合而成的實體。閉包在執行時可以有多個例項,不同的引用環境和相同的函式組合可以產生不同的例項。
上面提到了兩個關鍵的地方: 自由變數 和 函式, 這兩個關鍵稍後再說。還是得在贅述下“閉包”的意思,望文知意,可以形象的把它理解為一個封閉的包裹,這個包裹就是一個函式,當然還有函式內部對應的邏輯,包裹裡面的東西就是自由變數,自由變數可以在隨著包裹到處遊蕩。當然還得有個前提,這個包裹是被創建出來的。
在通過Python的語言介紹一下,一個閉包就是你呼叫了一個函式A,這個函式A返回了一個函式B給你。這個返回的函式B就叫做閉包。你在呼叫函式A的時候傳遞的引數就是自由變數。
舉個栗子:
def func(name):
def inner_func(age):
print 'name:', name, 'age:', age
return inner_func
bb = func('the5fire')
bb(26) # >>> name: the5fire age: 26
這裡面呼叫func的時候就產生了一個閉包——inner_func,並且該閉包持有自由變數——name,因此這也意味著,當函式func的生命週期結束之後,name這個變數依然存在,因為它被閉包引用了,所以不會被回收。
另外再說一點,閉包並不是Python中特有的概念,所有把函式做為一等公民的語言均有閉包的概念。不過像Java這樣以class為一等公民的語言中也可以使用閉包,只是它得用類或介面來實現。
閉包與裝飾器
其實裝飾器就是閉包的一種應用,下面來引用廖老師教程中的一個例子:
def log(func):
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper
上面的log函式就是一個裝飾器。它接受一個函式引數,我們使用python的@語法,把裝飾器放在函式的定義處,這樣當執行now函式的時候,就會自動執行log函式。
@log
def now():
print '2013-12-25'
now()
以上程式的輸出結果為:
call now():
2013-12-25
閉包的作用
閉包的最大特點是可以將父函式的變數與內部函式繫結,並返回繫結變數後的函式(也即閉包),此時即便生成閉包的環境(父函式)已經釋放,閉包仍然存在,這個過程很像類(父函式)生成例項(閉包),不同的是父函式只在呼叫時執行,執行完畢後其環境就會釋放,而類則在檔案執行時建立,一般程式執行完畢後作用域才釋放,因此對一些需要重用的功能且不足以定義為類的行為,使用閉包會比使用類佔用更少的資源,且更輕巧靈活。
關注web安全