python元编程_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)
# .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 元编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python创建进程的方法_python
- 下一篇: python数字编码_Python 编码