1.为函数添加包装器

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

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

from functools import wraps

def outer_nowraps(func):

def inner(*args, **kwargs):

pass

return func(*args, **kwargs)

return inner

def outer_wraps(func):

@wraps(func)

def inner(*args, **kwargs):

pass

return func(*args, **kwargs)

return inner

@outer_nowraps

def handle_nowraps(*args, **kwargs00):

pass

@outer_wraps

def handle_wraps(*args, **kwargs):

pass

if __name__ == '__main__':

print(handle_nowraps)

# .inner at 0x0000026363980620> 指向装饰器返回值(函数)

help(handle_nowraps)

# Help on function inner in module __main__:inner(*args, **kwargs)

print(handle_wraps)

# 指向自身

help(handle_wraps)

# Help on function handle_wraps in module __main__:handle_wraps(*args, **kwargs)

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

@outer_wraps

def handle_wraps(*args, **kwargs):

pass

handle_wraps = outer_wraps(handle_wraps)

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

class A:

@classmethod

def method(cls):

pass

class A:

def method(cls):

pass

method = classmethod(method)

2.解除装饰器

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

3. 定义带参数的装饰器

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

from functools import wraps

import logging

def logged(level, name=None, message=None):

"""实现在不同函数上自定义日志级别及日志输出"""

def decorator(func: function):

log_name = name if name else func.__module__

log = logging.getLogger(log_name)

log_msg = message if message else func.__name__

@wraps(func)

def wrapper(*args, **kwargs):

log.log(level, log_msg)

return func(*args, **kwargs)

return wrapper

return decorator

@logged(logging.DEBUG)

def add(x, y):

return x + y

@logged(logging.CRITICAL, "example")

def spam():

print("Spam")

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

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

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

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

from functools import wraps

from functools import partial

import logging

def modify_wrapper(obj, func=None):

if func is None:

return partial(modify_wrapper, obj)

setattr(obj, func.__name__, func)

return func

def logged(level, name=None, message=None):

"""实现在不同函数上自定义日志级别及日志输出"""

def decorator(func):

log_name = name if name else func.__module__

log = logging.getLogger(log_name)

log_msg = message if message else func.__name__

@wraps(func)

def wrapper(*args, **kwargs):

log.log(level, log_msg)

return func(*args, **kwargs)

@modify_wrapper(wrapper)

def set_level(newlevel):

nonlocal level

level = newlevel

# 1. modify_wrapper(wrapper)返回一个partial(modify_wrapper, obj),固定了obj(即wrapper对象)

# 2. 返回的partial对象接收了一个set_level函数对象参数(未固定)

# 3. setattr(obj, func.__name__, func)为obj(即wrapper对象)添加了func(即set_level属性)

# 4. 返回的仍然是set_level这一函数引用

# 上述4步的作用就是为wrapper赋以set_level这一函数引用作为其属性

# set_level = modify_wrapper(wrapper)(set_level)

@modify_wrapper(wrapper)

def set_message(new_msg):

nonlocal log_msg

log_msg = new_msg

# 理由同上

return wrapper

return decorator

@logged(logging.DEBUG)

def add(x, y):

return x + y

@logged(logging.CRITICAL, "example")

def spam():

print("Spam")

if __name__ == '__main__':

logging.basicConfig(level=logging.DEBUG)

print(add(1, 2)) # 输出: DEBUG:__main__:add 3

add.set_message("set msg")

print(add(3, 4)) # 输出: DEBUG:__main__:set msg 7

add.set_level(logging.CRITICAL)

print(add(1, 4)) # 输出: CRITICAL:__main__:set msg 5

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

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

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

python元编程_Python 元编程相关推荐

  1. python元编程_python元编程详解(3)

    今天转载一片非常精彩的文章供大家欣赏:参考文章链接. python开发者门户一个很好的学习python的网站,大家有时间可以多看看. 下面正式开始今天的内容: 在理解元类之前,你需要先掌握Python ...

  2. python 鱼骨图_python面向对象编程,鱼骨图分析法

    一.面向对象编程 ​ 面向过程编程,核心是编程二字,过程指的是解决问题的步骤,即先干什么.后干什么.再干什么.然后干什么-- 与工厂的工艺流程差不多,前后都有影响 优点:复杂的问题流程化,进而简单化, ...

  3. python socket编程_Python Socket编程实现网络编程

    对于有经验的开发人员来说,掌握的编程语言应该是不少的.在这些编程语言中,网络编程的应用时必不可少的.其中Python也是这样的编程语言.我们今天将会在这里为大家详细介绍一下Python Socket编 ...

  4. 理解python并发编程_Python并发编程很简单

    上次已经和大家探讨了关于进程和线程的区别和联系相关的东东,今天呢,咱们再次回到 好啦,废话少说,咱们就开始吧! 首先说一下哦,_thread和threading哦,到这可能有朋友会问了,这两个有什么区 ...

  5. python 网络编程_Python网络编程(六)

    回顾 在<Python进阶记录之网络编程(五)>中,我们介绍了如何利用TCP服务端和客户端实现一个简单的点对点聊天,区别基于TCP协议和UDP协议的不同之处.今天我们讲一下如何利用多进程和 ...

  6. python udp通信_Python网络编程(三)

    回顾 在<Python进阶记录之网络编程(二)>中,我们介绍了UDP端口号的基本概念和作用以及Python中UDP服务端的端口绑定和简单创建.今天我们利用UDP server和UDP cl ...

  7. python元类_Python元类

    python元类 Welcome to today's tutorial on python metaclass. We all know that python is an object orien ...

  8. python元类编程_python元类编程

    什么叫元类?   年轻人先不要在意这些细节.我们一步一步的来! 001. oop的世界里有一句话 "万物皆对象" classPerson(object): name=Noneif ...

  9. python完全支持面向对象编程_python面向对象编程----009

    本篇内容: 1.反射 2.面向对象编程 3.面向对象三大特性 4.类成员 5.类成员修饰符 6.类的特殊成员 7.单例模式 反射 python中的反射功能是由以下四个内置函数提供:hasattr.ge ...

最新文章

  1. CondLaneNet:基于条件卷积的自顶向下车道检测框架
  2. 操作系统常用调度算法
  3. 树的先序遍历,中序遍历,后续遍历(递归和非递归实现)
  4. dotnetcore Http服务器研究(一)
  5. nginx注册为windows系统服务
  6. 循环控制_break语句
  7. docker选择安装位置_监控摄像机的安装位置选择和焦距选择
  8. icp光谱仪的工作原理_ICP2060T ICP光谱仪
  9. Ubuntu root账号的使用
  10. meet --- 位运算
  11. 吴恩达神经网络和深度学习-学习笔记-45-完全版YOLO算法
  12. 《java编程思想》
  13. 基于粒子群算法的IEEE-30节点系统MATLAB配网无功优化仿真,有各变量参数,及仿真程序
  14. 第19节贝叶斯原理及实例
  15. 性能与实用兼具 Parallels Desktop 13 for Mac全球首发
  16. C++第三方库HPSocket数据的发送与接收
  17. Python Pycharm 对代码进行TODO标记注释
  18. Swiper(介绍、官网链接、引入链接、使用方式,滑动样式属性含义、查找滑动效果、动态Swiper、Vue-CLI引入)
  19. 移动物联网卡APN如何设置?
  20. 20172305 结对编程项目-四则运算 第二周 阶段总结

热门文章

  1. 开启Linux系统路由转发功能 实现多网段电脑共享上网
  2. cuda core和sp
  3. 【算法】一个简单的k均值(k-means)原理
  4. [云炬创业基础笔记]第十一章创业计划书测试9
  5. Encoder-Decoder (based on RNNS / LSTM)用于序列学习方案
  6. 图像及其表达与性质(上)
  7. 编译原理之正则表达式
  8. 转 ajax.dll 与 ajaxpro.dll的用法
  9. Delphi 中的 Var buffer 开类型参数
  10. 什么是索引?为什么要建立索引?并举例说明.(以某一具体的DBMS为例)