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

1. 闭包函数

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

       

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

def count():
a = 1
b = 1
def sum():
c = 1
return a + c # a - 自由变量
return sum

  

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

2. python装饰器

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

    实质: 是一个函数

    参数:是你要装饰的函数名(并非函数调用

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

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

    特点:不需要对对象做任何的代码上的变动

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

2.1  函数装饰器

函数的函数装饰器

我们还是以为函数添加计时功能为例,讲述函数装饰器。

import time

def decorator(func):
def wrapper(*args, **kwargs):
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

  

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

        

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

类方法的函数装饰器

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

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() # 函数调用

  

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

2.2 类装饰器

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

class Decorator(object):
def __init__(self, f):
self.f = f
def __call__(self):
print("decorator start")
self.f()
print("decorator end") @Decorator
def func():
print("func") func()

  这里有注意的是:__call__()是一个特殊方法,它可将一个类实例变成一个可调用对象:

p = Decorator(func) # p是类Decorator的一个实例
p() # 实现了__call__()方法后,p可以被调用

  

要使用类装饰器必须实现类中的__call__()方法,就相当于将实例变成了一个方法。

2.3  装饰器链

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

        

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

2.4  python装饰器库 - functools

def decorator(func):
def inner_function():
pass
return inner_function @decorator
def func():
pass print(func.__name__) # 输出: inner_function
 上述代码最后执行的结果不是 func,而是 inner_function!这表示被装饰函数自身的信息丢失了!怎么才能避免这种问题的发生呢?

可以借助functools.wraps()函数:
from functools import wraps
def decorator(func):
@wraps(func)
def inner_function():
pass
return inner_function @decorator
def func():
pass print(func.__name__) #输出: func

  

[转]python 装饰器的更多相关文章

  1. 关于python装饰器

    关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...

  2. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

  3. Python 装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...

  4. python 装饰器修改调整函数参数

    简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...

  5. python 装饰器学习(decorator)

    最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...

  6. Python装饰器详解

    python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...

  7. 关于python装饰器(Decorators)最底层理解的一句话

    一个decorator只是一个带有一个函数作为参数并返回一个替换函数的闭包. http://www.xxx.com/html/2016/pythonhexinbiancheng_0718/1044.h ...

  8. Python装饰器由浅入深

    装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们 ...

  9. Python装饰器与面向切面编程

    今天来讨论一下装饰器.装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等.装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数 ...

  10. python装饰器方法

    前几天向几位新同事介绍项目,被问起了@login_required的实现,我说这是django框架提供的装饰器方法,验证用户是否登录,只要这样用就行了,因为自己不熟,并没有做过多解释. 今天查看dja ...

随机推荐

  1. golang的interface剖析

      背景: golang的interface是一种satisfied式的.A类只要实现了IA interface定义的方法,A就satisfied了接口IA.更抽象一层,如果某些设计上需要一些更抽象的 ...

  2. linux shell脚本报错总结

    1  rizhi.sh: line 28: warning: here-document at line 9 delimited by end-of-file (wanted `EOF') 原因是末尾 ...

  3. BZOJ4864[BeiJing 2017 Wc]神秘物质——非旋转treap

    题目描述 21ZZ 年,冬. 小诚退休以后, 不知为何重新燃起了对物理学的兴趣. 他从研究所借了些实验仪器,整天研究各种微观粒子.这 一天, 小诚刚从研究所得到了一块奇异的陨石样本, 便迫不及待地开始 ...

  4. BZOJ2277[Poi2011]Strongbox——数论

    题目描述 Byteasar is a famous safe-cracker, who renounced his criminal activity and got into testing and ...

  5. BZOJ3129 SDOI2013方程(容斥原理+扩展lucas)

    没有限制的话算一个组合数就好了.对于不小于某个数的限制可以直接减掉,而不大于某个数的限制很容易想到容斥,枚举哪些超过限制即可. 一般情况下n.m.p都是1e9级别的组合数没办法算.不过可以发现模数已经 ...

  6. 【比赛】NOIP2018 总结

    一.考试过程 Day1: 先看了一遍题目,得到的结论是没有题是直接秒掉的,然后一道一道认真看. 看T1的时候开始并没想起来有一道原题,只是脑海中有一个印象,好像求差分和可以.然后自测了一下小样例,发现 ...

  7. luogu4197 Peaks (kruskal重构树+主席树)

    按照边权排序建出kruskal重构树,每次就变成了先找一个权值<=x的最远的祖先,然后看这个子树的第k小.离散化一下,在dfs序上做主席树即可 而且只需要建叶节点的主席树 注意输出的是第k小点的 ...

  8. 【bzoj1507】 JSOI2008—Blue Mary的旅行

    http://www.lydsy.com/JudgeOnline/problem.php?id=1570 (题目链接) 题意 给出$m$个航班,每天只能做一次飞机,有$T$人从起点到终点,问最晚到达的 ...

  9. 2018 省选 T1 一双木棋

    题目描述 菲菲和牛牛在一块n 行m 列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手. 棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束. 落子的规则是:一个格子可以落子当且仅当 ...

  10. (转)同一服务器部署多个tomcat时的端口号修改详情

    背景:在同一个服务器上部署工程,总会遇到tomcat端口占用的情况,所有有必要分清楚各个端口的作用,和需要更改的端口. 同一服务器部署多个tomcat时,存在端口号冲突的问题,所以需要修改tomcat ...