Python 元编程
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)
# <function outer_nowraps.<locals>.inner at 0x0000026363980620> 指向装饰器返回值(函数)
help(handle_nowraps)
# Help on function inner in module __main__:inner(*args, **kwargs)
print(handle_wraps)
# <function handle_wraps at 0x0000026363980730> 指向自身
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元编程
简单定义"元编程是一种编写计算机程序的技术,这些程序可以将自己看做数据,因此你可以在运行时对它进行内审.生成和/或修改",本博参考<<Python高级编程>> ...
- python元编程(metaclass)
Python元编程就是使用metaclass技术进行编程,99%的情况下不会使用,了解即可. Python中的类和对象 对于学习Python和使用Python的同学,你是否好奇过Python中的对象究 ...
- Python 元编程 - 装饰器
Python 中提供了一个叫装饰器的特性,用于在不改变原始对象的情况下,增加新功能或行为. 这也属于 Python "元编程" 的一部分,在编译时一个对象去试图修改另一个对象的信息 ...
- Python高级编程第二版--笔记
不只是CPython Stackless Python Jython(与java集成) IronPython(与net集成) PyPy python真正出众的领域在于围绕语言打造的整个生态系统. Py ...
- python高级编程之元类(第3部分结束)
# -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #元编程 #new-style类带来了一种能力,通过2个特殊方法(_ ...
- Python类元编程
类元编程是指在运行时创建或定制类.在Python中,类是一等对象,因此任何时候都可以使用函数创建新类,而无需用class关键字.类装饰器也是函数,不过能够审查.修改,甚至把被装饰的类替换成其他类.元类 ...
- Python的元编程案例
Python的元编程案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是元编程 元编程概念来自LISP和smalltalk. 我们写程序是直接写代码,是否能够用代码来生成 ...
- Python 元类编程实现一个简单的 ORM
概述 什么是ORM? ORM全称"Object Relational Mapping",即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码 ...
- python之元编程
一.什么是元编程 元编程是一种编写计算机程序的技术,这些程序可以将自己看作数据,因此你可以在运行时对它进行内省.生成和/或修改. Python在语言层面对函数.类等基本类型提供了内省及实时创建和修改的 ...
随机推荐
- 分布式系统根基:物理时钟和Lamport逻辑时钟
分布式系统解决了传统单体架构的单点问题和性能容量问题,另一方面也带来了很多的问题,其中一个问题就是多节点的时间同步问题:不同机器上的物理时钟难以同步,导致无法区分在分布式系统中多个节点的事件时序.19 ...
- Docker中上传镜像到docker hub中
原文参考:https://blog.csdn.net/sk_grace/article/details/81220675 申请Docker hub账号首先在https://hub.docker.com ...
- DOS常见命名整理
Dos命令 1.打开终端的方式 (1).window+R / 点击开始栏,输入cmd,然后回车 (2).在某个文件夹下打开终端 按住shift+右击,有个在此处打开命令窗口 2.修改终端背景和文字颜色 ...
- 补充1:IDA的脚本IDC语言
1.IDA脚本的打开与使用: IDA脚本两种语言:IDC(IDC,本地脚本语言)和Python 2.IDC语言介绍 1.IDC变量:IDC是一种松散的语言,没有明确的类型.使用3中数据类型,整数(ID ...
- 使用SAP CRM中间件XIF(External Interface)一步步创建服务订单
tcode WE19, choose an existing IDOC in the system: Just change the existing IDOC Service Order ID to ...
- Java开发环境之Tomcat
查看更多Java开发环境配置,请点击<Java开发环境配置大全> 壹章:Tomcat安装教程 1)去官网下载安装包 http://tomcat.apache.org/ 建议下载压缩包(zi ...
- Shell 编程 基础
本篇主要写一些shell脚本的基础知识,编程规范. 第一个shell脚本 [root@localhost ~]# vim first.sh #!/bin/bash # This is first Sh ...
- 关于IE环境下按回车键会自动触发button 按钮的点击事件的解决方案
今天项目中遇到IE不兼容的问题,于是就根据问题进行修改,修改过程中发现,在输入框内用扫码枪扫描东西后会自动执行页面下面的button按钮,但是其它浏览不会出现这样的问题. 解决方案: 1.用a标签 2 ...
- php中的Exception
如果定制的EXCEPTION搞定了,默认的,就自然不在话下罗. 直接上最曲折的过程. InvalidIdException.php <?php namespace Bookstore\Excep ...
- GitHub小知识与教程
如果你是一枚Coder,但是你不知道Github,那么我觉的你就不是一个菜鸟级别的Coder,因为你压根不是真正Coder,你只是一个Code搬运工. 但是你如果已经在读这篇文章了,我觉的你已经知道G ...