理解 python 装饰器

时间:2023-03-08 17:23:32
理解 python 装饰器

变量

    name = 'world'
x = 3

变量是代表某个值的名字

函数

	def hello(name):
return 'hello' + name hello('word)
hello word

函数通过def关键字、函数名和可选的参数列表定义。

是可以调用的,它执行某种行为并且返回一个值。

函数内部也可以定义函数

    def outer():
x = 1
def inner():
print x # 1
inner() >> outer()
1

作用域&生存周期

	def func(x):
print 'x is', x
print locals() func(50) >> x is 50
{'x': 50} >> print x
Traceback (most recent call last):
NameError: name 'x' is not defined

函数会创建一个新的作用域(命名空间)。

函数的命名空间随着函数调用开始而开始,结束而销毁

g = 'global variable'

>> print globals()
{'g': 'global variable',
'__builtins__': <module '__builtin__' (built-in)>,
'__file__': 'test.py',
'__package__': None,
'func': <function func at 0x10472d8c0>,
'__name__': '__main__',
'__doc__': None} def foo():
g = 'test'
print 'g is', g
print locals()
print globals() >> foo()
g is test
{'g': 'test'}
{'g': 'global variable',
'__builtins__': <module '__builtin__' (built-in)>,
'__file__': 'test.py',
'__package__': None,
'func': <function func at 0x10472d8c0>,
'__name__': '__main__',
'__doc__': None} >> print g
global variable

在函数内部遇到变量的时候会有现在自己的命名空间里找

猜一下段代码会执行的结果是什么

g = '我已经定义了'

def foo():
print g
g = '我重新定义了'
print g

答案

函数参数

函数有两种参数

  1. 位置参数
  2. 命名参数
	def foo(x, y=0):
return x - y

*args and **kwargs? [duplicate]

python 中一切都是对象

函数和python中其他一样都是对象

In [7]: class A(object):
...: pass In [8]: A
Out[8]: __main__.A
In [9]: type(A)
Out[9]: type
In [10]: def foo():
....: pass In [11]: type(foo)
Out[11]: function In [12]: A.__class__
Out[12]: type In [13]: foo.__class__
Out[13]: function In [14]: a = 1
In [15]: a.__class__
Out[15]: int # 类 是对象
In [16]: issubclass(A.__class__, object)
Out[16]: True # 变量 是对象
In [17]: issubclass(a.__class__, object)
Out[17]: True # 函数 是对象
In [18]: issubclass(foo.__class__, object)
Out[18]: True

所以函数也可以作为参数传递给其它函数,也可以被当做返回值返回

def add(x, y):
return x + y def apply(func):
return func >> a = apply(add)
>> type(a)
<type 'function'> >> a(1, 2)
>> 3

闭包

def make_adder(a):
def adder(b):
return b + a
return adder add = make_adder(5) >> add
# output
<function adder at 0x108166140> >> add(3)
# output
8
  • adder 就是一个闭包
  • 也可以说 make_adder 指向一个闭包
  • 或者说 make_add 是闭包的工厂函数

闭包可以认为是一个内层函数(adder),由一个变量指代,而这个变量相对于外层包含它的函数而言,是本地变量

嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间

闭包只是在形式和表现上像函数,但实际上不是函数。函数是一些可执行的代码,这些代码在函数被定义后就确定了,不会在执行时发生变化,所以一个函数只有一个实例。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

装饰器

对一个已有的模块做一些“修饰工作”,所谓修饰工作就是想给现有的模块加上一些小装饰(一些小功能,这些小功能可能好多模块都会用到),但又不让这个小装饰(小功能)侵入到原有的模块中的代码里去

def my_decorator(func):
def wrapper():
print "Before the function runs"
func()
print "After the function runs"
return wrapper def my_func():
print "I am a stand alone function" >> my_func() # output
I am a stand alone function # 然后,我们在这里装饰这个函数
# 将函数传递给装饰器,装饰器将动态地将其包装在任何想执行的代码中,然后返回一个新的函数 >> my_func = my_decorator(my_func)
>> my_func()
#output
Before the function runs
I am a stand alone function
After the function runs # 也可以这么写 @ my_decorator
def my_func():
print "I am a stand alone function" >> my_func()
#output
Before the function runs
I am a stand alone function
After the function runs

装饰器是设计模式中装饰器模式英文版)的python实现

多个装饰器

装饰器可以嵌套使用

def bread(func):
def wrapper():
print "</''''''\>"
func()
print "<\______/>"
return wrapper def ingredients(func):
def wrapper():
print "#tomatoes#"
func()
print "~salad~"
return wrapper def sandwich(food="--ham--"):
print food #outputs: #嵌套两个装饰器
>> sandwich = bread(ingredients(sandwich))
>> sandwich() # outputs:
</''''''\>
#tomatoes#
--ham--
~salad~
<\______/>

更简单的写法


@bread
@ingredients
def sandwich(food="--ham--"):
print food

装饰器的顺序是很重要的

@ingredients
@bread
def sandwich(food="--ham--"):
print food # outputs: #tomatoes#
</' ' ' ' ' '\>
--ham--
<\______/>
~salad~

Decorator 的本质

首先看一下这段代码

def deco(fn):
print "I am %s!" % fn.__name__ @deco
def func():
pass # output
I am func! # 没有执行func 函数 但是 deco 被执行了

在用某个@decorator来修饰某个函数func时

@decorator
def func():
pass

其解释器会解释成下面这样的语句:

func = decorator(func)

其实就是把一个函数当参数传到另一个函数中,然后再回调

但是值得注意的是装饰器必须返回一个函数给func

回到刚才的例子


def my_decorator(func):
def wrapper():
print "Before the function runs"
func()
print "After the function runs"
return wrapper def my_func():
print "I am a stand alone function" >> my_func = my_decorator(my_func)
>> my_func()
#output
Before the function runs
I am a stand alone function
After the function runs

my_decorator(my_func)返回了wrapper()函数,所以,my_func其实变成了wrapper的一个变量,而后面的my_func()执行其实变成了wrapper()

比如:多个decorator

@decorator_one
@decorator_two
def func():
pass 相当于: func = decorator_one(decorator_two(func)) 比如:带参数的decorator: @decorator(arg1, arg2)
def func():
pass
相当于: func = decorator(arg1,arg2)(func)

带参数的装饰器

首先看一下, 如果被装饰的方法有参数

def a_decorator(method_to_decorate):
def wrapper(self, x):
x -= 3
print 'x is %s' % x
method_to_decorate(self, x)
return wrapper class A(object): def __init__(self):
self.b = 42 @a_decorator
def number(self, x):
print "b is %s" % (self.b + x) a = A()
a.number(-3) # output
x is -6
b is 36

通常我们都使用更加通用的装饰器,可以作用在任何函数或对象方法上,而不必关系其参数 使用

def a_decorator(method_to_decorate):
def wrapper(*args, **kwargs):
print '****** args ******'
print args
print kwargs
method_to_decorate(*args, **kwargs)
return wrapper @a_decorator
def func():
pass func()
#output
****** args ******
()
{} @a_decorator
def func_with_args(a, b=0):
pass
return a + b func_with_args(1, b=2) #output
****** args ******
(1,)
{'b': 2}

上边的示例是带参数的被装饰函数

现在我们看一下向装饰器本身传递参数

向装饰器本身传递参数

再看一下这段代码

def deco(fn):
print "I am %s!" % fn.__name__ @deco
def func():
pass # output
I am func!

没有执行func 函数 但是 deco 被执行了

在用某个@deco来修饰某个函数func时

其解释器会解释成下面这样的语句:

func = deco(func)

装饰器必须使用函数作为参数,你不能直接传递参数给装饰器本身

如果想传递参数给装饰器,可以 声明一个用于创建装饰器的函数

# 我是一个创建装饰器的函数
def decorator_maker():
print "I make decorators!" def my_decorator(func):
print "I am a decorator!" def wrapped():
print "I am the wrapper around the decorated function. "
return func() print "As the decorator, I return the wrapped function."
return wrapped print "As a decorator maker, I return a decorator"
return my_decorator # decorator_maker()返回的是一个装饰器
new_deco = decorator_maker() #outputs
I make decorators!
As a decorator maker, I return a decorator # 使用装饰器
def decorated_function():
print "I am the decorated function" decorated_function = new_deco(decorated_function)
decorated_function() # outputs
I make decorators!
As a decorator maker, I return a decorator
I am a decorator!
As the decorator, I return the wrapped function.
I am the wrapper around the decorated function.
I am the decorated function

使用@修饰

decorated_function = new_deco(decorated_function)

# 等价于下面的方法

@new_deco
def func():
print "I am the decorated function" @decorator_maker()
def func():
print "I am the decorated function"

my_decorator(装饰器函数)是decorator_maker(装饰器生成函数)的内部函数

所以可以使用把参数加在decorator_maker(装饰器生成函数)的方法像装饰器传递参数

# 我是一个创建带参数装饰器的函数
def decorator_maker_with_arguments(darg1, darg2):
print "I make decorators! And I accept arguments:", darg1, darg2 def my_decorator(func):
print "I am a decorator! Somehow you passed me arguments:", darg1, darg2 def wrapped(farg1, farg2):
print "I am the wrapper around the decorated function."
print "I can access all the variables", darg1, darg2, farg1, farg2
return func(farg1, farg2) print "As the decorator, I return the wrapped function."
return wrapped print "As a decorator maker, I return a decorator"
return my_decorator @decorator_maker_with_arguments("deco_arg1", "deco_arg2")
def decorated_function_with_arguments(function_arg1, function_arg2):
print ("I am the decorated function and only knows about my arguments: {0}"
" {1}".format(function_arg1, function_arg2)) decorated_function_with_arguments('farg1', 'farg2') # outputs I make decorators! And I accept arguments: deco_arg1 deco_arg2
As a decorator maker, I return a decorator
I am a decorator! Somehow you passed me arguments: deco_arg1 deco_arg2
As the decorator, I return the wrapped function.
I am the wrapper around the decorated function.
I can access all the variables deco_arg1 deco_arg2 farg1 farg2
I am the decorated function and only knows about my arguments: farg1 farg2

这里装饰器生成函数内部传递参数是闭包的特性

使用装饰器需要注意

  • 装饰器是Python2.4的新特性
  • 装饰器会降低代码的性能
  • 装饰器仅在Python代码导入时被调用一次,之后你不能动态地改变参数.当你使用"import x",函数已经被装饰

最后Python2.5解决了最后一个问题,它提供functools模块,包含functools.wraps.这个函数会将被装饰函数的名称,模块,文档字符串拷贝给封装函数

def foo():
print "foo" print foo.__name__
#outputs: foo # 但当你使用装饰器
def bar(func):
def wrapper():
print "bar"
return func()
return wrapper @bar
def foo():
print "foo" print foo.__name__
#outputs: wrapper

"functools" 可以修正这个错误


import functools def bar(func):
# 我们所说的 "wrapper", 封装 "func"
@functools.wraps(func)
def wrapper():
print "bar"
return func()
return wrapper @bar
def foo():
print "foo" # 得到的是原始的名称, 而不是封装器的名称
print foo.__name__
#outputs: foo

类装饰器

class myDecorator(object):

    def __init__(self, func):
print "inside myDecorator.__init__()"
self.func = func def __call__(self):
self.func()
print "inside myDecorator.__call__()" @myDecorator
def aFunction():
print "inside aFunction()" print "Finished decorating aFunction()" aFunction() # output:
# inside myDecorator.__init__()
# Finished decorating aFunction()
# inside aFunction()
# inside myDecorator.__call__()

我们可以看到这个类中有两个成员:

  1. 一个是__init__(),这个方法是在我们给某个函数decorator时被调用,所以,需要有一个func的参数,也就是被decorator的函数。
  2. 一个是__call__(),这个方法是在我们调用被decorator函数时被调用的

如果decorator有参数的话,init() 就不能传入func了,而fn是在__call__的时候传入

class myDecorator(object):

    def __init__(self, arg1, arg2):
self.arg1 = arg2 def __call__(self, func):
def wrapped(*args, **kwargs):
return self.func(*args, **kwargs)
return wrapped

装饰器示例

def counter(func):
"""
记录并打印一个函数的执行次数
"""
def wrapper(*args, **kwargs):
wrapper.count = wrapper.count + 1
res = func(*args, **kwargs)
print "{0} has been used: {1}x".format(func.__name__, wrapper.count)
return res
wrapper.count = 0
return wrapper
  • 装饰器做缓存

from functools import wraps
def memo(fn):
cache = {}
miss = object() @wraps(fn)
def wrapper(*args):
result = cache.get(args, miss)
if result is miss:
result = fn(*args)
print "{0} has been used: {1}x".format(fn.__name__, wrapper.count)
cache[args] = result
return result return wrapper @memo
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)

15言和知性中用到的缓存

def cache_for(duration):
def deco(func):
@wraps(func)
def fn(*args, **kwargs):
key = pickle.dumps((args, kwargs))
value, expire = func.func_dict.get(key, (None, None))
now = int(time.time())
if value is not None and expire > now:
return value
value = func(*args, **kwargs)
func.func_dict[key] = (value, int(time.time()) + duration)
return value
return fn
return deco

更多缓存示例

  • 统计代码运行时间
def timeit(fn):

    @wraps(fn)
def real_fn(*args, **kwargs):
if config.common['ENVIRON'] == 'PRODUCTION':
return fn(*args, **kwargs) _start = time.time()
#app.logger.debug('Start timeit for %s' % fn.__name__)
result = fn(*args, **kwargs)
_end = time.time()
_last = _end - _start
app.logger.debug('End timeit for %s in %s seconds.' %
(fn.__name__, _last))
return result return real_fn

【更多debug工具 示例】

【flask route】

PythonDecoratorLibrary

关于Python Decroator的各种提案

Python本身提供了一些装饰器:property,staticmethod

参考链接

  1. How can I make a chain of function decorators in Python?
  2. 理解PYTHON中的装饰器
  3. Python修饰器的函数式编程
  4. Understanding Python Decorators in 12 Easy Steps!
  5. PEP 0318 -- Decorators for Functions and Methods
  6. PEP 3129 -- Class Decorators