python装饰器

预备知识

一级对象

python将一切(包括函数)视为object的子类,即一切皆为对象,因此函数可以像变量一样被指向和传递,下面我们来看一个例子。

1
2
3
4
def foo():
pass
#注意issubclass是python的一个内置函数,用于判断两个类是不是子类关系
print issubclass(foo.__class__, object)

输出结果:

1
True

上述代码说明了python中的函数都是object的子类,下面我们看一下函数被当做参数传递的效果

1
2
3
4
5
6
7
def foo(func):
func()
def bar():
print "bar"
foo(bar)

运行结果如下:

1
bar

python中的作用域 namespace

python 提供namespace来重新实现函数/方法,变量等信息的区别,七一共有三种作用域:

  • local namespace:作用范围为当前函数或者类方法
  • global namespace:作用范围为当前模块
  • build-in namespace:作用范围为所有模块

当变量或者方法出现重名的情况时,python会按照local->global->build-in的顺序去搜索,并以第一个找到的元素为当前的namespace,此种做法与C/C++中的相似

*args和**kwargs

在python中我们使用*args和kwargs传递可变长参数,*args用作传递非命名键值可变长参数列表(位置参数);**kwargs用作传递键值可变长参数

  • *args:把所有的参数按照出现顺序打包成一个list
  • **kwargs:把所有的key-value形式的参数打包成一个dict

例子:

1
2
3
4
5
6
7
def add(x, y):
print x + y
params_list = (1, 2)
dict_list = {"x":1, "y":2}
add(*params_list)
add(**dict_list)

打印结果:

1
2
3
3

python装饰器入门

python允许你,作为程序员,使用函数完成一些很酷的事情。在python中,函数是一级对象(first-class),这就意味着你可以像使用字符串,整数,或者其他对象一样使用函数。例如,你可以将函数赋值给变量:

1
2
3
4
5
def square(n):
return n * n
print square(4) #16
alias = square
print alias(5) #25

然而一等函数的真正威力在于你可以把函数传给其他函数,或者从其他函数中返回函数。函数的内值函数map利用了这种能力:给map传一个函数以及一个列表,他会依次以列表中的每个元素作为参数调用你传给它的函数,从而生成一个新的列表。如下所示的例子中应用了上面的square函数:

1
2
number = [1,2,3,4]
print map(square, number) #[1,4,9,16]

如果一个函数接受一个函数作为参数或者返回一个函数,则这个函数被称为高阶函数虽然map简单使用了我们传给它的函数,而没有改变这个函数,但我们也可以使用高阶函数去改变其他函数的行为。
例如:
假设有这样一个函数,会被调用很多次,以致运行待解非常昂贵:

1
2
def fib(n):
return n if n in [0,1] else fib(n - 2) + fib(n - 1)

为了提高这个函数的效率,我们一般会保存计算过程中得出的中间结果,这样对于函数调用树中经常出现某个n,当需要计算n对应的结果时,就不需要重复计算了。有很多种方式可以实现这一点。例如我们可以将这些结果存在一个字典中,当某个值为参数调用fib函数的时候,首先去字典中查一下结果是否已经计算出来,如果计算出来直接返回反之计算。
但是这样的话,每次我们想调用fib函数,都需要重复那段相同的字典检查样板式代码。相反,如果让fib函数自己在内部负责存储结果,那么在其他代码中调用fib,就非常方便,只要简单的调用它就好了。这种技术被称为memoization
当然我们可以把这种memozition代码直接放入fib函数中,但是python给我们提供了一种更加优雅的选择因为可以编写修改其他函数的函数,那么我们就可以便携一个通用的memozation函数,以一个函数作为参考,并返回这个函数的memozation版本。

1
2
3
4
5
6
7
8
9
def memoze(fn):
store_results = {}
def memoized(args):
try:
return store_results[args]
except:
result = store_result[args] = fn(args)
return result
return memoized

如上,momoize函数以另一个函数作为参数,函数体中创建了一个字典对象来存储函数调用结果:键为被memoized包含后的函数的参数,值为以键为参数调用函数的返回值。memoize函数返回一个新的函数,这个函数会首先检查store_results中是否存在与当前参数对应的条目,如果有则直接返回,如果没有,则使用原始函数进行计算。memoize返回的这种新的函数常被称为包装器函数。因为它只是另外一个真正起作用的函数外面的一个薄层。
很好,我们现在有了memoize函数,现在将fib函数传给它,从而得到了一个经过包装的fib,这个版本的fib函数不需要重复以前的那样繁重的工作:

1
2
3
4
def fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)
fib = memoize(fib)

通过高阶函数memoize,我们获得了memoization带来的好处,并不需要对fib函数自己做出任何的改变,以免夹杂着memoization的代码而模糊了函数的实质工作。但是,你也许注意到上面的代码看着还是有点别扭,因为我们必须写三遍fib。由于这种模式(传递一个函数给另一个函数,然后将结果返回给与原来那个函数同名的函数变量,在使用包装器函数的代码中极为常见),python提供了一种特殊的愈发:装饰器

1
2
3
@memoize
def fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

这里我们说memoize函数装饰了fib函数。需要注意的是这仅是语法上的简便写法(被称为“语法糖”)。这段代码与前面的代码片段做的是同样的事情:定义一个名为fib的函数,把它传给memoize函数,将返回结果存为名为fib的函数变量。特殊的(看起来有点奇怪的)@语法只是减少了冗余。

你可以使用多个装饰器,它会自底向上逐个起作用,例如假如有另外一个装饰器函数decorate

1
2
3
4
@memoize
@decorate
def fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)

等价于:

1
2
3
def fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)
fib = memoize(decorator(fib))

当装饰器函数含有参数的时候,方法是

1
2
3
@mimoize("172.168.1.1")
fib(n):
return n if n in [0, 1] else fib(n - 2) + fib(n - 1)