1.为函数添加包装器

总是存在这样的场景,在一个函数执行前后需要做一些操作处理,常见于日志创建、权限认证或者性能分析等。但有一个问题存在,那就是被装饰的函数,其元信息会丢失,函数引用会指向装饰器的返回值(函数)引用

这里介绍functools模块下的wraps函数, 能够避免函数元信息丢失的情况发生, 保留原始函数的元数据。

  1. from functools import wraps
  2. def outer_nowraps(func):
  3. def inner(*args, **kwargs):
  4. pass
  5. return func(*args, **kwargs)
  6. return inner
  7. def outer_wraps(func):
  8. @wraps(func)
  9. def inner(*args, **kwargs):
  10. pass
  11. return func(*args, **kwargs)
  12. return inner
  13. @outer_nowraps
  14. def handle_nowraps(*args, **kwargs00):
  15. pass
  16. @outer_wraps
  17. def handle_wraps(*args, **kwargs):
  18. pass
  19. if __name__ == '__main__':
  20. print(handle_nowraps)
  21. # <function outer_nowraps.<locals>.inner at 0x0000026363980620> 指向装饰器返回值(函数)
  22. help(handle_nowraps)
  23. # Help on function inner in module __main__:inner(*args, **kwargs)
  24. print(handle_wraps)
  25. # <function handle_wraps at 0x0000026363980730> 指向自身
  26. help(handle_wraps)
  27. # Help on function handle_wraps in module __main__:handle_wraps(*args, **kwargs)

此外,值得说明的是,装饰器就是一个函数,它接收一个函数作为参数返回一个新的函数(利用partial实现),例如以下情况是等效的

  1. @outer_wraps
  2. def handle_wraps(*args, **kwargs):
  3. pass
  4. handle_wraps = outer_wraps(handle_wraps)

对于类而言,诸如@staticmethod, @classmethod, @property原理也是一样的,例如以下情况是等效的

  1. class A:
  2. @classmethod
  3. def method(cls):
  4. pass
  5. class A:
  6. def method(cls):
  7. pass
  8. method = classmethod(method)

2.解除装饰器

如果一个装饰器(内部被wraps包装)已经作用在了一个函数上,如果想撤销它,直接访问原始的未包装的那个函数,可以使用该函数对象的__wrapped__属性来实现,当然并不是所有的装饰器内部均是使用wraps进行包装,例如常见的@staticmethod@classmethod@property等等,被这些装饰的函数是不具备解除装饰器的能力的。

3. 定义带参数的装饰器

对于logging模块来说,日志常常分为DEBUGINFOWARNINGERRORCRITICAL等,如果此时要实现一个装饰器,在不同函数上应用不同的装饰级别,就可以考虑使用一个带参数的装饰器来完成。

  1. from functools import wraps
  2. import logging
  3. def logged(level, name=None, message=None):
  4. """实现在不同函数上自定义日志级别及日志输出"""
  5. def decorator(func: function):
  6. log_name = name if name else func.__module__
  7. log = logging.getLogger(log_name)
  8. log_msg = message if message else func.__name__
  9. @wraps(func)
  10. def wrapper(*args, **kwargs):
  11. log.log(level, log_msg)
  12. return func(*args, **kwargs)
  13. return wrapper
  14. return decorator
  15. @logged(logging.DEBUG)
  16. def add(x, y):
  17. return x + y
  18. @logged(logging.CRITICAL, "example")
  19. def spam():
  20. print("Spam")

刚刚我们了解到了如何定义不带参数的装饰器,以及如何使用等效的语法表示,那么对于这种有参装饰器,又如何在语法上等效表示呢?logged(logging.DEBUG)实际上返回了一个decorator的引用,所以等效表示语法如下:

  1. add = logged(logging.DEBUG)(add) # 函数引用

4.可自定义属性的装饰器

这也正是Python作为面向对象语言的高级特性,在装饰器返回时,实际上是一个函数引用被接收,那么,这个函数也是function对象,一切对象都可以动态的自定义添加属性,由此便可以实现操作最内层函数引用的属性的方式,来动态的改变装饰器最外层作用域的变量(nonlocal)。

  1. from functools import wraps
  2. from functools import partial
  3. import logging
  4. def modify_wrapper(obj, func=None):
  5. if func is None:
  6. return partial(modify_wrapper, obj)
  7. setattr(obj, func.__name__, func)
  8. return func
  9. def logged(level, name=None, message=None):
  10. """实现在不同函数上自定义日志级别及日志输出"""
  11. def decorator(func):
  12. log_name = name if name else func.__module__
  13. log = logging.getLogger(log_name)
  14. log_msg = message if message else func.__name__
  15. @wraps(func)
  16. def wrapper(*args, **kwargs):
  17. log.log(level, log_msg)
  18. return func(*args, **kwargs)
  19. @modify_wrapper(wrapper)
  20. def set_level(newlevel):
  21. nonlocal level
  22. level = newlevel
  23. # 1. modify_wrapper(wrapper)返回一个partial(modify_wrapper, obj),固定了obj(即wrapper对象)
  24. # 2. 返回的partial对象接收了一个set_level函数对象参数(未固定)
  25. # 3. setattr(obj, func.__name__, func)为obj(即wrapper对象)添加了func(即set_level属性)
  26. # 4. 返回的仍然是set_level这一函数引用
  27. # 上述4步的作用就是为wrapper赋以set_level这一函数引用作为其属性
  28. # set_level = modify_wrapper(wrapper)(set_level)
  29. @modify_wrapper(wrapper)
  30. def set_message(new_msg):
  31. nonlocal log_msg
  32. log_msg = new_msg
  33. # 理由同上
  34. return wrapper
  35. return decorator
  36. @logged(logging.DEBUG)
  37. def add(x, y):
  38. return x + y
  39. @logged(logging.CRITICAL, "example")
  40. def spam():
  41. print("Spam")
  42. if __name__ == '__main__':
  43. logging.basicConfig(level=logging.DEBUG)
  44. print(add(1, 2)) # 输出: DEBUG:__main__:add 3
  45. add.set_message("set msg")
  46. print(add(3, 4)) # 输出: DEBUG:__main__:set msg 7
  47. add.set_level(logging.CRITICAL)
  48. print(add(1, 4)) # 输出: CRITICAL:__main__:set msg 5

上述代码的精妙之处就在于,

1.partial以装饰器(modify_wrapper)自身展开固定(固定参数是装饰器(logged)内部wrapper函数对象)。

2.当再次调用modify_wrapper时候(即modify_wrapper(wrapper)(set_level)时,不定参数funcset_level传递进来),在对wrapper完成属性绑定后,返回了set_level函数对象,并等待继续调用

Python 元编程的更多相关文章

  1. Python元编程

    简单定义"元编程是一种编写计算机程序的技术,这些程序可以将自己看做数据,因此你可以在运行时对它进行内审.生成和/或修改",本博参考<<Python高级编程>> ...

  2. python元编程(metaclass)

    Python元编程就是使用metaclass技术进行编程,99%的情况下不会使用,了解即可. Python中的类和对象 对于学习Python和使用Python的同学,你是否好奇过Python中的对象究 ...

  3. Python 元编程 - 装饰器

    Python 中提供了一个叫装饰器的特性,用于在不改变原始对象的情况下,增加新功能或行为. 这也属于 Python "元编程" 的一部分,在编译时一个对象去试图修改另一个对象的信息 ...

  4. Python高级编程第二版--笔记

    不只是CPython Stackless Python Jython(与java集成) IronPython(与net集成) PyPy python真正出众的领域在于围绕语言打造的整个生态系统. Py ...

  5. python高级编程之元类(第3部分结束)

    # -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #元编程 #new-style类带来了一种能力,通过2个特殊方法(_ ...

  6. Python类元编程

    类元编程是指在运行时创建或定制类.在Python中,类是一等对象,因此任何时候都可以使用函数创建新类,而无需用class关键字.类装饰器也是函数,不过能够审查.修改,甚至把被装饰的类替换成其他类.元类 ...

  7. Python的元编程案例

    Python的元编程案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是元编程 元编程概念来自LISP和smalltalk. 我们写程序是直接写代码,是否能够用代码来生成 ...

  8. Python 元类编程实现一个简单的 ORM

    概述 什么是ORM? ORM全称"Object Relational Mapping",即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码 ...

  9. python之元编程

    一.什么是元编程 元编程是一种编写计算机程序的技术,这些程序可以将自己看作数据,因此你可以在运行时对它进行内省.生成和/或修改. Python在语言层面对函数.类等基本类型提供了内省及实时创建和修改的 ...

随机推荐

  1. memcpy函数的实现

    1.按1个字节拷贝 (1)不要直接使用形参,要转换成char* (2)目标地址要实现保存 (3)要考虑源和目标内存重叠的情况 void * mymemcpy(void *dest, const voi ...

  2. Integrated SOA Gateway 不是当前用户的有效责任。请联系您的系统管理员。

    问题:给用户新增职责集成SOA网关,点击路径进入时出现报错:"Integrated SOA Gateway 不是当前用户的有效责任.请联系您的系统管理员." 解决:功能管理员职责, ...

  3. java项目路径总结,java.io.File支持的路放方式

    1.直接输入路径 已maven项目为例,直接输入路径的4种方式,即是File类支持的方式: /** * FileOutpurStream以字节数组方式写入文件 * @throws IOExceptio ...

  4. linux服务器问题排查:w命令卡住

    基本情况 系统: ubuntu16.04 症状: who命令可以用,w命令用不了 sudo iotop命令会卡住,黑屏 nvidia-smi命令和nvl命令都用不了,卡住 排查步骤 strace ps ...

  5. springboot 单元测试 指定启动类

    问题 在做单元测试时,写了一个工具类,用于注入spring的上下文. public class AppBeanUtil implements ApplicationContextAware { pri ...

  6. linux中网络部分的总结

    二.简述iproute家族命令 静态配置地址的方法有一下几种方式: (1)ifconfig (2)ip命令 (3)GUI工具 (4)TUI工具 (5)编辑配置文件 1.ifconfig 查看接口:if ...

  7. 【转】Linux 网络工具详解之 ip tuntap 和 tunctl 创建 tap/tun 设备

    原文:https://www.cnblogs.com/bakari/p/10449664.html -------------------------------------------------- ...

  8. python应用-解决现实应用题

    公鸡5元1只,母鸡3元1只,小鸡一元3只,100元买100只鸡,三种鸡各多少只 x+y+z=100 5*x+3*y+z//3=100 z%3==0 穷举法-穷尽所有的可能性找到真正的答案 for x ...

  9. oracle中删除表:drop、delete、truncate

    相同点,使用drop delete truncate 都会删除表中的内容 drop table 表名 delete from 表名(后面不跟where语句,则删除表中所有的数据) truncate t ...

  10. OpenGL是什么?GPU是什么?

    一.GPU与CPU CPU是处理基本算数运算的单元:它处理的数据是数:整型.浮点型.bool等等: GPU是处理图形运算的单元:它处理的数据是图形的数据矩阵:   GPU的输入是一个和多个图形,输出是 ...