217

I need to mark routines as deprecated, but apparently there's no standard library decorator for deprecation. I am aware of recipes for it and the warnings module, but my question is: why is there no standard library decorator for this (common) task ?

Additional question: are there standard decorators in the standard library at all ?

Ken Williams
  • 22,756
  • 10
  • 85
  • 147
Stefano Borini
  • 138,652
  • 96
  • 297
  • 431
  • 23
    now there is a [deprecation](https://pypi.python.org/pypi/deprecation) package – muon Nov 01 '17 at 21:13
  • 19
    I understand the ways to do it, but came here for some insight on why it's not in the std lib (as I assume is the case of the OP) and don't see a good answer to the actual question – SwimBikeRun Apr 01 '19 at 14:57
  • 19
    Why does it happen so often that questions get dozens of answers that don't even attempt to answer the question, and actively ignore things like "I'm aware of recipes"? It's maddening! – Catskul May 02 '19 at 16:46
  • 7
    @Catskul because of fake internet points. – Stefano Borini May 02 '19 at 19:07
  • 1
    The OP rules out `warnings.warn`, but for those who aren't aware of it, I prefer it to anything found here for deprecation. https://stackoverflow.com/a/9008488/733092 – Noumenon Mar 06 '21 at 12:58
  • 2
    @Catskul Aren't *why* questions deprecated at all or shouldn't they be on SO? As we see so often, they tend to produce nonanswers. I would not have left this comment if I had not seen all your *doesn't answer* decorators – Wolf May 27 '21 at 12:21
  • 2
    I'm unaware of any ruling against "why" questions, but IMO lots of "why" questions are likely to have a succinct answer, and the question/answers can be important so I'd be sad if they were disallowed. – Catskul May 27 '21 at 15:34
  • Watch https://peps.python.org/pep-0702/ (slated for python 3.12) – Darren Weber Mar 16 '23 at 18:42

8 Answers8

91

Here's some snippet, modified from those cited by Leandro:

import warnings
import functools

def deprecated(func):
    """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."""
    @functools.wraps(func)
    def new_func(*args, **kwargs):
        warnings.simplefilter('always', DeprecationWarning)  # turn off filter
        warnings.warn("Call to deprecated function {}.".format(func.__name__),
                      category=DeprecationWarning,
                      stacklevel=2)
        warnings.simplefilter('default', DeprecationWarning)  # reset filter
        return func(*args, **kwargs)
    return new_func

# Examples

@deprecated
def some_old_function(x, y):
    return x + y

class SomeClass:
    @deprecated
    def some_old_method(self, x, y):
        return x + y

Because in some interpreters the first solution exposed (without filter handling) may result in a warning suppression.

endolith
  • 25,479
  • 34
  • 128
  • 192
Patrizio Bertoni
  • 2,582
  • 31
  • 43
  • 14
    Why not use `functools.wraps` rather than setting the name and doc like that? – Maximilian Aug 06 '15 at 14:40
  • 1
    @Maximilian: Edited to add that, to save future copy-pasters of this code from doing it wrong too – Eric Jun 28 '16 at 03:59
  • 33
    I do not like side effect (turning the filter on / off). It's not the job of the decorator to decide this. – Kentzo Apr 18 '17 at 17:48
  • 1
    Turning the filter on and off may trigger http://bugs.python.org/issue29672 – gerrit Jul 07 '17 at 16:13
  • 23
    doesn't answer the actual question. – Catskul May 02 '19 at 16:41
  • @Catskul The actual question would likely be subject to closure as being too subjective. – FeRD Jul 07 '20 at 16:35
  • 4
    Strongly agree with @Kentzo - disabling filters then resetting them to defaults is going to give some developer an amazing headache – Aaron V Dec 07 '20 at 21:34
  • Why `stackevel=2` here? What's deprecated is calling `new_func`, not calling the caller of `new_func`. (Note that `new_func` is what's actually called whenever someone calls the function that was decorated.) See the example case of using `stacklevel` in the documentation: https://docs.python.org/3/library/warnings.html#warnings.warn I don't think it applies here. – fonini Mar 30 '22 at 14:03
82

Here is another solution:

This decorator (a decorator factory in fact) allow you to give a reason message. It is also more useful to help the developer to diagnose the problem by giving the source filename and line number.

EDIT: This code use Zero's recommendation: it replace warnings.warn_explicit line by warnings.warn(msg, category=DeprecationWarning, stacklevel=2), which prints the function call site rather than the function definition site. It makes debugging easier.

EDIT2: This version allow the developper to specify an optional "reason" message.

import functools
import inspect
import warnings

string_types = (type(b''), type(u''))


def deprecated(reason):
    """
    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.
    """

    if isinstance(reason, string_types):

        # The @deprecated is used with a 'reason'.
        #
        # .. code-block:: python
        #
        #    @deprecated("please, use another function")
        #    def old_function(x, y):
        #      pass

        def decorator(func1):

            if inspect.isclass(func1):
                fmt1 = "Call to deprecated class {name} ({reason})."
            else:
                fmt1 = "Call to deprecated function {name} ({reason})."

            @functools.wraps(func1)
            def new_func1(*args, **kwargs):
                warnings.simplefilter('always', DeprecationWarning)
                warnings.warn(
                    fmt1.format(name=func1.__name__, reason=reason),
                    category=DeprecationWarning,
                    stacklevel=2
                )
                warnings.simplefilter('default', DeprecationWarning)
                return func1(*args, **kwargs)

            return new_func1

        return decorator

    elif inspect.isclass(reason) or inspect.isfunction(reason):

        # The @deprecated is used without any 'reason'.
        #
        # .. code-block:: python
        #
        #    @deprecated
        #    def old_function(x, y):
        #      pass

        func2 = reason

        if inspect.isclass(func2):
            fmt2 = "Call to deprecated class {name}."
        else:
            fmt2 = "Call to deprecated function {name}."

        @functools.wraps(func2)
        def new_func2(*args, **kwargs):
            warnings.simplefilter('always', DeprecationWarning)
            warnings.warn(
                fmt2.format(name=func2.__name__),
                category=DeprecationWarning,
                stacklevel=2
            )
            warnings.simplefilter('default', DeprecationWarning)
            return func2(*args, **kwargs)

        return new_func2

    else:
        raise TypeError(repr(type(reason)))

You can use this decorator for functions, methods and classes.

Here is a simple example:

@deprecated("use another function")
def some_old_function(x, y):
    return x + y


class SomeClass(object):
    @deprecated("use another method")
    def some_old_method(self, x, y):
        return x + y


@deprecated("use another class")
class SomeOldClass(object):
    pass


some_old_function(5, 3)
SomeClass().some_old_method(8, 9)
SomeOldClass()

You'll get:

deprecated_example.py:59: DeprecationWarning: Call to deprecated function or method some_old_function (use another function).
  some_old_function(5, 3)
deprecated_example.py:60: DeprecationWarning: Call to deprecated function or method some_old_method (use another method).
  SomeClass().some_old_method(8, 9)
deprecated_example.py:61: DeprecationWarning: Call to deprecated class SomeOldClass (use another class).
  SomeOldClass()

EDIT3: This decorator is now part of the Deprecated library:

New stable release v1.2.13

Laurent LAPORTE
  • 21,958
  • 6
  • 58
  • 103
  • 6
    Works, well - I prefer replacing the `warn_explicit` line with `warnings.warn(msg, category=DeprecationWarning, stacklevel=2)` which prints the function call site rather than the function definition site. It makes debugging easier. – Zero Nov 28 '16 at 23:24
  • Hello, I would like to use your code snippet in [a GPLv3-licensed library](https://github.com/FIDUCEO/FCDR_HIRS/). Would you be willing to relicense your code under GPLv3 [or any more permissive license](https://opensource.stackexchange.com/a/3/33), so that I can legally do so? – gerrit Jul 07 '17 at 17:19
  • @gerrit: [All user contributions are licensed under Creative Commons Attribution-Share Alike](https://stackoverflow.com/help/licensing). – Laurent LAPORTE Jul 09 '17 at 10:15
  • 1
    @LaurentLAPORTE I know. CC-BY-SO does not permit usage within GPLv3 (because of the share-alike bit), which is why I'm asking if you would be willing to release this code specifically additionally under a GPL-compatible license. If not, that's fine, and I won't use your code. – gerrit Jul 09 '17 at 14:27
  • 13
    doesn't answer the actual question. – Catskul May 02 '19 at 16:42
  • @gerrit GPL is actual a much less permissive license than most others. In many cases GPL should be avoided. – Danny Varod May 09 '21 at 10:39
  • 2
    @DannyVarod I know, but for code, CC-BY-SA is even more restrictive than GPL. When I asked the question, I was working on a GPL library. GPL libraries can use GPL code or more permissive code, but GPL libraries can *not* use CC-BY-SA code, so I was not able to use this code snippet. (And CC-BY-SA was never used for code anyway; SO would do good in licensing code snippets in user contributions under something more permissive, because as it is now, most users cannot use code snippets they find on SO) – gerrit May 09 '21 at 20:23
31

As muon suggested, you can install the deprecation package for this.

The deprecation library provides a deprecated decorator and a fail_if_not_removed decorator for your tests.

Installation

pip install deprecation

Example Usage

import deprecation

@deprecation.deprecated(deprecated_in="1.0", removed_in="2.0",
                        current_version=__version__,
                        details="Use the bar function instead")
def foo():
    """Do some stuff"""
    return 1

See http://deprecation.readthedocs.io/ for the full documentation.

CrackerJack9
  • 3,650
  • 1
  • 27
  • 48
Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
19

I guess the reason is that Python code can't be processed statically (as it done for C++ compilers), you can't get warning about using some things before actually using it. I don't think that it's a good idea to spam user of your script with a bunch of messages "Warning: this developer of this script is using deprecated API".

Update: but you can create decorator which will transform original function into another. New function will mark/check switch telling that this function was called already and will show message only on turning switch into on state. And/or at exit it may print list of all deprecated functions used in program.

ony
  • 12,457
  • 1
  • 33
  • 41
  • 5
    And you should be able to indicate deprecation **when the function is imported from the module**. Decorator would be a right tool for that. – Janusz Lenar Feb 21 '13 at 09:23
  • @JanuszLenar, that warning will be show even if we don't use that deprecated function. But I guess I can update my answer with some hint. – ony Feb 21 '13 at 11:37
19

You can create a utils file

import warnings

def deprecated(message):
  def deprecated_decorator(func):
      def deprecated_func(*args, **kwargs):
          warnings.warn("{} is a deprecated function. {}".format(func.__name__, message),
                        category=DeprecationWarning,
                        stacklevel=2)
          warnings.simplefilter('default', DeprecationWarning)
          return func(*args, **kwargs)
      return deprecated_func
  return deprecated_decorator

And then import the deprecation decorator as follows:

from .utils import deprecated

@deprecated("Use method yyy instead")
def some_method():
 pass
vitaly
  • 2,755
  • 4
  • 23
  • 35
Erika Dsouza
  • 1,015
  • 12
  • 8
4

Python 3.12 will include the typing.deprecated decorator which will indicate deprecations to type checkers like mypy.

As an example, consider this library stub named library.pyi:

from typing import deprecated

@deprecated("Use Spam instead")
class Ham: ...

@deprecated("It is pining for the fiords")
def norwegian_blue(x: int) -> int: ...

@overload
@deprecated("Only str will be allowed")
def foo(x: int) -> str: ...
@overload
def foo(x: str) -> str: ...

Here is how type checkers should handle usage of this library:

from library import Ham  # error: Use of deprecated class Ham. Use Spam instead.

import library

library.norwegian_blue(1)  # error: Use of deprecated function norwegian_blue. It is pining for the fiords.
map(library.norwegian_blue, [1, 2, 3])  # error: Use of deprecated function norwegian_blue. It is pining for the fiords.

library.foo(1)  # error: Use of deprecated overload for foo. Only str will be allowed.
library.foo("x")  # no error

ham = Ham()  # no error (already reported above)

Source: PEP702

thehale
  • 913
  • 10
  • 18
2

Python is a dynamically typed language. Not necessary declare the type to variable or argument type for function statically.

Since its dynamic every thing if processed at runtime. Even if a method is deprecated it will be known at runtime or during interpretation only.

use deprecation module to deprecate methods.

deprecation is a library that enables automated deprecations. It offers the deprecated() decorator to wrap functions, providing proper warnings both in documentation and via Python’s warnings system, as well as the deprecation.fail_if_not_removed() decorator for test methods to ensure that deprecated code is eventually removed.

Installing :

python3.10 -m pip install deprecation

Small demonstration:

import deprecation

@deprecation.deprecated(details="Use bar instead")
def foo():
    print("Foo")


def bar():
    print("Bar")


foo()

bar()

Output:

test.py: DeprecatedWarning: foo is deprecated. Use bar instead
  foo()

Foo

Bar
Udesh
  • 2,415
  • 2
  • 22
  • 32
0

UPDATE: I think is better, when we show DeprecationWarning only first time for each code line and when we can send some message:

import inspect
import traceback
import warnings
import functools

import time


def deprecated(message: str = ''):
    """
    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 first time and filter is set for show DeprecationWarning.
    """
    def decorator_wrapper(func):
        @functools.wraps(func)
        def function_wrapper(*args, **kwargs):
            current_call_source = '|'.join(traceback.format_stack(inspect.currentframe()))
            if current_call_source not in function_wrapper.last_call_source:
                warnings.warn("Function {} is now deprecated! {}".format(func.__name__, message),
                              category=DeprecationWarning, stacklevel=2)
                function_wrapper.last_call_source.add(current_call_source)

            return func(*args, **kwargs)

        function_wrapper.last_call_source = set()

        return function_wrapper
    return decorator_wrapper


@deprecated('You must use my_func2!')
def my_func():
    time.sleep(.1)
    print('aaa')
    time.sleep(.1)


def my_func2():
    print('bbb')


warnings.simplefilter('always', DeprecationWarning)  # turn off filter
print('before cycle')
for i in range(5):
    my_func()
print('after cycle')
my_func()
my_func()
my_func()

Result:

before cycle
C:/Users/adr-0/OneDrive/Projects/Python/test/unit1.py:45: DeprecationWarning: Function my_func is now deprecated! You must use my_func2!
aaa
aaa
aaa
aaa
aaa
after cycle
C:/Users/adr-0/OneDrive/Projects/Python/test/unit1.py:47: DeprecationWarning: Function my_func is now deprecated! You must use my_func2!
aaa
C:/Users/adr-0/OneDrive/Projects/Python/test/unit1.py:48: DeprecationWarning: Function my_func is now deprecated! You must use my_func2!
aaa
C:/Users/adr-0/OneDrive/Projects/Python/test/unit1.py:49: DeprecationWarning: Function my_func is now deprecated! You must use my_func2!
aaa

Process finished with exit code 0

We can just click on the warning path and go to the line in PyCharm.

ADR
  • 1,255
  • 9
  • 20