• python中装饰器的使用教程详解


    先看下面的函数简单理解一下

    示例代码:

    1. import logging
    2. def use_logging(func):
    3. logging.error("%s is running" % func.__name__)
    4. func()
    5. def bar():
    6. print('ccc')
    7. use_logging(bar)

    运行结果:

            上述代码中逻辑上不难理解,但每次都要将一个函数作为参数传递给use_logging函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行bar(),但是现在不得不改成use_logging(bar)。有种简单的方法就是使用装饰器

    简单装饰器实现:

    示例代码:

    1. import logging
    2. def use_logging(func):
    3. def wrapper(*args, **kwargs):
    4. logging.error("%s is running" % func.__name__)
    5. return func(*args, **kwargs)
    6. return wrapper
    7. def bar():
    8. print('ccc')
    9. bar = use_logging(bar)
    10. bar()

    运行结果:

            函数use_logging就是装饰器,它把真正执行的方法放在了func函数里面,看起来bar被use_logging装饰了。

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

    示例代码:

    1. import logging
    2. def use_logging(func):
    3. def wrapper(*args, **kwargs):
    4. logging.error("%s is running" % func.__name__)
    5. return func(*args, **kwargs)
    6. return wrapper
    7. @use_logging
    8. def bar():
    9. print('aaa')
    10. @use_logging
    11. def foo():
    12. print('bbb')
    13. bar()
    14. foo()

    运行结果:

             如上述代码所示,可以省去bar = use_logging(bar)这一句定义了,直接调用bar()即可得到想要的结果。对于其它函数也是可以直接调用装饰器来修饰函数的,而不用再次重复性的修改函数或增加新的封装。这样就提高了程序的可重复利用性,并增加了程序的可读性。

    带参数的装饰器:

            装饰器在使用的过程中可以传递参数,增大了装饰器的灵活性。在上述代码中,@use_logging装饰器唯一的参数就是执行业务的函数,在使用装饰器的时候可以传递参数,比如@decorator(a),这就为装饰器的编写和使用提供了更大的灵活性。

    示例代码:

    1. import logging
    2. def use_logging(level):
    3. def decorator(func):
    4. def wrapper(*args, **kwargs):
    5. if level == 'error':
    6. logging.error("%s is running" % func.__name__)
    7. return func(*args, **kwargs)
    8. return wrapper
    9. return decorator
    10. @use_logging(level='info')
    11. def bar():
    12. print('aaa')
    13. @use_logging(level='error')
    14. def foo():
    15. print('bbb')
    16. bar()
    17. foo()

     运行结果:

            上述代码中的use_logging是允许带参数的装饰器。实际上是对原有装饰器的一个函数封装,并返回一个装饰器。可以将它理解为一个含有参数的装饰器。当使用@use_logging(level="error")调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。

     关于带参数的装饰器,详见博文:带有参数的装饰器_IT之一小佬的博客-CSDN博客

     类装饰器:

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

    示例代码:

    1. class Test(object):
    2. def __init__(self, func):
    3. self._func = func
    4. def __call__(self, *args, **kwargs):
    5. print('class decorator running')
    6. self._func()
    7. print('class decorator ending')
    8. @Test
    9. def bar():
    10. print('bar')
    11. bar()

    运行结果:

     functools.wraps

            使用装饰器可以极大的复用了代码,但它也有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表等。

    示例代码:

    1. def logged(func):
    2. def with_logging(*args, **kwargs):
    3. print(func.__name__ + 'was called')
    4. return func(*args, **kwargs)
    5. return with_logging
    6. @logged
    7. def test():
    8. """
    9. does some math
    10. :param x:
    11. :return:
    12. """
    13. x = 6
    14. return x ** 2
    15. # 上述函数等价于
    16. def test2():
    17. """
    18. does some math
    19. :param x:
    20. :return:
    21. """
    22. x = 5
    23. return x ** 2
    24. a = test()
    25. print(a)
    26. b = logged(test2)
    27. print(b())
    28. print("*" * 100)
    29. print(test.__name__)
    30. print(test.__doc__)
    31. print("*" * 100)
    32. print(test2.__name__)
    33. print(test2.__doc__)

    运行结果:

            上述代码执行结果中不难看出,函数test被with_logging取代了,当然它的docstring,__name__等信息就变成了with_logging函数的信息了。

            出现上述问题有时候是问题是挺严重的,这时候我们可以使用functools.wraps库,wraps本身也是一个装饰器,它能够把原函数的元信息拷贝到装饰器函数中,这使用装饰器函数也有原函数一样的元信息了。

    示例代码:

    1. from functools import wraps
    2. def logged(func):
    3. @wraps(func)
    4. def with_logging(*args, **kwargs):
    5. print(func.__name__ + 'was called')
    6. return func(*args, **kwargs)
    7. return with_logging
    8. @logged
    9. def test():
    10. """
    11. does some math
    12. :param x:
    13. :return:
    14. """
    15. x = 6
    16. return x ** 2
    17. # 上述函数等价于
    18. def test2():
    19. """
    20. does some math
    21. :param x:
    22. :return:
    23. """
    24. x = 5
    25. return x ** 2
    26. a = test()
    27. print(a)
    28. b = logged(test2)
    29. print(b())
    30. print("*" * 100)
    31. print(test.__name__)
    32. print(test.__doc__)
    33. print("*" * 100)
    34. print(test2.__name__)
    35. print(test2.__doc__)

     运行结果:

    内置装饰器

    @staticmathod、@classmethod、@property

    装饰器的执行顺序:

    1. @a
    2. @b
    3. @c
    4. def f():
    5. pass
    6. # 等价于
    7. f = a(b(c(f)))

    参考博文:

    如何理解Python装饰器? - 知乎 

  • 相关阅读:
    【全栈计划 —— 编程语言之C#】基础入门知识一文懂
    Hibernate知识大合集
    基于C#实现的《勇士返乡》游戏设计
    【管理运筹学】第 7 章 | 图与网络分析(5,最小费用流问题及最小费用最大流问题)
    【音视频基础】封装格式与编码数据
    前端状态码大全(200,404,503等)
    C#接口和继承的区别、联系与使用场景
    ctfhub-端口扫描(SSRF)
    谈谈Go语言中函数的本质
    LeetCode精选200道--字符串篇
  • 原文地址:https://blog.csdn.net/weixin_44799217/article/details/125904334