本文主要记录自己对几个高级语法概念的理解:匿名函数、lambda表达式、闭包、装饰器。
这几个概念并非Python特有,但本文只限于用Python做说明。
1. 匿名函数
匿名函数(anonymous function)是指未与任何标识符绑定的函数,多用在functional programming languages领域,典型应用场合:
1) 作为参数传给高阶函数(higher-order function ),如python中的built-in函数filter/map/reduce都是典型的高阶函数
2) 作为高阶函数的返回值(虽然此处的"值"实际上是个函数对象)
与命名函数(named function)相比,若函数只被调用1次或有限次,则匿名函数在语法上更轻量级。
具体语法上,python通过lambda语法支持函数体为表达式的匿名函数,即:python的lambda表达式本质上是个匿名函数,但其函数体只能是个表达式,不能包含其它语句。
此外,高级动态语言常借助匿名函数实现闭包(closure)或装饰器(decorator)等高级语法。
在一些场合下,lambda表达式的使用使得python程序看起来非常简洁。例如,下面是根据value对dict元素做排序的代码示例:
>>> foo = {'father' : 65, 'mother' : 62, 'sister' : 38, 'brother' : 29, 'me' : 28}
>>> sorted(foo.iteritems(), key=lambda x: x[1])
[('me', 28), ('brother', 29), ('sister', 38), ('mother', 62), ('father', 65)]
2. 闭包
闭包(closure)本质上是一个包含了其引用环境(referencing environment)的函数或函数引用,这里的"引用环境"通常由一张表来维护,该表存储了函数体会访问的非局部变量(non-local variables)的引用。
与C语言中的函数指针相比,闭包允许嵌套函数访问其作用域外的non-local变量,这与Python解释器对变量的作用域查找规则有关(Python支持LEGB的查找规则,想深究的话,可以参考
对于运行时内存分配模型会在线性栈上创建局部变量的语言来说(典型如C语言),通常很难支持闭包。因为这些语言底层实现中,若函数返回,则函数中定义的局部变量均会随着函数栈被回收而销毁。但闭包在底层实现上要求其要访问的non-local变量在闭包被执行的时候保持有效,直到这个闭包的生命周期结束,这意外着这些non-local变量只有在其确定不再被使用时才能销毁,而不能随着定义这些变量的函数返回销毁。因此,天生支持闭包的语言通常采用garbage collection的方式管理内存,因为gc机制保证了变量只有不再被引用时才会由系统销毁并回收其内存空间。
具体语法上,闭包通常伴随着函数嵌套定义。以Python为例,一个简单的闭包示例如下:
#!/bin/env python
#-*- encoding: utf-8 -*-
def startAt_v1(x):
def incrementBy(y):
return x + y
print 'id(incrementBy)=%s' % (id(incrementBy))
return incrementBy
def startAt_v2(x):
return lambda y: x + y
if '__main__' == __name__:
c1 = startAt_v1(2)
print 'type(c1)=%s, c1(3)=%s' % (type(c1), c1(3))
print 'id(c1)=%s' % (id(c1))
c2 = startAt_v2(2)
print 'type(c2)=%s, c2(3)=%s' % (type(c2), c2(3))
执行结果如下:
id(incrementBy)=139730510519782
type(c1)=, c1(3)=5
id(c1)=139730510519782
type(c2)=, c2(3)=5
上述示例中,startAt_v1和startAt_v2均实现了闭包,其中:v1借助嵌套定义函数实现;v2则借助lambda表达式/匿名函数来实现。
3. 装饰器
python支持装饰器(decorator)语法。装饰器的概念对于初学者来说比较晦涩,因为它涉及到函数式编程的几个概念(如匿名函数、闭包),这也是本文先介绍匿名函数和闭包的原因。
#!/bin/env python
#-*- encoding: utf-8 -*-
def wrapper(fn):
def inner(n, m):
n += 1
print 'in inner: fn=%s, n=%s, m=%s' % (fn.__name__, n, m)
return fn(n, m) + 6 // 这里有return且返回值为int对象
return inner
@wrapper
def foo(n, m):
print 'in foo: n=%s, m=%s' % (n, m)
return n * m
print foo(2, 3)
上面的示例中,foo通过@wrapper语法糖声明它的装饰器是wrapper,在wrapper中,定义了嵌套的inner函数(该函数的参数列表必须与被装饰函数foo的参数列表保持一致),装饰器wrapper修改foo的行为后,返回inner(注意:由于inner的返回值是个int对象,故wrpper最终返回的也是个int对象)。
in inner: fn=foo, n=3, m=3
in foo: n=3, m=3
foo(2, 3)=15
需要再次强调的是,正常情况下,装饰器以被装饰函数(假设称为original_fn)作为入参,其定义体内实现一个新的wrapper函数对传入的original_fn做修改后返回,在上一层,装饰器函数将wrapper函数对象作为返回值返回。当然,在装饰器函数体内直接修改original_fn并返回在语法上也是合法的,只不过这种情况下,装饰器返回的不是一个函数对象,而是一个普通对象(如int/list/dict/tuple等)。我们用下面的代码来解释这段话。
demo1:装饰器函数内定义wrapper()对被装饰函数做行为修改,装饰器函数返回wrapper函数对象
#!/bin/env python
#-*- encoding: utf-8 -*-
def call(*argv, **kwargs):
print 'argv=%s' % (argv)
def call_fn(fn):
def wrapper(n): ## NOTICE: 引入wrapper()
return fn(*argv, **kwargs) ## fn的实参来自call()的参数,没有用table()的参数
return wrapper ## NOTICE: 返回wrapper函数对象
return call_fn
@call(5)
def table(n):
value = []
for i in xrange(n):
value.append(i)
return value
print 'type(table)=%s' % (type(table))
x = table(1) ## 装饰器内并未用到这里传入的参数,而是用了call()的参数,这里传入参数只是为语法正确而已
print len(x), x[3]
运行结果如下:
argv=5
type(table)=
5 3
上述代码中,table本身是个函数,被call()装饰后依然是个函数,而x是table(1)函数的执行结果,虽然这个结果被装饰器包装过(装饰器修改了table的实参)。
#!/bin/env python
#-*- encoding: utf-8 -*-
def call(*argv, **kwargs):
print 'argv=%s' % (argv)
def call_fn(fn):
return fn(*argv, **kwargs) ## NOTICE: 没有定义wrapper函数,而是直接返回fn的调用结果
return call_fn
@call(5)
def table(n):
value = []
for i in xrange(n):
value.append(i)
return value
print 'type(table)=%s' % (type(table))
print len(table), table[3] ## NOTICE: 这里的table被装饰器"改写"成了一个list对象!!!
运行结果如下:
argv=5
type(table)=
5 3
上述代码中,
table本来是个函数对象,但经过call()装饰后,返回的是个list!所以直接调用len(table)运行正确。看到装饰器的黑魔法了吧?
此外,上面这段代码表明,装饰器函数本身也可以支持传入参数(注意语法糖@call(5)那句),此时,需要在通常的装饰函数外层再包一层支持参数的函数,也即,最外层是支持普通参数的装饰器函数原型(如上例的call()),在该函数定义体内,再定义通常意义上的装饰器函数(如上例的call_fn(),该函数以被装饰函数名作为入参)作为第2层函数,在第2层函数定义体内,定义wrapper函数对被装饰函数行为做改写。
当然,如上例,wrapper函数并非必需,我们完全可以在第2层函数内直接对被装饰函数行为做修改,而不必引入wrapper函数,只不过其最终行为比较让人意外而已。
关于支持普通参数的装饰器函数的更多实例,建议阅读这篇文章A guide to Python's function decorators中“Passing arguments to decorators”部分的说明,讲的很清楚。
【参考资料】
1. wikipedia: Anonymous function
2. wikipedia: Closure (computer programming)
3. Chapter 17 of Book: Learning Python - Powerful Object-Oriented Programming, 4th edition
4. A beginner's guide to Python's namespaces, scope resolution, and the LEGB rule
5. Python Decorators
6. Understanding Python Decorators in 12 Easy Steps!
7. Decorators I: Introduction to Python Decorators
8. A guide to Python's function decorators
============= EOF =================