Get specific info about calling code in Python decorators
I have a Python decorator that I'm using for marking functions/methods as deprecated. It works sufficiently, but I'd like it to work better. Specifically, what I want for it to do is to be able to tell the exact line number of the call to the deprecated function. That way one does not have to grep through source code looking for it; instead the warning will point them directly to it. (That's not to say that someone wouldn't grep through the code anyway to look for other places that use call the deprecated function, but they shouldn't have to do that in response to such a warning, IMHO).
class deprecated(object):
'''This class implements the deprecated decorator.
This decorator is used to mark a function or a class as
deprecated. A message indicating why it is deprecated (and what
to use instead) must be provided. Also, a version number
indicating when the function or class was deprecated must _also_
be provided.
Optionally, a third parameter may be passed that indicates when
the deprecated functionality will actually be removed.'''
def __init__(self, message, version, to_be_removed_in_version = None):
self._message = message
self._version = version
self._removal_version = to_be_removed_in_version
return
def __call__(self, func):
def wrapper(*args, **kwargs):
import warnings
import inspect
frame = inspect.currentframe().f_back
where = 'Called from {0} somewhe开发者_StackOverflowre after line {1}.'
where = where.format(frame.f_code.co_filename,
frame.f_code.co_firstlineno)
msg = 'Call to deprecated function {0} (since {1}{2}) {3}'
if self._removal_version is not None:
removal = ', scheduled for removal by version {0}'
removal = removal.format(self._removal_version)
msg = msg.format(func.__name__, self._version,
removal, where)
warnings.warn_explicit(msg, category = DeprecationWarning,
filename = func.func_code.co_filename,
lineno = func.func_code.co_firstlineno + 1)
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
wrapper.__dict__.update(func.__dict__)
return wrapper
You don't need all that stuff:
from functools import wraps
import warnings
def deprecated(message, newfunc):
def _deprecated(func):
@wraps(func)
def _wrapped(*args, **kwds):
warnings.warn(message + ', ' + newfunc,
DeprecationWarning, stacklevel=2)
return func(*args, **kwds)
return _wrapped
return _deprecated
That's all! Check it out:
@deprecated("function foo is deprecated", "use bar instead")
def foo(bar):
print 'Hello', bar
foo('World')
I get
teste.py:17: DeprecationWarning: function foo is deprecated, use bar instead
foo('World')
Hello World
Another example:
class Foo(object):
@deprecated("method bar is deprecated", "use baz instead")
def bar(self, baz):
print 'Goodbye', baz
f = Foo()
f.bar('World')
I get:
teste.py:25: DeprecationWarning: method bar is deprecated, use baz instead
f.bar('World')
Goodbye World
The secret is the stacklevel
parameter to warnings.warn
. The docs say:
The stacklevel argument can be used by wrapper functions written in Python, (...) makes the warning refer to deprecation()‘s caller, rather than to the source of deprecation() itself (since the latter would defeat the purpose of the warning message).
精彩评论