如何在 Python 中用 Decorator 顯示 deprecated 警告?
用途: 用來警告 function 或 class method 已過時,如果有指定取代的function的話,在runtime時改用取代的function
Python 2.7.2+ (default, Oct 4 2011, 20:03:08)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from vsgui.api import input_text
>>> input_text
<function ask_text at 0x8e0009c>
>>> input_text('input it')
vsgui/api.py:45: DeprecationWarning: Call to deprecated function input_text.; use ask_text instead
def input_text(*args, **kwargs):
用法一: 只宣告 Function 已被 depreacted, 不指定取代的 function。
@depreacted() # <- 這一段表示執行deprecated() 取得wrap function
def old1(): # 當old1()被呼叫時,實際上是先呼叫wrap(old1)以取得old1 function, 再執行old()
print 1
print 'function is '+old1
old1()
執行結果:
function is <function old1 at 0xb7317f44>
recipe-deprecated-warring.py:45: DeprecationWarning: Call to deprecated function old1.
def old1():
called old1
因為有使用 functools.wraps,所以雖然 old1 這個變數實際上已繫結在 wrap function, 但 print 時還是會顯示為 old1 function。
用法二: 除了警告外,還會在runtime時改執行取代depreacted function的新functoin
@depreacted(new1) # <- 這一段表示執行deprecated(new1) 取得wrap
def old2(): # 當old1()被呼叫時,實際上是呼叫wrap(old1) 取得new_func 再執行 new_func
print 1 # 而new_func會再執行new1,並把*args, **kwargs全數pass給new1
print 'function is '+old2
注意: 目前還不能對class method 指定 replacement method。
源始碼
重用了 Python Wiki - Smart deprecation warnings, Active Code Stack - deprecated 的部份源碼。
# took codes from the following:
#
#- [Python Wiki - Smart deprecation warnings ](http://wiki.python.org/moin/PythonDecoratorLibrary#Smart_deprecation_warnings_.28with_valid_filenames.2C_line_numbers.2C_etc..29)
# - [Active Code Stack - deprecated ](http://code.activestate.com/recipes/391367-deprecated/)
import os
import warnings
import functools
# enable to show warring
warnings.simplefilter('default')
def deprecated(replacement=None):
"""This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted
when the function is used.
ref:
- recipe-391367-1 on active stack code
- recipe-577819-1 on active stack code
@replacement function replacement function
"""
def wrapper(old_func):
wrapped_func = replacement and replacement or old_func
@functools.wraps(wrapped_func)
def new_func(*args, **kwargs):
msg = "Call to deprecated function %(funcname)s." % {
'funcname': old_func.__name__}
if replacement:
msg += "; use {} instead".format(replacement.__name__)
warnings.warn_explicit(msg,
category=DeprecationWarning,
filename=old_func.func_code.co_filename,
lineno=old_func.func_code.co_firstlineno + 1
)
return wrapped_func(*args, **kwargs)
return new_func
return wrapper
def new1():
print 'called new1'
@deprecated()
def old1():
print 'called old1'
@deprecated(new1)
def old2():
print 'called old1'
if __name__ == '__main__':
print old1
old1()
print old2
old2()
運作原理
- 第一個decorator用來產生第二個 decorator wrapper
- 第二個 decorator wrapper 用來產生新的 function new_func
- 執行新的 function new_func 會先顯示 warrning, 最後再把參數傳給要 wrapped_func,最後再傳回 wrapped_func 執行結果
- 在 runtime 時,因為是閉包的關係, 要執行的 wrapped_func 根據decorator wrapper的replacement參數來決定是用來代換的function, 還是原本的function.
下面是抽象化後的源碼
@deprecated()
def _sum(args):
pass
def deprecated(replacement=None): # 第一個 decorator
....
def real_decorator(original_func): # 第二個 decorator,
# 套用在original function 上, 也就是_sum
....
def func_with_warn(*args, **kwargs): # 產生好新function
....
# 顯示warring字串
....
return original_func(*args, **kwargs) # 回傳_sum的執行解果
return func_with_warn
return real_decorator