Python装饰器详解

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

在装饰器中,通常使用下面这些python技巧

  • *args和**kwargs
  • 闭包
  • 作为参数的函数

使用*收集位置参数

当参数被用在函数内部时,*将一组可变数量的位置参数集合成参数值的元组

def print_args(*args):
    print("Positional argument tuple:", args)

>>>print_args()
Positional argument tuple: ()

>>>print_args(3, 2, 1, "hello", "world")
Positional argument tuple: (3, 2, 1, 'hello', 'world')

使用**收集关键字参数

使用**可以将参数收集到一个字典中,参数的名字是字典的键,对应参数的值是字典的值

def print_kwargs(**kwargs):
    print("Keyword argument:", kwargs)

>>>print_kwargs()
Keyword argument: {}

>>>print_kwargs(key_1="value_1", key_2="value_2", key_3="value_3")
Keyword argument: {'key_1': 'value_1', 'key_2': 'value_2', 'key_3': 'value_3'}

闭包

在python中,可以在函数中定义另外一个函数(内部函数)。当需要在函数内部多次执行复杂的任务时,内部函数是非常有用的,从而避免了循环和代码的堆叠重复

def knights(saying):
    def inner(quote):
        return "We are the knights who say: '%s'" % quote
    return inner(saying)

>>>knights("Ni!")
We are the knights who say: 'Ni!'

内部函数可以看作一个闭包。闭包是一个可以由另一个函数动态生成的函数,并且可以改变和存储函数外创建的变量的值

def knights2(saying):
    def inner2():
        return "We are the knights who say: '%s'" % saying
    return inner2

a = knights2("Duck")

>>>type(a)
<class 'function'>
>>>a
<function knights2.<locals>.inner2 at 0x109c15598>
>>>a()
We are the knights who say: 'Duck'
  • inner2()直接使用外部的saying参数,而不是通过另外一个参数获取。
  • knights2()返回值为inner2函数对象,而不是调用它

inner2()函数可以得到saying参数的值并且记录下来。return inner2这行返回的是inner2函数的复制(没有直接调用)。所以它就是一个闭包:一个被动态创建的可以记录外部变量的函数

怎么写一个装饰器

在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。

def debug(fun):
    def wrapper():
        print("[DEBUG]: enter {}():".format(fun.__name__))
        return fun()
    return wrapper

def say_hello():
    print("hello world")

hello = debug(say_hello)

>>>hello()
[DEBUG]: enter say_hello():
hello world
None

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

def debug(fun):
    def wrapper():
        print("[DEBUG]: enter {}():".format(fun.__name__))
        return fun()
    return wrapper

@debug
def say_hello():
    print("hello world")

>>>say_hello()
[DEBUG]: enter say_hello():
hello world

被装饰的函数带参数

使用位置参数*和关键字参数**

def debug(fun):
    def wrapper(*args, **kwargs):
        print("[DEBUG]: enter {}():".format(fun.__name__))
        return fun(*args, **kwargs)
    return wrapper

@debug
def say_hello(name):
    print("hello", name)

>>>say_hello("tan")
[DEBUG]: enter say_hello():
hello tan

带参数的装饰器

当带参数的装饰器被打在某个函数上时,比如@logging(level='INFO'),它其实是一个函数,会马上被执行,它返回的结果是一个装饰器.

def logging(level):
    def outer_wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print("[{level}]: enter {func}()".format(level=level, func=func.__name__))
            return func(*args, **kwargs)
        return inner_wrapper
    return outer_wrapper


@logging(level="INFO")
def say_hello(name):
    print("hello world:", name)

# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say_hello)

>>>say_hello("tan")
[INFO]: enter say_hello()
hello world: tan

基于类实现的装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例

外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。

class Test():
    def __call__(self):
        print 'call me!'

t = Test()
>>>t() 
call me

__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。

回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象.那么用类来实现也是也可以的.我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果.

class logging:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[INFO]: enter {func}()".format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@logging
def say_hello(name):
    print("hello world", name)

>>>say_hello("tan")
[INFO]: enter say_hello()
hello world tan

带参数的类装饰器

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点. 那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来. 然后在重载__call__方法是就需要接受一个函数并返回一个函数

class logging:
    def __init__(self, level):
        self.level = level

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print("[{level}]: enter {func}()".format(level=self.level, func=func.__name__))
            return func(*args, **kwargs)
        return wrapper


@logging(level="INFO")
def say_hello(name):
    print("hello world", name)


>>>say_hello("tan")
[INFO]: enter say_hello()
hello world tan

使用decorator优化装饰器

嵌套的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好. decorator.py 是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper(),再使用decorate(func, wrapper)方法就可以完成一个装饰器。

from decorator import decorate
from datetime import datetime

def wrapper(func, *args, **kwargs):
    """print log before a function."""
    print("[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__))
    return func(*args, **kwargs)

def logging(func):
    return decorate(func, wrapper)  # 用wrapper装饰func

@logging
def say_hello(name):
    print("hello world", name)

>>>say_hello("tan")
[DEBUG] 2018-11-26 11:01:21.879185: enter say_hello()
hello world tan

你也可以使用它自带的@decorator装饰器来完成你的装饰器。

from decorator import decorator

@decorator
def logging(func, *args, **kwargs):
    """print log before a function."""
    print("[DEBUG]: enter {}()".format(func.__name__))
    return func(*args, **kwargs)

@logging
def say_hello(name):
    print("hello world", name)

>>>say_hello("tan")
[DEBUG]: enter say_hello()
hello world tan

decorator.py实现的装饰器能完整保留原函数的namedocargs,唯一有问题的就是inspect.getsource(func)返回的还是装饰器的源代码,你需要改成inspect.getsource(func.__wrapped__)

使用wrapt优化装饰器

wrapt是一个功能非常完善的包,用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心inspect中遇到的所有问题,因为它都帮你处理了,甚至inspect.getsource(func)也准确无误。

import wrapt

@wrapt.decorator
def logging(wrapped, instance, args, kwargs):
    print("[INFO]: enter {}()".format(wrapped.__name__))
    return wrapped(*args, **kwargs)

@logging
def say_hello(name):
    print("hello world", name)

say_hello("tan")

使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs),注意第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外,argskwargs也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。

使用wrapt写一个带参数的装饰器

import wrapt

def logging(level):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print("[{}]: enter {}()".format(level, wrapped.__name__))
        return wrapped(*args, **kwargs)
    return wrapper

@logging(level="INFO")
def say_hello(name):
    print("hello world", name)

>>>say_hello("tan")
[INFO]: enter say_hello()
hello world tan

 

转载于:https://my.oschina.net/u/4000872/blog/2961040

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值