1、创建函数
在提到装饰器之前,先从创建函数说起,python中用def语句定义函数
def function_name(arguments):
"function_documentation string"
function_body_suite
注意在再python中不允许函数在未声明之前就进行引用或者调用。
def foo():
print "in foo()"
bar()
print foo()
可以再函数foo()前给出bar()的声明
def bar():
print "bar()"
实际上可以再bar()函数定义foo()函数
def foo():
print "in foo()"
bar()
def bar()
print "bar()"
print foo()
这是因为即使foo()函数中对bar()函数进行调用出现bar()函数之前,但 foo()本身不是在bar()声明之前被调用的,也就是先声明foo(),接着声明bar(),最后才调用foo(),这个时候bar()函数已经存在了。
2、内部/内嵌函数
在函数的内部创建另一个函数是完全合法的,这种函数叫做内部/内嵌函数。
def foo():
def bar():
print "bar() called"
print "foo() called"
bar()
print foo()
print bar()
内部函数一个很有趣的地方在于整个函数体都在外部函数的作用域之内,如果没有任何对bar()的外部引用,那么除了在函数体内,任何地方都不能对其进行调用,这就是上面异常的原因。但是注意lambda函数例外。如果内部函数的定义包含了在外部函数里定义的对象的引用(这个对象甚至可以是在外部函数之外),内部函数会变成闭包(closure)。
3、闭包
对于闭包的定义:如果在一个函数内部,对在外部作用域(但不是全局作用域)的变量进行引用,那么这个内部函数就被称为是闭包(closure)。定义在外部函数内的但由内部函数引用或者使用的变量被称为自用变量。
def make_print(msg):
def printer():
print msg #外部变量,夹带有“私货”msg
return printer #注意返回的是函数,返回的带有“私货”的函数
printer = make_print('Foo')
print printer
闭包的意义就在于它带了外部变量,如果没有携带则和普通的函数没有差别,一个函数懈怠了不同的"私货",就实现了不同的功能。这里结合实际的代码示例进行说明。
def outer():
x = 1
def inner():
print x #1
return inner
foo = outer()
print foo.func_closure
从上一个示例可以看到,inner
是 outer
返回的一个函数,存储在变量 foo
里然后用 foo()
来调用。但是它能运行吗?先来思考一下作用域规则。
Python 中一切都按作用域规则运行—— x
是函数 outer
中的一个局部变量,当函数 inner
在 #1
处打印 x
时,Python 在 inner
中搜索局部变量但是没有找到,然后在外层作用域即函数 outer
中搜索找到了变量 x
。
但如果从变量的生命周期角度来看应该如何呢?变量 x
对函数 outer
来说是局部变量,即只有当 outer
运行时它才存在。只有当 outer
返回后才能调用 inner
,所以依据 Python 运行机制,在调用 inner
时 x
就应该不存在了,那么这里应该有某种运行错误出现。结果并不是如此,返回的 inner
函数正常运行。Python 支持一种名为函数闭包的特性,意味着 在非全局作用域定义的 inner
函数在定义时记得外层命名空间是怎样的。inner
函数包含了外层作用域变量,通过查看它的 func_closure
属性可以看出这种函数闭包特性。记住——每次调用函数 outer
时,函数 inner
都会被重新定义。此时 x
的值没有变化,所以返回的每个 inner
函数和其它的 inner
函数运行结果相同,但是如果稍做一点修改呢?
def outer(x):
def inner():
print x #1
return inner
print1 = outer(1)
print2 = outer(2)
print print1()
print print2()
python中如果一个函数没有返回值(即明确没有return *,则这个函数的返回值为none,类似于c/c++的return void形式)从这个示例可以看到闭包——函数记住其外层作用域的事实——可以用来构建本质上有一个硬编码参数的自定义函数。虽然没有直接给 inner
函数传参 1 或 2,但构建了能“记住”该打印什么数的 inner
函数自定义版本。
闭包是强大的技术——在某些方面来看可能感觉它有点像面向对象技术:outer
作为 inner
的构造函数,有一个类似私有变量的 x
。闭包的作用不胜枚举——如果你熟悉 Python中 sorted
函数的参数 key
,也许你已经写过 lambda
函数通过第二项而非第一项来排序一些列表。也可以写一个 itemgetter
函数,接收一个用于检索的索引并返回一个函数,然后就能恰当的传递给 key
参数了。
4、装饰器
装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。让我们从简单的开始,直到能写出实用的装饰器。
def outer(some_func):
def inner():
print "before some_func"
ret = some_func() # 1
return ret + 1
return inner
def foo():
return 1
decorated = outer(foo) # 2
print decorated()
首先,定义了一个带单个参数 some_func
的名为 outer
的函数。然后在 outer
内部定义了一个内嵌函数 inner
。inner
函数将打印一行字符串然后调用 some_func
,并在 #1
处获取其返回值。在每次 outer
被调用时,some_func
的值可能都会不同,不论 some_func
是什么函数,都将调用它。最后,inner
返回 some_func()
的返回值加 1。在 #2
处可以看到,当调用赋值给 decorated
的返回函数时,得到的是一行文本输出和返回值 2,而非期望的调用 foo
的返回值 1。
我们可以说变量 decorated
是 foo
的装饰版——即 foo
加上一些东西。事实上,如果写了一个实用的装饰器,可能会想用装饰版来代替 foo
,这样就总能得到“附带其他东西”的 foo
版本。用不着学习任何新的语法,通过将包含函数的变量重新赋值就能轻松做到这一点:
foo = outer(foo)
print foo()
现在任意调用 foo()
都不会得到原来的 foo
,而是新的装饰器版!明白了吗?来写一个更实用的装饰器。
想象一个提供坐标对象的库。它们可能主要由一对对的 x
、y
坐标组成。遗憾的是坐标对象不支持数学运算,并且我们也无法修改源码。然而我们需要做很多数学运算,所以要构造能够接收两个坐标对象的 add
和 sub
函数,并且做适当的数学运算。这些函数很容易实现(为方便演示,提供一个简单的 Coordinate
类)。
class Coordinate(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return "Coord: " + str(self.__dict__)
def add(a, b):
return Coordinate(a.x + b.x, a.y + b.y)
def sub(a, b):
return Coordinate(a.x - b.x, a.y - b.y)
one = Coordinate(100, 200)
two = Coordinate(300, 200)
print add(one, two)
5、函数装饰器@符号
add = wrapper(add)
这种模式可以随时用来包装任意函数。但是如果定义了一个函数,可以用 @ 符号来装饰函数,如下:
@ wrapper
def add(a, b):
return Coordinate(a.x + b.x, a.y + b.y)
值得注意的是,这种方式和简单的使用 wrapper
函数的返回值来替换原始变量的做法没有什么不同—— Python 只是添加了一些语法糖来使之看起来更加明确。
使用装饰器很简单!虽说写类似 staticmethod
或者 classmethod
的实用装饰器比较难,但用起来仅仅需要在函数前添加 @装饰器名
即可!
6、args 和 *kwargs
上面我们写了一个实用的装饰器,但它是硬编码的,只适用于特定类型的函数——带有两个参数的函数。内部函数 checker
接收两个参数,然后继续将参数传给闭包中的函数。如果我们想要一个能适用任何函数的装饰器呢?让我们来实现一个为每次被装饰函数的调用添加一个计数器的装饰器,但不改变被装饰函数。这意味着这个装饰器必须接收它所装饰的任何函数的调用信息,并且在调用这些函数时将传递给该装饰器的任何参数都传递给它们。
碰巧,Python 对这种特性提供了语法支持。请务必阅读 Python Tutorial 以了解更多,但在定义函数时使用 *
的用法意味着任何传递给函数的额外位置参数都是以 *
开头的。如下
def one(*args):
print args # 1
print one()
#()
print one(1, 2, 3)
#(1, 2, 3)
def two(x, y, *args): # 2
print x, y, args
print two('a', 'b', 'c')
#a b ('c',)
第一个函数 one
简单的打印了传给它的任何位置参数(如果有)。在 #1
处可以看到,在函数内部只是简单的用到了变量 args
—— *args
只在定义函数时用来表示位置参数将会保存在变量 args
中。Python 也允许指定一些变量,并捕获任何在 args
里的额外参数,如 #2
处所示。
*
符号也可以用在函数调用时,在这里它也有类似的意义。在调用函数时,以 *
开头的变量表示该变量内容需被取出用做位置参数。再举例如下:
def add(x, y):
return x + y
lst = [1,2]
print add(lst[0], lst[1]) # 1
#3
print add(*lst) # 2
#3
在 #1 处的代码和 #2 处的作用相同——可以手动做的事情,在 #2 处 Python 帮我们自动处理了。这看起来不错,*args
可以表示在调用函数时从迭代器中取出位置参数, 也可以表示在定义函数时接收额外的位置参数。
接下来介绍稍微复杂一点的用来表示字典和键值对的 **
,就像 *
用来表示迭代器和位置参数。很简单吧?
def foo(**kwargs):
print kwargs
print foo()
#{}
print foo(x=1, y=2)
#{'y': 2, 'x': 1}
当定义一个函数时,使用 **kwargs
来表示所有未捕获的关键字参数将会被存储在字典 kwargs
中。此前 args
和 kwargs
都不是 Python 中语法的一部分,但在函数定义时使用这两个变量名是一种惯例。和 *
的使用一样,可以在函数调用和定义时使用 **
。
dct = {'x': 1, 'y': 2}
def bar(x, y):
return x + y
print bar(**dct)
#3
6、更通用的装饰器
def logger(func):
def inner(*args, **kwargs): #1
print "Arguments were: %s, %s" % (args, kwargs)
return func(*args, **kwargs) #2
return inner
注意在 #1处函数 inner接收任意数量和任意类型的参数,然后在 #2处将他们传递给被包装的函数。这样一来我们可以包
装或装饰任意函数,而不用管它的签名。
@logger
def foo1(x, y=1):
return x * y
@logger
def foo2():
return 2
print foo1(5, 4)
#Arguments were: (5, 4), {}
#20
print foo1(1)
#Arguments were: (1,), {}
#1
print foo2()
#Arguments were: (), {}
#2
每一个函数的调用会有一行日志输出和预期的返回值。