开发者

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).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜