前言

python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。
很多python初学者学到面向对象类和方法是一道大坎,那么python中的装饰器是你进入Python高级语法大门的一道坎。

计算函数运行时间

假设你写了几个函数,有一天领导心血来潮说,你把每个函数的运行时长(结束时间-开始时间)统计下,作为一个python实习生的你可能会这样写

原始函数

import time

def func_a():
print("hello")
time.sleep(0.5) def func_b():
print("world")
time.sleep(0.8) if __name__ == '__main__':
func_a()
func_b()

添加运行时长

作为一个实习生的你,可能想到的解决办法如下

import time

def func_a():
start = time.time()
print("hello")
time.sleep(0.5)
end = time.time()
print("运行时长:%.4f 秒" % (end-start)) def func_b():
start = time.time()
print("world")
time.sleep(0.8)
end = time.time()
print("运行时长:%.4f 秒" % (end-start)) if __name__ == '__main__':
func_a()
func_b()

运行结果:

hello
运行时长:0.5009 秒
world
运行时长:0.8008 秒

上面的代码虽然满足了领导的要求,但是如果你写的函数很多的话,每个函数都这样去添加,会显得代码很臃肿,有很多重复代码。
有一天你边上的一个python老司机看了下你的代码,给你指了条明路:装饰器

函数装饰器

装饰器可以写成函数式装饰器,也可以写成一个类装饰器,先从简单的函数装饰器开始学习。
python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象。

runtime函数就是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。在函数上方使用@语法糖就可以调用这个装饰器了

import time

def runtime(func):
def wrapper():
start = time.time()
f = func() # 原函数
end = time.time()
print("运行时长:%.4f 秒" % (end-start))
return f
return wrapper @runtime
def func_a():
print("hello")
time.sleep(0.5) @runtime
def func_b():
print("world")
time.sleep(0.8) if __name__ == '__main__':
func_a()
func_b()

运行结果

hello
运行时长:0.5001 秒
world
运行时长:0.8001 秒

函数带参数装饰器

上面的runtime就是一个简单的装饰器模型了,但并不强壮,如果函数里面带有参数,那就不管用了,并且函数的参数是不固定的,这时候就需要用到*args,**kwargs两兄弟了

import time

def runtime(func):
def wrapper(*args, **kwargs):
start = time.time()
f = func(*args, **kwargs) # 原函数
end = time.time()
print("运行时长:%.4f 秒" % (end-start))
return f
return wrapper @runtime
def func_a(a):
print("hello"+a)
time.sleep(0.5) @runtime
def func_b(b, c="xx"):
print("world"+b+c)
time.sleep(0.8) if __name__ == '__main__':
func_a("a")
func_b("b", c="xxx")

类装饰器

关于__call__方法,不得不先提到一个概念,就是可调用对象(callable),我们平时自定义的函数、内置函数和类都属于可调用对象,
但凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数 callable。
如果在类中实现了__call__方法,那么实例对象也将成为一个可调用对象

import time

class runtime(object):
def __init__(self, func):
self.func = func def __call__(self, *args, **kwargs):
start = time.time()
f = self.func(*args, **kwargs) # 原函数
end = time.time()
print("运行时长:%.4f 秒" % (end-start))
return f @runtime
def func_a(a):
print("hello"+a)
time.sleep(0.5) @runtime
def func_b(b, c="xx"):
print("world"+b+c)
time.sleep(0.8) if __name__ == '__main__':
func_a("a")
func_b("b", c="xxx")

装饰器带参数

快到年底了,领导说运行的速度先不要太快了,让客户先加钱,然后再以正常的速度显示,那么现在的需求是让每个函数的运行时间加50%,该如何实现呢?
这就到了装饰器的高级语法,装饰器也需要带上参数了

函数装饰器

import time

def runtime(slowly=1):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
start = time.time()
f = func(*args, **kwargs) # 原函数
end = time.time()
t = end-start
time.sleep((slowly-1)*t) # 延迟效果
new_end = time.time()
print("运行时长:%.4f 秒" % (new_end-start))
return f
return inner_wrapper
return wrapper @runtime(1.5)
def func_a(a):
print("hello"+a)
time.sleep(0.5) @runtime(1.5)
def func_b(b, c="xx"):
print("world"+b+c)
time.sleep(0.8) if __name__ == '__main__':
func_a("a")
func_b("b", c="xxx")

类装饰器

import time

class runtime(object):
def __init__(self, slowly=1):
self.slowly = slowly def __call__(self, func):
def wrapper(*args, **kwargs):
start = time.time()
f = func(*args, **kwargs) # 原函数
end = time.time()
t = end-start
time.sleep((self.slowly-1)*t) # 延迟效果
new_end = time.time()
print("运行时长:%.4f 秒" % (new_end-start))
return f
return wrapper @runtime(1.5)
def func_a(a):
print("hello"+a)
time.sleep(0.5) @runtime(1.5)
def func_b(b, c="xx"):
print("world"+b+c)
time.sleep(0.8) if __name__ == '__main__':
func_a("a")
func_b("b", c="xxx")

使用场景

用哪些地方需要使用装饰器呢?

  • 如果你用过locust,设置权重会用到@task(1),
  • 如果你用过pytest框架,使用fixture功能的时候经常会用到@pytest.fixture(scope="function")
  • allure里面可以添加测试步骤 @allure.step('修改购物车')
  • 被大量使用于Flask和Django web框架中,检查是否被授权去使用一个web应用的端点(endpoint)。如 @login_required
  • 也可以自己写个装饰器添加日志

前面一篇对python装饰器有了初步的了解了,但是还不够完美,领导看了后又提出了新的需求,希望运行的日志能显示出具体运行的哪个函数。

namedoc

__name__用于获取函数的名称,__doc__用于获取函数的docstring内容(函数的注释)

import time

def func_a(a):
'''func_a --> hello'''
print("hello"+a)
time.sleep(0.5)
return True def func_b(b, c="xx"):
'''func_b --> world'''
print("world"+b+c)
time.sleep(0.8)
return True if __name__ == '__main__':
print(func_a.__name__) # 结果 func_a
print(func_a.__doc__) # func_a --> hello
print(func_b.__name__) # func_b
print(func_b.__doc__) # func_b --> world

装饰器加函数名称日志

在装饰器里面添加2行代码,打印正在运行函数的名称和docstring内容

import time

def runtime(func):
'''runtime decorators'''
def wrapper(*args, **kwargs):
'''wrapper inner fuction'''
print("running function : %s" % func.__name__)
print("docstring: %s" % func.__doc__)
start = time.time()
f = func(*args, **kwargs) # 原函数
end = time.time()
print("运行时长:%.4f 秒" % (end-start))
return f
return wrapper @runtime
def func_a(a):
'''func_a --> hello'''
print("hello"+a)
time.sleep(0.5)
return True @runtime
def func_b(b, c="xx"):
'''func_b --> world'''
print("world"+b+c)
time.sleep(0.8)
return True if __name__ == '__main__':
func_a("a")
print(func_a.__name__)
print(func_a.__doc__)

运行结果

running function : func_a
docstring: func_a --> hello
helloa
运行时长:0.5008 秒
wrapper
wrapper inner fuction

从运行的结果可以看出,func_a.__name__运行的结果是wrapper,func_a.__doc__运行的结果是wrapper inner fuction。
也就是说被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),那这个问题如何解决呢?
这就需要用到functools里面的一个wraps函数了

functools

当func_a函数被装饰后,导致了一个副作用:自身的函数属性和docstring内容变成了wrapper函数的属性了。
这里需用到functools里面的一个wraps的装饰器来消除这样的副作用。

import time
from functools import wraps def runtime(func):
'''runtime decorators'''
@wraps(func)
def wrapper(*args, **kwargs):
'''wrapper inner fuction'''
print("running function : %s" % func.__name__)
print("docstring: %s" % func.__doc__)
start = time.time()
f = func(*args, **kwargs) # 原函数
end = time.time()
print("运行时长:%.4f 秒" % (end-start))
return f
return wrapper

只需在wrapper函数上加上@wraps(func)即可解决

运行结果

running function : func_a
docstring: func_a --> hello
helloa
运行时长:0.5004 秒
func_a
func_a --> hello

类装饰器

带参数的装饰器,可以写成类装饰器

import time
from functools import wraps class runtime(object):
'''runtime class decorators'''
def __init__(self, slowly=1):
self.slowly = slowly def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
'''wrapper inner fuction'''
print("running function : %s" % func.__name__)
print("docstring: %s" % func.__doc__)
start = time.time()
f = func(*args, **kwargs) # 原函数
end = time.time()
t = end-start
time.sleep((self.slowly-1)*t) # 延迟效果
new_end = time.time()
print("运行时长:%.4f 秒" % (new_end-start))
return f
return wrapper @runtime(1.5)
def func_a(a):
'''func_a --> hello'''
print("hello"+a)
time.sleep(0.5)
return True @runtime()
def func_b(b, c="xx"):
'''func_b --> world'''
print("world"+b+c)
time.sleep(0.8)
return True if __name__ == '__main__':
func_a("a")
print(func_a.__name__)
print(func_a.__doc__)

运行结果

running function : func_a
docstring: func_a --> hello
helloa
运行时长:0.7522 秒
func_a
func_a --> hello

来源:https://mp.weixin.qq.com/s/1-U6RAd_dDKpYDXMJCqWAw

面试题-python 什么是装饰器(decorator )?的更多相关文章

  1. python语法32[装饰器decorator](转)

    一 装饰器decorator decorator设计模式允许动态地对现有的对象或函数包装以至于修改现有的职责和行为,简单地讲用来动态地扩展现有的功能.其实也就是其他语言中的AOP的概念,将对象或函数的 ...

  2. python 语法之 装饰器decorator

    装饰器 decorator 或者称为包装器,是对函数的一种包装. 它能使函数的功能得到扩充,而同时不用修改函数本身的代码. 它能够增加函数执行前.执行后的行为,而不需对调用函数的代码做任何改变. 下面 ...

  3. python中的装饰器decorator

    python中的装饰器 装饰器是为了解决以下描述的问题而产生的方法 我们在已有的函数代码的基础上,想要动态的为这个函数增加功能而又不改变原函数的代码 例如有三个函数: def f1(x): retur ...

  4. 浅析python中的装饰器decorator

    最近学习python,其中decorator比较难理解,遂写一篇来总结供后续查阅. 定义一个函数,想在运行时动态的改变函数的功能,又不想改变函数本身的代码,可以使用高阶函数(可以使用函数作为参数) 装 ...

  5. [转] Python中的装饰器(decorator)

    想理解Python的decorator首先要知道在Python中函数也是一个对象,所以你可以 将函数复制给变量 将函数当做参数 返回一个函数 函数在Python中和变量的用法一样也是一等公民,也就是高 ...

  6. Python学习笔记: 装饰器Decorator

    介绍 装饰器是对功能函数的加强. 在原来的功能函数之外,另外定义一个装饰器函数,对原来的功能函数进行封装(wrapper)并在wrapper的过程中增加一些辅助功能. 应用场景 如下场景: 业务函数f ...

  7. python 装饰器(decorator)

    装饰器(decorator) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 装饰器(decorator)是一种高级Python语 ...

  8. python函数编程-装饰器decorator

    函数是个对象,并且可以赋值给一个变量,通过变量也能调用该函数: >>> def now(): ... print('2017-12-28') ... >>> l = ...

  9. python让人头大的装饰器...decorator带参不带参用法和原理.,..

    0. 概念什么叫装饰器,其实也可以叫做包装器.即对于一个既有的函数func(args),在调用它之前和之后,我们希望都做一些事情,把这个函数包装起来. python中的装饰器分为两类:函数装饰器和类装 ...

  10. Python 装饰器Decorator(一)

    (一) 装饰器基础知识 什么是Python装饰器?Python里装饰器是一个可调用的对象(函数),其参数是另一个函数(被装饰的函数) 假如有一个名字为somedecorator的装饰器,target是 ...

随机推荐

  1. mac goland go env 环境变量 和 mac上终端go env 不一样

    编辑 vim ~/.zshrc. 在这里设置环境变量.goland 里面的才会生效.设置bash_profile 没用.

  2. Android 13 - Media框架(15)- OpenMax(三)

    关注公众号免费阅读全文,进入音视频开发技术分享群! 上一节学习了 media.codec 服务中的部分内容,这一节我们将一起了解 OMX IL 层的 API 以及相关的结构体等内容. 1.相关路径 以 ...

  3. zabbix笔记_006 zabbix web监控

    web监控 web监控是对http网站服务进行监控,模拟用户访问网站,对特定的结果进行告警,通知管理员网站状态. web监控是运维必备知识点,通过实验能够熟悉配置和了解zabbix是如何监控web站点 ...

  4. mysql GROUP_CONCAT使用

    完整的语法如下: 1 group_concat([DISTINCT] 要连接的字段 [Order BY ASC/DESC 排序字段] [Separator '分隔符']) 基本查询 1 2 3 4 5 ...

  5. javascript class 方法的this指向问题

    踩坑记录 JavaScript 的 class 里面有两种定义方法的方式 普通函数(fun1) 箭头函数(fun2) class Obj { func1() { // write some code. ...

  6. JavaScript实现防抖节流函数

    review 防抖函数 防抖函数一般是短时间内多次触发,但是只有最后一次触发结束后的delay秒内会去执行相对应的处理函数. 相当于一个赛道里面一次只能跑一辆赛车,如果此时已经有一辆赛车在跑道里面跑, ...

  7. FFmpeg开发笔记(三十)解析H.264码流中的SPS帧和PPS帧

    ​<FFmpeg开发实战:从零基础到短视频上线>一书的"2.1.1  音视频编码的发展历程"介绍了H.26x系列的视频编码标准,其中H.264至今仍在广泛使用,无论视频 ...

  8. 前端学习之nvm

    接手了新的项目 需要使用nodejs,但是版本又不同如何解决呢 如果自己下载配置环境变量也太复杂了 下载nvm https://nvm.uihtm.com/download.html 使用nvm 下载 ...

  9. Xcode调试内存最新理解

    前提: Xcode 16.0 beta 设置 Scheme设置中勾选Malloc Scribble.Malloc Stack Logging. 这么做是为了在Memory Graph.Profile中 ...

  10. jdk17+spring6下打jar包

    由于特定情况,本机下有多个jdk,而JAVA_HOME又只有一个. 本人习惯在命令行下一个命令编译打包程序,如何解决这个问题? 研究了不少时间,得到了两个解决方案: 1.使用bat   --  非常烂 ...