Python函数装饰器的使用教程

作者:自动化代码美学 时间:2022-10-26 03:01:06 

目录
  • 典型的函数装饰器

  • 叠放装饰器

  • 参数化装饰器

  • 标准库中的装饰器

    • functools.wraps

    • functools.lru_cache

    • functools.singledispatch

  • 小结

    • 参考资料:

      典型的函数装饰器

      以下示例定义了一个装饰器,输出函数的运行时间:

      Python函数装饰器的使用教程

      函数装饰器和闭包紧密结合,入参func代表被装饰函数,通过自由变量绑定后,调用函数并返回结果。

      使用clock装饰器:


      import time
      from clockdeco import clock

      @clock
      def snooze(seconds):
         time.sleep(seconds)

      @clock
      def factorial(n):
         return 1 if n < 2 else n*factorial(n-1)

      if __name__=='__main__':
         print('*' * 40, 'Calling snooze(.123)')
         snooze(.123)
         print('*' * 40, 'Calling factorial(6)')
         print('6! =', factorial(6))  # 6!指6的阶乘

      输出结果:

      Python函数装饰器的使用教程

      这是装饰器的典型行为:把被装饰的函数换成新函数,二者接受相同的参数,而且返回被装饰的函数本该返回的值,同时还会做些额外操作。

      值得注意的是factorial()是个递归函数,从结果来看,每次递归都用到了装饰器,打印了运行时间,这是因为如下代码:


      @clock
      def factorial(n):
         return 1 if n < 2 else n*factorial(n-1)

      等价于:


      def factorial(n):
         return 1 if n < 2 else n*factorial(n-1)

      factorial = clock(factorial)

      factorial引用的是clock(factorial)函数的返回值,也就是装饰器内部函数clocked,每次调用factorial(n),执行的都是clocked(n)。

      叠放装饰器


      @d1
      @d2
      def f():
         print("f")

      等价于:


      def f():
         print("f")

      f = d1(d2(f))

      参数化装饰器

      怎么让装饰器接受参数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。

      示例如下:


      registry = set()

      def register(active=True):
         def decorate(func):
             print('running register(active=%s)->decorate(%s)'
                   % (active, func))
             if active:
                 registry.add(func)
             else:
                 registry.discard(func)

      return func
         return decorate

      @register(active=False)
      def f1():
         print('running f1()')

      # 注意这里的调用
      @register()
      def f2():
         print('running f2()')

      def f3():
         print('running f3()')

      register是一个装饰器工厂函数,接受可选参数active默认为True,内部定义了一个装饰器decorate并返回。需要注意的是装饰器工厂函数,即使不传参数,也要加上小括号调用,比如@register()。

      再看一个示例:


      import time

      DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'

      # 装饰器工厂函数
      def clock(fmt=DEFAULT_FMT):
         # 真正的装饰器
         def decorate(func):
             # 包装被装饰的函数
             def clocked(*_args):
                 t0 = time.time()
                 # _result是被装饰函数返回的真正结果
                 _result = func(*_args)  
                 elapsed = time.time() - t0
                 name = func.__name__
                 args = ', '.join(repr(arg) for arg in _args)
                 result = repr(_result)
                 # **locals()返回clocked的局部变量
                 print(fmt.format(**locals()))  
                 return _result
             return clocked  
         return decorate

      if __name__ == '__main__':

      @clock()  
         def snooze(seconds):
             time.sleep(seconds)

      for i in range(3):
             snooze(.123)

      这是给典型的函数装饰器添加了参数fmt,装饰器工厂函数增加了一层嵌套,示例中一共有3个def。

      标准库中的装饰器

      Python内置了三个用于装饰方法的函数:property、classmethod和staticmethod,这会在将来的文章中讲到。本文介绍functools中的三个装饰器:functools.wraps、functools.lru_cache和functools.singledispatch。

      functools.wraps

      Python函数装饰器在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的装饰器来消除这样的副作用(它能保留原有函数的名称和函数属性)。

      示例,不加wraps:


      def my_decorator(func):
         def wrapper(*args, **kwargs):
             '''decorator'''
             print('Calling decorated function...')
             return func(*args, **kwargs)
         return wrapper

      @my_decorator
      def example():
         """Docstring"""
         print('Called example function')

      print(example.__name__, example.__doc__)
      # 输出wrapper decorator

      加wraps:


      import functools

      def my_decorator(func):
         @functools.wraps(func)
         def wrapper(*args, **kwargs):
             '''decorator'''
             print('Calling decorated function...')
             return func(*args, **kwargs)
         return wrapper

      @my_decorator
      def example():
         """Docstring"""
         print('Called example function')

      print(example.__name__, example.__doc__)
      # 输出example Docstring

      functools.lru_cache

      lru是Least Recently Used的缩写,它是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。

      示例:


      import functools

      from clockdeco import clock

      @functools.lru_cache()
      @clock
      def fibonacci(n):
         if n < 2:
             return n
         return fibonacci(n-2) + fibonacci(n-1)

      if __name__=='__main__':
         print(fibonacci(6))

      优化了递归算法,执行时间会减半。

      注意,lru_cache可以使用两个可选的参数来配置,它的签名如下:


      functools.lru_cache(maxsize=128, typed=False)
      • maxsize:最大存储数量,缓存满了以后,旧的结果会被扔掉。

      • typed:如果设为True,那么会把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整型参数(如1和1.0)区分开。

      functools.singledispatch

      Python3.4的新增语法,可以用来优化函数中的大量if/elif/elif。使用@singledispatch装饰的普通函数会变成泛函数:根据第一个参数的类型,以不同方式执行相同操作的一组函数。所以它叫做single dispatch,单分派。

      根据多个参数进行分派,就是多分派了。

      示例,生成HTML,显示不同类型的Python对象:


      import html

      def htmlize(obj):
         content = html.escape(repr(obj))
         return '<pre>{}</pre>'.format(content)

      因为Python不支持重载方法或函数,所以就不能使用不同的签名定义htmlize的变体,只能把htmlize变成一个分派函数,使用if/elif/elif,调用专门的函数,比如htmlize_str、htmlize_int等。时间一长htmlize会变得很大,跟各个专门函数之间的耦合也很紧密,不便于模块扩展。

      @singledispatch经过深思熟虑后加入到了标准库,来解决这类问题:


      from functools import singledispatch
      from collections import abc
      import numbers
      import html

      @singledispatch
      def htmlize(obj):
         # 基函数 这里不用写if/elif/elif来分派了
         content = html.escape(repr(obj))
         return '<pre>{}</pre>'.format(content)

      @htmlize.register(str)
      def _(text):
         # 专门函数
         content = html.escape(text).replace('\n', '<br>\n')
         return '<p>{0}</p>'.format(content)

      @htmlize.register(numbers.Integral)
      def _(n):
         # 专门函数
         return '<pre>{0} (0x{0:x})</pre>'.format(n)

      @htmlize.register(tuple)
      @htmlize.register(abc.MutableSequence)
      def _(seq):
         # 专门函数
         inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
         return '<ul>\n<li>' + inner + '</li>\n</ul>'

      @singledispatch装饰了基函数。专门函数使用@<<base_function>>.register(<<type>>)装饰,它的名字不重要,命名为_,简单明了。

      这样编写代码后,Python会根据第一个参数的类型,调用相应的专门函数。

      小结

      本文首先介绍了典型的函数装饰器:把被装饰的函数换成新函数,二者接受相同的参数,而且返回被装饰的函数本该返回的值,同时还会做些额外操作。接着介绍了装饰器的两个高级用法:叠放装饰器和参数化装饰器,它们都会增加函数的嵌套层级。最后介绍了3个标准库中的装饰器:保留原有函数属性的functools.wraps、缓存耗时的函数结果的functools.lru_cache和优化if/elif/elif代码的functools.singledispatch。

      参考资料:

      《流畅的Python》https://github.com/fluentpython/example-code/tree/master/07-closure-deco

      https://blog.csdn.net/liuzonghao88/article/details/103586634

      来源:https://www.cnblogs.com/df888/p/14833074.html

      标签:python,函数,装饰器
      0
      投稿

      猜你喜欢

    • Golang两行代码实现发送钉钉机器人消息

      2024-04-26 17:30:39
    • HTML和CSS中的视觉语义

      2010-07-09 13:08:00
    • .NET Core、Xamarin、.NET Standard和.NET Framework四者之间的区别介绍

      2023-07-13 03:38:13
    • laravel多视图共享数据实例代码

      2023-06-19 01:34:14
    • Python3.9.1中使用split()的处理方法(推荐)

      2022-04-17 23:16:12
    • 用SQL语句删除重复记录的四种方法

      2011-05-03 09:25:00
    • 经验总结:ASP与存储过程解析

      2008-11-07 13:33:00
    • Html的几个小技巧

      2011-04-29 14:02:00
    • 对pandas的算术运算和数据对齐实例详解

      2023-05-29 07:24:51
    • sqlserver 存储过程动态参数调用实现代码

      2011-10-24 19:41:22
    • 解决pytorch 保存模型遇到的问题

      2021-12-13 08:26:37
    • 一文详解Python中的Map,Filter和Reduce函数

      2022-03-02 07:51:28
    • Python数据预处理时缺失值的不同处理方式总结

      2022-02-14 22:58:25
    • 详解python的循环

      2022-12-29 04:22:52
    • Asp 操作Cookies(包括设置[赋值]、读取、删除[设置过期时间])

      2011-03-10 11:06:00
    • 讲解设计应用程序时避免阻塞的八个准则

      2009-01-04 14:16:00
    • Python日志模块logging简介

      2022-07-14 23:55:22
    • Python读取Excel数据实现批量生成PPT

      2021-03-05 23:57:35
    • Mysql数据库名和表名的大小写敏感性问题

      2010-06-07 14:07:00
    • ext3下删除mysql数据库的数据恢复案例

      2009-05-13 14:39:00
    • asp之家 网络编程 m.aspxhome.com