资讯专栏INFORMATION COLUMN

Python 装饰器使用指南

NeverSayNever / 405人阅读

摘要:装饰器是可调用的对象,其参数是另一个函数被装饰的函数。第二大特性是,装饰器在加载模块时立即执行。另一个常见的装饰器是,它的作用是协助构建行为良好的装饰器。

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。

装饰器基础知识

首先看一下这段代码

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

装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二大特性是,装饰器在加载模块时立即执行。

装饰器何时执行

装饰器的一个关键特性是,它们在被装饰的函数定义后立即运行。这通常在导入是(python 加载模块时)。

看下下面的示例:

registry = []  # registry 保存被@register 装饰的函数的引用

def register(func):  # register 的参数是一个函数
    print("running register(%s)" % func)  # 打印被装饰的函数
    registry.append(func)  # 把 func 存入 `registery`
    return func  # 返回 func:必须返回函数,这里返回的函数与通过参数传入的一样

@register  # `f1` 和 `f2`被 `@register` 装饰
def f1():
    print("running f1()")

@register
def f2():
    print("running f2()")

def f3():  # <7>
    print("running f3()")

def main():  # main 打印 `registry`,然后调用 f1()、f2()和 f3()
    print("running main()")
    print("registry ->", registry)
    f1()
    f2()
    f3()

if __name__=="__main__":
    main()  # <9>

运行代码结果如下:

running register()
running register()
running main()
registry -> [, ]
running f1()
running f2()
running f3()

从结果可以发现register 在模块中其他函数之前运行了两次。调用 register 时,传给它的参数是被装饰的函数(例如)。

看完上边的示例我们知道,函数被装饰器装饰后会变成装饰器函数的一个参数,那这时就不得不说变量的作用域了。

变量作用域

先看下下边这段代码:

def f1(a):
    print(locals())
    print(a)
    print(b)
    
    
f1(3)
# output
{"a": 3}
3
Traceback(most recent call last):
    File "", line 1, in 
    File "", line 3, in f1
NameError: global name "b" is not defined

这里的错误是因为全局变量 b 没有定义,如果我们先在函数外部给 b 赋值,再调用这个方法就不会报错了。

函数运行时会创建一个新的作用域(命名空间)。函数的命名空间随着函数调用开始而开始,结束而销毁。
这个例子中 f1 的命名空间中只有 {"a": 3},所以 b 会被认为是全局变量。

再看一个例子:

b = 6
def f2(a):
    print(a)
    print(globals())
    print(locals())
    print(b)
    b = 9
    
f2(3)
# output
3
{
    "__name__": "__main__",
    "__doc__": None, 
    "__package__": None, 
    "__loader__": <_frozen_importlib_external.SourceFileLoader object at 0x10c7f2dd8>, 
    "__spec__": None, 
    "__annotations__": {}, 
    "__builtins__": , 
    "__file__": "~/var_local.py", 
    "__cached__": None, 
    "b": 6, 
    "f2": 
}
{"a": 3}
3
Traceback(most recent call last):
    File "", line 1, in 
    File "", line 3, in f1
UnboundLocalError: local variable "b" referenced before assignment

这个例子和上一个例子不同是,我现在函数外部定义了全局变量b,但是执行f2 这个方法并没有打印6,这是为什么呢?
这是因为执行函数时 Python 会尝试从局部变量中获取 b,函数对于已经引用但未赋值的变量并不会自动声明为局部变量,所以解释器发现后边的赋值之前有引用就会抛出 UnboundLocalError 错误。

Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。

如果要让解释器把b当做全局变量,要使用global声明:

b = 6
def f3(a):
    global b
    print(a)
    print(b)
    b = 9
    
f2(3)
# output
3
6
闭包

闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用,但仍能使用那些绑定。

介绍闭包前先要说明一下 Python 的函数参数

函数的两种参数

函数有两种参数

位置参数

命名参数

def foo(x, y=0):
    return x - y
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)


>> a(1, 2)
>> 3
闭包的使用

先来看一个示例:假设有个名为 avg 的函数,它的作用是计算不断增加的系列值的均值;
它是这么使用的:

>>> avg(10)
10
>>> avg(11)
10.5
>>> avg(12)
11

那么我们考虑下,avg 从何而来,它又在哪里保存历史值呢,这个用闭包如何实现呢?
下边的代码是闭包的实现:

def make_averager():
    series = []

    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)

    return averager

调用 make_averager 时,返回一个 averager 函数对象。每次调用 averager 时,它都会把参数添加到系列值中,然后计算当前平均值。

avg = make_averager()

>>> avg(10)
10
>>> avg(11)
10.5
>>> avg(12)
11

seriesmake_averager 函数的局部变量,因为那个函数的定义体中初始化了series: series=[]。但在averager 函数中,series 是自由变量(指未在本地作用域中绑定的变量)。

averager 的闭包延伸到那个函数的作用域之外,包含自由变量series的绑定。

avg 就是一个闭包

也可以说 make_averager 指向一个闭包

或者说 make_averager 是闭包的工厂函数

闭包可以认为是一个内层函数(averager),由一个变量指代,而这个变量相对于外层包含它的函数而言,是本地变量
嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间

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

装饰器 实现一个简单的装饰器

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

def my_decorator(func):
    def wrapper():
        print "Before the function runs"
        func() # 这行代码可用,是因为 wrapper 的闭包中包含自由变量 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 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",函数已经被装饰

使用 functools.wraps

最后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__()

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

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

一个是__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
装饰器示例

Python 内置了三个用于装饰方法的函数:property、classmethod和 staticmethod。
另一个常见的装饰器是 functools.wraps,它的作用是协助构建行为良好的装饰器。

functools.lru_cache

functools.lru_cache 实现了内存缓存功能,它可以把耗时长的函数结果保存起来,避免传入相同参数时重复计算。

我们自己的实现代码如下:

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)
统计函数执行次数的装饰器
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

装饰器做缓存

带有过期时间的内存缓存
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
参考链接

How can I make a chain of function decorators in Python?

理解PYTHON中的装饰器

Python修饰器的函数式编程

Understanding Python Decorators in 12 Easy Steps!

PEP 0318 -- Decorators for Functions and Methods

PEP 3129 -- Class Decorators

[args and *kwargs? [duplicate]](http://stackoverflow.com/ques...

why-cant-i-set-a-global-variable-in-python

【flask route】

PythonDecoratorLibrary

关于Python Decroator的各种提案


最后,感谢女朋友支持。

欢迎关注(April_Louisa) 请我喝芬达

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/40745.html

相关文章

  • Python】一文弄懂python装饰(附源码例子)

    摘要:装饰器的使用符合了面向对象编程的开放封闭原则。三简单的装饰器基于上面的函数执行时间的需求,我们就手写一个简单的装饰器进行实现。函数体就是要实现装饰器的内容。类装饰器的实现是调用了类里面的函数。类装饰器的写法比我们装饰器函数的写法更加简单。 目录 前言 一、什么是装饰器 二、为什么要用装饰器 ...

    liuchengxu 评论0 收藏0
  • Python: 会打扮的装饰

    摘要:一般情况下,我们使用装饰器提供的语法糖,来简化上面的写法像上面的情况,可以动态修改函数或类功能的函数就是装饰器。本文标题为会打扮的装饰器本文链接为参考资料修饰器的函数式编程中的装饰器介绍思诚之道装饰器入门与提高赖明星 装饰器 我们知道,在 Python 中,我们可以像使用变量一样使用函数: 函数可以被赋值给其他变量 函数可以被删除 可以在函数里面再定义函数 函数可以作为参数传递给另外...

    blastz 评论0 收藏0
  • Python学习之路26-函数装饰和闭包

    摘要:初步认识装饰器函数装饰器用于在源代码中标记函数,以某种方式增强函数的行为。函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。只有涉及嵌套函数时才有闭包问题。如果想保留函数原本的属性,可以使用标准库中的装饰器。 《流畅的Python》笔记本篇将从最简单的装饰器开始,逐渐深入到闭包的概念,然后实现参数化装饰器,最后介绍标准库中常用的装饰器。 1. 初步认识装饰器 函数装饰...

    sunny5541 评论0 收藏0
  • Python装饰

    摘要:一引用书流畅的书二基本概念问题装饰器是什么解答严格来说,装饰器只是语法糖,装饰器是可调用的对象,可以像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数问题装饰器有什么特性解答装饰器有个特性,一是可以把被装饰的函数替换成其他函数, 一, 引用 [书] 流畅的Python [书] Effective Python 二, 基本概念 showImg(https://segme...

    aisuhua 评论0 收藏0
  • Python中的函数装饰和闭包

    摘要:变量查找规则在中一个变量的查找顺序是局部环境,闭包,全局,内建闭包引用了自由变量的函数。闭包的作用闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数,此时即便生成闭包的环境父函数已经释放,闭包仍然存在。 导语:本文章记录了本人在学习Python基础之函数篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、掌握装饰器的本质、功...

    caozhijian 评论0 收藏0
  • python装饰的原理和使用

    摘要:我们以测量函数运行时间为例来讲一讲装饰器的运行原理。三更加通用的装饰器前面两部分讲了装饰器的原理,这一部分就讲讲要写出一个通用的装饰器需要注意的问题。首先就是参数的问题,装饰器返回的函数不是原来的函数,函数的签名也就和原来的函数签名不一样。 一、最简单的装饰器 装饰器是python中很基础也很实用的一个特性。通过装饰器我们可以很方便地为一些函数添加相同的功能。我们以测量函数运行时间为例...

    goji 评论0 收藏0

发表评论

0条评论

NeverSayNever

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<