08装饰器

  之前你有没有这样一段经历:好久以前你写过一个函数,如今你忽然有了个想法就是你想看看,之前那个函数在你数据集上的运行时间是多少,这时候你能够修改以前代码为它加上计时的功能,可是这样的话是否是还要大致读读你以前的这个的代码,稍微搞清楚一点它的逻辑,才敢给它添加新的东西。这样是否是很繁琐,要是你以前写的代码足够乱足够长,再去读它是否是很抓狂...。实际工做中,咱们经常会遇到这样的场景,可能你的需求还不仅是这么简单。那么有没有一种能够不对源码作任何修改,而且能够很好的实现你全部需求的手段呢?答案固然是有,这就是今天咱们要介绍的python装饰器。有了装饰器,你除了不用担忧前面提到的问题,而且还能够很好的处理接下来要作的事:那就是如今你又有了一个新的需求,好比为另外一个函数添加计时功能,这时就很是简单了,把要装饰的函数丢给装饰器就行了,它会自动给你添加完功能并返回给你。是否是很神奇?下面咱们将一层层剥开它的神秘面纱。html

1. 闭包函数

在看装饰器以前,咱们先来搞清楚什么是闭包函数。python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也一样拥有。这样咱们就能够理解在函数内建立一个函数的行为是彻底合法的。这种函数被叫作内嵌函数,这种函数只能够在外部函数的做用域内被正常调用,在外部函数的做用域以外调用会报错,例如:python

In [14]:
def outFunction():
    print('out side the function')
    def inFunction():
        print('inside the function')
    inFunction()
In [15]:
outFunction()
 
out side the function
inside the function
In [16]:
inFunction()
 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-16-62c8de274b88> in <module>
----> 1inFunction()

NameError: name 'inFunction' is not defined
 

而若是内部函数里引用了外部函数里定义的对象(甚至是外层以外,但不是全局变量),那么此时内部函数就被称为闭包函数。闭包函数所引用的外部定义的变量被叫作自由变量。闭包从语法上看很是简单,可是却有强大的做用。闭包能够将其本身的代码和做用域以及外部函数的做用结合在一块儿。下面给出一个简单的闭包的例子:编程

In [17]:
def count():
    a = 1
    b = 1
    def sum():
        c = 1
        return a + c  # a - 自由变量
    return sum  # 注意返回的是一个sum()函数对象
In [19]:
result = count()
result
Out[19]:
<function __main__.count.<locals>.sum()>
In [20]:
result()
Out[20]:
2
 

总结:什么函数能够被称为闭包函数呢?主要是知足两点:函数内部定义的函数;引用了外部变量但非全局变量。闭包

 

2. python装饰器

有了闭包函数的概念,咱们再去理解装饰器会相对容易一些。python装饰器本质上就是一个函数,它可让其余函数在不须要作任何代码变更的前提下增长额外的功能,装饰器的返回值也是一个函数对象(函数的指针)。装饰器函数的外部函数传入我要装饰的函数名字,返回通过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。从上面这段描述中咱们须要记住装饰器的几点属性,以便后面能更好的理解:app

    实质: 是一个函数编程语言

    参数:是你要装饰的函数名(并不是函数调用)ide

    返回:是装饰完的函数名(也非函数调用)函数

    做用:为已经存在的对象添加额外的功能性能

    特色:不须要对对象作任何的代码上的变更测试

python装饰器有不少经典的应用场景,好比:插入日志、性能测试、事务处理、权限校验等。装饰器是解决这类问题的绝佳设计。而且从引入中的列子中咱们也能够概括出:装饰器最大的做用就是对于咱们已经写好的程序,咱们能够抽离出一些雷同的代码组建多个特定功能的装饰器,这样咱们就能够针对不一样的需求去使用特定的装饰器,这时由于源码去除了大量泛化的内容而使得源码具备更加清晰的逻辑。

2.1 函数装饰器

函数的函数装饰器 咱们仍是觉得函数添加计时功能为例,讲述函数装饰器。

In [22]:
import time

def decorator(func):
    def wrapper(*args, **kwargs):  # 这个wrapper函数要替换掉原来的func函数,func函数可能有形参,也可能没有形参,也可能有多个形参
        start_time = time.time()
        func()
        end_time = time.time()
        print(end_time - start_time)

    return wrapper


@decorator
def func():
    time.sleep(0.8)


func()  # 函数调用# 输出:0.800644397735595
 
0.8000457286834717
 

在上面代码中 func是我要装饰器的函数,我想用装饰器显示func函数运行的时间。@decorator这个语法至关于 执行 func = decorator(func),为func函数装饰并返回。在来看一下咱们的装饰器函数 - decorator,该函数的传入参数是func (被装饰函数),返回参数是内层函数。这里的内层函数-wrapper,其实就至关于闭包函数,它起到装饰给定函数的做用,wrapper参数为args, **kwargs。args表示的参数以列表的形式传入;**kwargs表示的参数以字典的形式传入:

In [23]:
def test(*args, **kwargs):
    print('args:', end=' ')
    print(args)
    print('kwargs', end=' ')
    print(kwargs)
test(["world"], 'world', 1, a=1, b=2)
 
args: (['world'], 'world', 1)
kwargs {'a': 1, 'b': 2}
 

从图中咱们能够看到:凡是以key=value形式的参数均存在kwargs中,剩下的全部参数都以列表的形式存于args中。这里要注意的是:为了避免破坏原函数的逻辑,咱们要保证内层函数wrapper和被装饰函数func的传入参数和返回值类型必须保持一致。

 

2.2 类方法的函数装饰器

类方法的函数装饰器和函数的函数装饰器相似。

In [24]:
import time

def decorator(func):
    def wrapper(me_instance):
        start_time = time.time()
        func(me_instance)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper

class Method(object):
    @decorator
    def func(self):
        time.sleep(0.8)


p1 = Method()
p1.func()  # 函数调用
 
0.8000459671020508
 

对于类方法来讲,都会有一个默认的参数self,它实际表示的是类的一个实例,因此在装饰器的内部函数wrapper也要传入一个参数 - me_instance就表示将类的实例p1传给wrapper,其余的用法都和函数装饰器相同。

 

2.3 类装饰器

前面咱们提到的都是让 函数做为装饰器去装饰其余的函数或者方法,那么可不可让 一个类发挥装饰器的做用呢?答案确定是能够的,一切皆对象嚒,函数和类本质没有什么不同。类的装饰器是什么样子的呢?

In [26]:
class Decorator(object):
    def __init__(self, f):
        self.f = f
        
    def __call__(self): # 这里有注意的是:__call__()是一个特殊方法,它可将一个类实例变成一个可调用对象:
        print("decorator start")
        self.f()
        print("decorator end")

@Decorator
def func():
    print("func")

func()
 
decorator start
func
decorator end
In [27]:
p = Decorator(func)  # p是类Decorator的一个实例
p()  # 实现了__call__()方法后,p能够被调用
 
decorator start
decorator start
func
decorator end
decorator end
 

2.4 装饰器链

一个python函数也能够被多个装饰器修饰,要是有多个装饰器时,这些装饰器的执行顺序是怎么样的呢?

In [57]:
def makebold(f):
    return lambda: '<b>' +f() + '</b>'
def makeitalic(f):
    return lambda: '<i>' +f() + '</i>'
In [58]:
@makebold
@makeitalic
def say():
    return "hello"
In [59]:
say()
Out[59]:
'<b><i>hello</i></b>'
 

可见,多个装饰器的执行顺序:是从近到远依次执行。

 

2.5 python装饰器库 - functools

In [62]:
def decorator(func):
    def inner_function():
        pass
    return inner_function

@decorator
def func():
    pass

func.__name__
Out[62]:
'inner_function'
 

述代码最后执行的结果不是 func,而是 inner_function!这表示被装饰函数自身的信息丢失了!怎么才能避免这种问题的发生呢?

能够借助functools.wraps()函数:

In [63]:
from functools import wraps

def decorator(func):
    @wraps(func)
    def inner_function():
        pass
    return inner_function

@decorator
def func():
    pass

func.__name__
Out[63]:
'func'
相关文章
相关标签/搜索