1Python进阶强化训练之装饰器使用技巧进阶

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:

如何使用函数装饰器?

实际案例

某些时候我们想为多个函数,统一添加某种功能,比如记时统计、记录日志、缓存运算结果等等。

我们不想在每个函数内一一添加完全相同的代码,有什么好的解决方案呢?


解决方案

定义装饰奇函数,用它来生成一个在原函数基础添加了新功能的函数,替代原函数

如有如下两道题:

题目一

斐波那契数列又称黄金分割数列,指的是这样一个数列:1,1,2,3,5,8,13,21,….,这个数列从第三项开始,每一项都等于前两项之和,求数列第n项。

题目二

一个共有10个台阶的楼梯,从下面走到上面,一次只能迈1-3个台阶,并且不能后退,走完整个楼梯共有多少种方法?

脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 函数装饰器
def  memp(func):
     cache  =  {}
     
     def  wrap( * args):
         if  args  not  in  cache:
             cache[args]  =  func( * args)
         return  cache[args]
         
     return  wrap
     
     
# 第一题
@memp
def  fibonacci(n):
     if  n < =  1 :
         return  1
     return  fibonacci(n  -  1 +  fibonacci(n  -  2 )
     
print (fibonacci( 50 ))
 
 
# 第二题
@memp
def  climb(n, steps):
     count  =  0
     if  = =  0 :
         count  =  1
     elif  n >  0 :
         for  step  in  steps:
             count  + =  climb(n  -  step, steps)
     return  count
     
     
print (climb( 10 , ( 1 2 3 )))

输出结果:

1
2
3
4
5
C:\Python\Python35\python.exe E: / python - intensive - training / s11.py
20365011074
274
 
Process finished with exit code  0

如何为被装饰的函数保存元数据?

实际案例

在函数对象张保存着一些函数的元数据,例如:

方法 描述
f.__name__ 函数的名字
f.__doc__ 函数文档字符串
f.__module__ 函数所属模块名
f.__dict__ 属性字典
f.__defaults__ 默认参数元素

我们在使用装饰器后,再使用上面的这些属性访问时,看到的是内部包裹函数的元数据,原来函数的元数据变丢失掉了,应该如何解决?

解决方案

使用标准库functools中的装饰器wraps装饰内部包裹函数,可以指定将原来函数的某些属性更新到包裹函数上面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from  functools  import  wraps
 
def  mydecoratot(func):
     @wraps(func)
     def  wrapper( * args,  * * kwargs):
         """wrapper function"""
         print ( "In wrapper" )
         func( * args,  * * kwargs)
     return  wrapper
     
@mydecoratot
def  example():
     """example function"""
     print ( 'In example' )
     
print (example.__name__)
print (example.__doc__)

输出结果:

1
2
3
4
5
C:\Python\Python35\python.exe E: / python - intensive - training / s12.py
example
example function
 
Process finished with exit code  0

如何定义带参数的装饰器?

实际案例

实现一个装饰器,它用来检查被装饰函数的参数类型,装饰器可以通过指定函数参数的类型,调用时如果检测出类型不匹配则抛出异常,比如调用时可以写成如下:

1
2
3
@typeassert ( str int int )
def  f(a, b, c):
    ......

或者

1
2
3
@typeassert (y = list )
def  g(x, y):
    ......

解决方案

提取函数签名:inspect.signature()

带参数的装饰器,也就是根据参数定制化一个装饰器,可以看成生产装饰器的工厂,美的调用typeassert,返回一个特定的装饰器,然后用他去装饰其他函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from  inspect  import  signature
 
def  typeassery( * ty_args,  * * ty_kwargs):
     def  decorator(func):
         # 获取到函数参数和类型之前的关系
         sig  =  signature(func)
         btypes  =  sig.bind_partial( * ty_args,  * * ty_kwargs).arguments
         
         def  wrapper( * args,  * * kwargs):
             for  name, obj  in  sig.bind( * args,  * * kwargs).arguments.items():
                 if  name  in  btypes:
                     if  not  isinstance (obj, btypes[name]):
                         raise  TypeError( '"%s" must be "%s" '  %  (name, btypes[name]))
             return  func( * args,  * * kwargs)
             
         return  wrapper
         
     return  decorator
     
@typeassery ( int str list )
def  f(a, b, c):
     print (a, b, c)
     
# 正确的
f( 1 'abc' , [ 1 2 3 ])
# 错误的
f( 1 2 , [ 1 2 3 ])

执行结果

1
2
3
4
5
6
7
8
9
10
C:\Python\Python35\python.exe E: / python - intensive - training / s13.py
1  abc [ 1 2 3 ]
Traceback (most recent call last):
   File  "E:/python-intensive-training/s13.py" , line  28 in  <module>
     f( 1 2 , [ 1 2 3 ])
   File  "E:/python-intensive-training/s13.py" , line  14 in  wrapper
     raise  TypeError( '"%s" must be "%s" '  %  (name, btypes[name]))
TypeError:  "b"  must be  "<class 'str'>" 
 
Process finished with exit code  1

如何实现属性可修改的函数装饰器?

实际案例

为分析程序内那些函数执行时间开销较大,我们定义一个带timeout参数的函数装饰器,装饰功能如下:

  1. 统计被装饰函数单词调用运行时间

  2. 时间大于参数timeout的,将此次函数调用记录到log日志中

  3. 运行时可修改timeout的值

解决方案

为包裹函数增加一个函数,用来修改闭包中使用的自由变量

在python3中使用nonlocal访问嵌套作用于中的变量引用

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from  functools  import  wraps
import  time
import  logging
from  random  import  randint
 
def  warn(timeout):
     # timeout = [timeout]  # py2
     
     def  decorator(func):
         def  wrapper( * args,  * * kwargs):
             start  =  time.time()
             res  =  func( * args,  * * kwargs)
             used  =  time.time()  -  start
             if  used > timeout:
                 # if used > timeout:  # py2
                 msg  =  '"%s": "%s" > "%s"'  %  (func.__name__, used, timeout)
                 # msg = '"%s": "%s" > "%s"' % (func.__name__, used, timeout[0])  # py2
                 logging.warn(msg)
             return  res
             
         def  setTimeout(k):
             nonlocal timeout
             timeout  =  k
             # timeout[0] = k  # py2
             
         wrapper.setTimeout  =  setTimeout
         return  wrapper
         
     return  decorator
     
@warn ( 1.5 )
def  test():
     print ( 'In Tst' )
     while  randint( 0 1 ):
         time.sleep( 0.5 )
         
for  in  range ( 10 ):
     test()
     
test.setTimeout( 1 )
 
for  in  range ( 10 ):
     test()

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
C:\Python\Python35\python.exe E: / python - intensive - training / s14.py
In Tst
In Tst
WARNING:root: "test" "2.503000259399414"  "1.5"
In Tst
In Tst
In Tst
In Tst
In Tst
In Tst
In Tst
In Tst
In Tst
WARNING:root: "test" "1.0008063316345215"  "1"
In Tst
In Tst
In Tst
WARNING:root: "test" "1.0009682178497314"  "1"
In Tst
In Tst
WARNING:root: "test" "1.5025172233581543"  "1"
In Tst
In Tst
In Tst
In Tst
Process finished with exit code  0

如何在类中定义装饰器?

实际案例

实现一个能将函数调用信息记录到日志的装饰器:

  1. 把每次函数的调用时间,执行时间,调用次数写入日志

  2. 可以对被装饰函数分组,调用信息记录到不同日志

  3. 动态修改参数,比如日志格式

  4. 动态打开关闭日志输出功能

解决方案

为了让装饰器在使用上更加灵活,可以把类的实例方法作为装饰器,此时包裹函数中就可以持有实例对象,便于修改属性和扩展功能

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import  logging
from  time  import  localtime, time, strftime, sleep
from  random  import  choice
 
class  CallingInfo:
     def  __init__( self , name):
         log  =  logging.getLogger(name)
         log.setLevel(logging.INFO)
         fh  =  logging.FileHandler(name  +  '.log' )   # 日志保存的文件
         log.addHandler(fh)
         log.info( 'Start' .center( 50 '-' ))
         self .log  =  log
         self .formattter  =  '%(func)s -> [%(time)s - %(used)s - %(ncalls)s]'
         
     def  info( self , func):
         def  wrapper( * args,  * * kwargs):
             wrapper.ncalls  + =  1
             lt  =  localtime()
             start  =  time()
             res  =  func( * args,  * * kwargs)
             used  =  time()  -  start
             info  =  {}
             info[ 'func' =  func.__name__
             info[ 'time' =  strftime( '%x %x' , lt)
             info[ 'used' =  used
             info[ 'ncalls' =  wrapper.ncalls
             msg  =  self .formattter  %  info
             self .log.info(msg)
             return  res
             
         wrapper.ncalls  =  0
         return  wrapper
         
     def  SetFormatter( self , formatter):
         self .formattter  =  formatter
         
     def  turnOm( self ):
         self .log.setLevel(logging.INFO)
         
     def  turnOff( self ):
         self .log.setLevel(logging.WARN)
         
cinfo1  =  CallingInfo( 'mylog1' )
cinfo2  =  CallingInfo( 'mylog2' )
 
# 设置日志指定格式
# cinfo1.SetFormatter('%(func)s -> [%(time)s - %(ncalls)s]')
# 关闭日志
# cinfo2.turnOff()
 
@cinfo1 .info
def  f():
     print ( 'in F' )
     
@cinfo1 .info
def  g():
     print ( 'in G' )
     
@cinfo2 .info
def  h():
     print ( 'in H' )
     
for  in  range ( 50 ):
     choice([f, g, h])()
     sleep(choice([ 0.5 1 1.5 ]))









本文转自 Edenwy  51CTO博客,原文链接:http://blog.51cto.com/edeny/1924942,如需转载请自行联系原作者
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
7天前
|
开发者 Python
探索Python中的装饰器:从基础到高级应用
本文将带你深入了解Python中的装饰器,这一强大而灵活的工具。我们将一起探讨装饰器的基本概念,它们如何工作,以及如何使用它们来增强函数和类的功能,同时不改变其核心逻辑。通过具体代码示例,我们将展示装饰器的创建和使用,并探索一些高级应用,比如装饰器堆栈和装饰带参数的装饰器。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角,帮助你更有效地使用装饰器来简化和优化你的代码。
|
8天前
|
测试技术 数据安全/隐私保护 开发者
探索Python中的装饰器:从基础到高级应用
装饰器在Python中是一个强大且令人兴奋的功能,它允许开发者在不修改原有函数代码的前提下增加额外的功能。本文将通过具体代码示例,带领读者从装饰器的基础概念入手,逐步深入到高级用法,如带参数的装饰器和装饰器嵌套等。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
8天前
|
开发框架 数据建模 中间件
Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器是那些静悄悄的幕后英雄。它们不张扬,却能默默地为函数或类增添强大的功能。本文将带你了解装饰器的魅力所在,从基础概念到实际应用,我们一步步揭开装饰器的神秘面纱。准备好了吗?让我们开始这段简洁而富有启发性的旅程吧!
20 6
|
10天前
|
测试技术 Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界中,装饰器是那些能够为我们的代码增添魔力的小精灵。它们不仅让代码看起来更加优雅,还能在不改变原有函数定义的情况下,增加额外的功能。本文将通过生动的例子和易于理解的语言,带你领略装饰器的奥秘,从基础概念到实际应用,一起开启Python装饰器的奇妙旅程。
28 11
|
7天前
|
测试技术 开发者 Python
探索Python中的装饰器:从入门到实践
装饰器,在Python中是一块强大的语法糖,它允许我们在不修改原函数代码的情况下增加额外的功能。本文将通过简单易懂的语言和实例,带你一步步了解装饰器的基本概念、使用方法以及如何自定义装饰器。我们还将探讨装饰器在实战中的应用,让你能够在实际编程中灵活运用这一技术。
23 7
|
6天前
|
Python
探索Python中的装饰器:简化代码,增强功能
在Python的世界里,装饰器就像是给函数穿上了一件神奇的外套,让它们拥有了超能力。本文将通过浅显易懂的语言和生动的比喻,带你了解装饰器的基本概念、使用方法以及它们如何让你的代码变得更加简洁高效。让我们一起揭开装饰器的神秘面纱,看看它是如何在不改变函数核心逻辑的情况下,为函数增添新功能的吧!
|
7天前
|
程序员 测试技术 数据安全/隐私保护
深入理解Python装饰器:提升代码重用与可读性
本文旨在为中高级Python开发者提供一份关于装饰器的深度解析。通过探讨装饰器的基本原理、类型以及在实际项目中的应用案例,帮助读者更好地理解并运用这一强大的语言特性。不同于常规摘要,本文将以一个实际的软件开发场景引入,逐步揭示装饰器如何优化代码结构,提高开发效率和代码质量。
30 6
|
6天前
|
存储 缓存 Python
Python中的装饰器深度解析与实践
在Python的世界里,装饰器如同一位神秘的魔法师,它拥有改变函数行为的能力。本文将揭开装饰器的神秘面纱,通过直观的代码示例,引导你理解其工作原理,并掌握如何在实际项目中灵活运用这一强大的工具。从基础到进阶,我们将一起探索装饰器的魅力所在。
|
7天前
|
测试技术 开发者 Python
深入理解Python装饰器:从基础到高级应用
本文旨在为读者提供一个全面的Python装饰器指南,从其基本概念讲起,逐步深入探讨其高级应用。我们将通过实例解析装饰器的工作原理,并展示如何利用它们来增强函数功能、控制程序流程以及实现代码的模块化。无论你是Python初学者还是经验丰富的开发者,本文都将为你提供宝贵的见解和实用的技巧,帮助你更好地掌握这一强大的语言特性。
20 4
|
8天前
|
开发者 Python
Python中的装饰器:从入门到实践
本文将深入探讨Python的装饰器,这一强大工具允许开发者在不修改现有函数代码的情况下增加额外的功能。我们将通过实例学习如何创建和应用装饰器,并探索它们背后的原理和高级用法。
24 5