Python裡的閉包和AOP

Ant發表於2020-04-06

習慣了寫Java和JS程式碼,突然換成Python還真不習慣,最近在書上看到一段挺有意思的Python程式碼,才真心感覺到Python長盛不衰的價值。在很多語言裡,如果在一個內部函式裡,對外部作用域(但不是全域性作用域)的變數進行引用,那麼內部函式就被認為是閉包(closure)。定義在外部函式內的但由內部函式引用或者使用的變數被稱為自由變數。閉包的詞法變數不屬於全域性或者區域性作用域,而屬於一種“流量”的作用域。


先介紹一種語法,Python有種叫函式裝飾器的東西,這是一種特殊的函式,因為它接受函式物件為引數,對其進行封裝加工並返回。舉個簡單的例子:

@deco(deco_args)
def foo(): pass

#相當於:foo = deco(deco_args) (foo)

裝飾器封裝了foo,並返回新版本的foo。這其實就是一種閉包的用法,因為無論在deco函式中定義了那些變數,當呼叫新版本foo的時候都可以訪問到它們,那些變數並不隨著deco函式的執行結束返回而銷燬,而是繼續存活在“流量”空間裡等待被啟用。如果要問這有什麼作用?最容易理解的就是對比Java裡的AOP概念,在不改變原始碼的情況下,織入切面,新增統一的邏輯:打日誌、效能指標記錄、統一事務管理等等。而在python裡面,AOP居然如此之簡單,通過函式裝飾器和閉包的特性就可以輕易解決。下面這個例子,就是對閉包強大特性的一個體現:(給方法hello新增執行時間的分析,不改動原方法hello的程式碼)

#!usr/bin/env python

from time import time

def logged(when):
	def log(f, *args, **kargs):
		print '''Called:
function: %s
args: %r
kargs: %r''' % (f,args,kargs)

	def pre_logged(f):
		def wrapper(*args, **kargs):
			log(f, *args, **kargs)
			return f(*args, **kargs)
		return wrapper

	def post_logged(f):
		def wrapper(*args, **kargs):
			now = time()
			try:
				return f(*args, **kargs)
			finally:
				log(f, *args, **kargs)
				print "time delta: %s" % (time()-now)
		return wrapper

	try:
		return {"pre": pre_logged, "post": post_logged}[when]
	except:
		raise ValueError(e), 'must be "pre" or "post"'

@logged("post")
def hello(name):
	print "Hello, ", name

hello("World")

可以看到只是簡單的在hello方法前新增了一個函式裝飾器,就可以讓hello的呼叫具有分析執行時間的特性,成功的織入切面!我這的執行結果是:

Hello,  World
Called:
function: <function hello at 0x10755aa28>
args: ('World',)
kargs: {}
time delta: 3.88622283936e-05




相關文章