本文实例讲述了Python函数装饰器原理与用法。分享给大家供大家参考,具体如下:

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

严格来说,装饰器只是语法糖,装饰器是可调用的对象,可以像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数。

import time
#遵守开放封闭原则
def foo():
start = time.time()
# print(start) # 1504698634.0291758从1970年1月1号到现在的秒数,那年Unix诞生
time.sleep(3)
end = time.time()
print('spend %s'%(end - start))
foo()

bar()、bar2()也有类似的需求,怎么做?再在bar函数里调用时间函数?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门设定时间:

import time
def show_time(func):
start_time=time.time()
func()
end_time=time.time()
print('spend %s'%(end_time-start_time))
def foo():
print('hello foo')
time.sleep(3)
show_time(foo)

但是这样的话,你基础平台的函数修改了名字,容易被业务线的人投诉的,因为我们每次都要将一个函数作为参数传递给show_time函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行foo(),但是现在不得不改成show_time(foo)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

def show_time(f):
def inner():
start = time.time()
f()
end = time.time()
print('spend %s'%(end - start))
return inner
@show_time #foo=show_time(f)
def foo():
print('foo...')
time.sleep(1)
foo()
def bar():
print('bar...')
time.sleep(2)
bar()

输出结果:

foo...
spend 1.0005607604980469
bar...

函数show_time就是装饰器,它把真正的业务方法f包裹在函数里面,看起来像foo被上下时间函数装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。

@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作

装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

装饰器有2个特性,一是可以把被装饰的函数替换成其他函数, 二是可以在加载模块时候立即执行

def decorate(func):
print('running decorate', func)
def decorate_inner():
print('running decorate_inner function')
return func()
return decorate_inner
@decorate
def func_1():
print('running func_1')
if __name__ == '__main__':
print(func_1)
#running decorate <function func_1 at 0x000001904743DEA0>
# <function decorate.<locals>.decorate_inner at 0x000001904743DF28>
func_1()
#running decorate_inner function
# running func_1

通过args 和 *kwargs 传递被修饰函数中的参数

 def decorate(func):
def decorate_inner(*args, **kwargs):
print(type(args), type(kwargs))
print('args', args, 'kwargs', kwargs)
return func(*args, **kwargs)
return decorate_inner
@decorate
def func_1(*args, **kwargs):
print(args, kwargs)
if __name__ == '__main__':
func_1('', '', '', para_1='', para_2='', para_3='')
#返回结果
#<class 'tuple'> <class 'dict'>
# args ('1', '2', '3') kwargs {'para_1': '1', 'para_2': '2', 'para_3': '3'}
# ('1', '2', '3') {'para_1': '1', 'para_2': '2', 'para_3': '3'}

带参数的被装饰函数

 import time
# 定长
def show_time(f):
def inner(x,y):
start = time.time()
f(x,y)
end = time.time()
print('spend %s'%(end - start))
return inner
@show_time
def add(a,b):
print(a+b)
time.sleep(1)
add(1,2)

不定长

 import time
#不定长
def show_time(f):
def inner(*x,**y):
start = time.time()
f(*x,**y)
end = time.time()
print('spend %s'%(end - start))
return inner
@show_time
def add(*a,**b):
sum=0
for i in a:
sum+=i
print(sum)
time.sleep(1)
add(1,2,3,4)

带参数的装饰器

在上面的装饰器调用中,比如@show_time,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。

 import time
def time_logger(flag=0):
def show_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func(*args, **kwargs)
end_time = time.time()
print('spend %s' % (end_time - start_time))
if flag:
print('将这个操作的时间记录到日志中')
return wrapper
return show_time
@time_logger(flag=1)
def add(*args, **kwargs):
time.sleep(1)
sum = 0
for i in args:
sum += i
print(sum)
add(1, 2, 5)

@time_logger(flag=1) 做了两件事:

(1)time_logger(1):得到闭包函数show_time,里面保存环境变量flag

(2)@show_time   :add=show_time(add)

上面的time_logger是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器(一个含有参数的闭包函数)。当我 们使用@time_logger(1)调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。

叠放装饰器

执行顺序是什么

如果一个函数被多个装饰器修饰,其实应该是该函数先被最里面的装饰器修饰后(下面例子中函数main()先被inner装饰,变成新的函数),变成另一个函数后,再次被装饰器修饰

 def outer(func):
print('enter outer', func)
def wrapper():
print('running outer')
func()
return wrapper
def inner(func):
print('enter inner', func)
def wrapper():
print('running inner')
func()
return wrapper
@outer
@inner
def main():
print('running main')
if __name__ == '__main__':
main()
#返回结果
# enter inner <function main at 0x000001A9F2BCDF28>
# enter outer <function inner.<locals>.wrapper at 0x000001A9F2BD5048>
# running outer
# running inner
# running main

类装饰器

相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

 import time
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
start_time=time.time()
self._func()
end_time=time.time()
print('spend %s'%(end_time-start_time))
@Foo #bar=Foo(bar)
def bar():
print ('bar')
time.sleep(2)
bar() #bar=Foo(bar)()>>>>>>>没有嵌套关系了,直接active Foo的 __call__方法

标准库中有多种装饰器

例如:装饰方法的函数有property, classmethod, staticmethod; functools模块中的lru_cache, singledispatch,  wraps 等等

from functools import lru_cache
from functools import singledispatch
from functools import wraps

functools.wraps使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

 def foo():
print("hello foo")
print(foo.__name__)# foo
def logged(func):
def wrapper(*args, **kwargs):
print (func.__name__ + " was called")
return func(*args, **kwargs)
return wrapper
@logged
def cal(x):
resul=x + x * x
print(resul)
cal(2)
#
#cal was called
print(cal.__name__)# wrapper
print(cal.__doc__)#None
#函数f被wrapper取代了,当然它的docstring,__name__就是变成了wrapper函数的信息了。

使用装饰器会产生我们可能不希望出现的副作用, 例如:改变被修饰函数名称,对于调试器或者对象序列化器等需要使用内省机制的那些工具,可能会无法正常运行;

其实调用装饰器后,会将同一个作用域中原来函数同名的那个变量(例如下面的func_1),重新赋值为装饰器返回的对象;使用@wraps后,会把与内部函数(被修饰函数,例如下面的func_1)相关的重要元数据全部复制到外围函数(例如下面的decorate_inner)

 from functools import wraps
def decorate(func):
print('running decorate', func)
@wraps(func)
def decorate_inner():
print('running decorate_inner function', decorate_inner)
return func()
return decorate_inner
@decorate
def func_1():
print('running func_1', func_1)
if __name__ == '__main__':
func_1()
#输出结果
#running decorate <function func_1 at 0x0000023E8DBD78C8>
# running decorate_inner function <function func_1 at 0x0000023E8DBD7950>
# running func_1 <function func_1 at 0x0000023E8DBD7950>

---2019.11.21  21:13--多练习,熟能生巧--<https://www.jb51.net/article/167769.htm>

Python函数装饰器原理与用法详解《摘》的更多相关文章

  1. Python函数装饰器高级用法

    在了解了Python函数装饰器基础知识和闭包之后,开始正式学习函数装饰器. 典型的函数装饰器 以下示例定义了一个装饰器,输出函数的运行时间: 函数装饰器和闭包紧密结合,入参func代表被装饰函数,通过 ...

  2. python函数-装饰器

    python函数-装饰器 1.装饰器的原则--开放封闭原则 开放:对于添加新功能是开放的 封闭:对于修改原功能是封闭的 2.装饰器的作用 在不更改原函数调用方式的前提下对原函数添加新功能 3.装饰器的 ...

  3. 【python】redis基本命令和基本用法详解

    [python]redis基本命令和基本用法详解 来自http://www.cnblogs.com/wangtp/p/5636872.html 1.redis连接 redis-py提供两个类Redis ...

  4. Python @函数装饰器及用法

    1.函数装饰器的工作原理 函数装饰器的工作原理是怎样的呢?假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示: #funA 作为装饰器函数 def funA(fn): #... fn ...

  5. Python @函数装饰器及用法(超级详细)

    函数装饰器的工作原理是怎样的呢?假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示: #funA 作为装饰器函数 def funA(fn): #... fn() # 执行传入的fn参 ...

  6. Python 函数装饰器

    首次接触到装饰器的概念,太菜啦! Python 装饰器可以大大节省代码的编写量,提升代码的重复使用率.函数装饰器其本质也是一个函数,我们可以把它理解为函数中定义了一个子函数. 例如我们有这么一个需求, ...

  7. Python高手之路【四】python函数装饰器

    def outer(func): def inner(): print('hello') print('hello') print('hello') r = func() print('end') p ...

  8. python 函数 装饰器 内置函数

    函数 装饰器 内置函数 一.命名空间和作用域 二.装饰器 1.无参数 2.函数有参数 3.函数动态参数 4.装饰器参数 三.内置函数 salaries={ 'egon':3000, 'alex':10 ...

  9. Python 函数装饰器简明教程

    定义类的静态方法时,就使用了装饰器.其实面向对象中的静态方法都是使用了装饰器. @staticmethod def jump(): print(" 3 meters high") ...

随机推荐

  1. 04javascript02

    1.BOM编程 1.1入门 BOM就是浏览器对象模型编程,通过javascript引擎提供的四个浏览器对象,操作浏览器,这叫BOM编程. 1.2window对象(重点) <!DOCTYPE ht ...

  2. 09java进阶——IO

    1.File类 1.1目录及路径分隔符 package cn.jxufe.java.chapter09.demo01; import java.io.File; public class Test01 ...

  3. Hadoop之MapReduce 本机windows模式运行

    hadoop在windows本机运行 (1)在 windows环境下编译好的hadoop放到没有中文和空格的路径下 (2)编译好的hadoop内的hadoop.all文件要放到windows机器的wi ...

  4. 主席树(静态区间第k大)

    前言 如果要求一些数中的第k大值,怎么做? 可以先就这些数离散化,用线段树记录每个数字出现了多少次. ... 那么考虑用类似的方法来求静态区间第k大. 原理 假设现在要有一些数 我们可以对于每个数都建 ...

  5. 【leetcode】1160. Find Words That Can Be Formed by Characters

    题目如下: You are given an array of strings words and a string chars. A string is good if it can be form ...

  6. SonarQube规则之bug类型

    1.".equals()" should not be used to test the values of "Atomic" classes.bug 主要不要 ...

  7. java构造方法和重写equals

    Cell的构造函数 package Test; import java.util.Objects; public class Cell { int a; int b; public int getA( ...

  8. HDU 4511 小明系列故事——女友的考验 ( Trie图 && DP )

    题意 :  给出编号从1 ~ n 的 n 个平面直角坐标系上的点,求从给出的第一个点出发到达最后一个点的最短路径,其中有两种限制,其一就是只能从编号小的点到达编号大的点,再者不能走接下来给出的 m 个 ...

  9. Java——常用类(StringBuffer)

    [StringBuffer]   <1>java.lang.StringBuffer代表可变的字符序列. <2>StringBuffer和String类似,但是StringBu ...

  10. 搭建 .Net RabbitMQ 开发环境

    开发环境,window 10 64位,VS2017,系统账号需要用administrator. 1 先需要安装erlang语言开发包,一路默认安装就是了,地址:http://www.erlang.or ...