décorateurs dans la lib standard python (@deprecated spécifiquement)

127

Je dois marquer les routines comme obsolètes, mais apparemment, il n'y a pas de décorateur de bibliothèque standard pour la désapprobation. Je connais les recettes et le module d'avertissement, mais ma question est: pourquoi n'y a-t-il pas de décorateur de bibliothèque standard pour cette tâche (commune)?

Question supplémentaire: y a-t-il des décorateurs standard dans la bibliothèque standard?

Stefano Borini
la source
13
maintenant il y a un paquet de dépréciation
muon
11
Je comprends les moyens de le faire, mais je suis venu ici pour savoir pourquoi ce n'est pas dans la bibliothèque std (comme je suppose que c'est le cas de l'OP) et je ne vois pas de bonne réponse à la question réelle
SwimBikeRun
4
Pourquoi arrive-t-il si souvent que les questions reçoivent des dizaines de réponses qui n'essaient même pas de répondre à la question, et ignorent activement des choses comme "Je connais les recettes"? C'est exaspérant!
Catskul
1
@Catskul à cause de faux points Internet.
Stefano Borini
1
Vous pouvez utiliser la bibliothèque obsolète .
Laurent LAPORTE

Réponses:

59

Voici un extrait de code, modifié de ceux cités par 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

Parce que dans certains interprètes, la première solution exposée (sans gestion de filtre) peut entraîner une suppression d'avertissement.

Patrizio Bertoni
la source
14
Pourquoi ne pas utiliser functools.wrapsplutôt que de définir le nom et le document comme ça?
Maximilian
1
@Maximilian: Modifié pour ajouter cela, pour éviter que les futurs copieurs de ce code ne se trompent aussi
Eric
17
Je n'aime pas les effets secondaires (activer / désactiver le filtre). Ce n'est pas le travail du décorateur de décider cela.
Kentzo
1
L'activation et la désactivation du filtre peut déclencher bugs.python.org/issue29672
gerrit
4
ne répond pas à la question réelle.
Catskul
44

Voici une autre solution:

Ce décorateur (une usine de décorateurs en fait) vous permet de donner un message de raison . Il est également plus utile d'aider le développeur à diagnostiquer le problème en donnant le nom du fichier source et le numéro de ligne .

EDIT : Ce code utilise la recommandation de Zero: il remplace la warnings.warn_explicitligne par warnings.warn(msg, category=DeprecationWarning, stacklevel=2), qui imprime le site d'appel de fonction plutôt que le site de définition de fonction. Cela facilite le débogage.

EDIT2 : Cette version permet au développeur de spécifier un message optionnel "raison".

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

Vous pouvez utiliser ce décorateur pour les fonctions , les méthodes et les classes .

Voici un exemple simple:

@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()

Tu auras:

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: Ce décorateur fait maintenant partie de la bibliothèque obsolète:

Nouvelle version stable v1.2.10 🎉

Laurent LAPORTE
la source
6
Fonctionne bien - je préfère remplacer la warn_explicitligne par warnings.warn(msg, category=DeprecationWarning, stacklevel=2)laquelle imprime le site d'appel de fonction plutôt que le site de définition de fonction. Cela facilite le débogage.
Zéro
Bonjour, je souhaite utiliser votre extrait de code dans une bibliothèque sous licence GPLv3 . Seriez-vous prêt à renouveler la licence de votre code sous GPLv3 ou toute autre licence plus permissive , afin que je puisse légalement le faire?
gerrit
1
@LaurentLAPORTE Je sais. CC-BY-SO ne permet pas l'utilisation dans GPLv3 (à cause du bit de partage identique), c'est pourquoi je vous demande si vous seriez prêt à publier ce code spécifiquement sous une licence compatible GPL. Sinon, c'est bien et je n'utiliserai pas votre code.
gerrit le
2
ne répond pas à la question réelle.
Catskul
15

Comme muon l'a suggéré , vous pouvez installer le deprecationpackage pour cela.

La deprecationbibliothèque met à votre disposition un deprecateddécorateur et un fail_if_not_removeddécorateur pour vos tests.

Installation

pip install deprecation

Exemple d'utilisation

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

Voir http://deprecation.readthedocs.io/ pour la documentation complète.

Stevoisiak
la source
4
ne répond pas à la question réelle.
Catskul
1
Remarque PyCharm ne reconnaît pas cela
cz
12

Je suppose que la raison est que le code Python ne peut pas être traité de manière statique (comme cela a été le cas pour les compilateurs C ++), vous ne pouvez pas être averti de l'utilisation de certaines choses avant de l'utiliser. Je ne pense pas que ce soit une bonne idée de spammer l'utilisateur de votre script avec un tas de messages "Attention: ce développeur de ce script utilise une API obsolète".

Mise à jour: mais vous pouvez créer un décorateur qui transformera la fonction d'origine en une autre. La nouvelle fonction marquera / vérifiera le commutateur indiquant que cette fonction a déjà été appelée et affichera un message uniquement lors de la mise en marche du commutateur. Et / ou à la sortie, il peut afficher la liste de toutes les fonctions obsolètes utilisées dans le programme.

seulement
la source
3
Et vous devriez être en mesure d'indiquer la dépréciation lorsque la fonction est importée du module . Le décorateur serait un bon outil pour cela.
Janusz Lenar
@JanuszLenar, cet avertissement sera affiché même si nous n'utilisons pas cette fonction obsolète. Mais je suppose que je peux mettre à jour ma réponse avec un indice.
ony
8

Vous pouvez créer un fichier utils

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

Et puis importez le décorateur d'obsolescence comme suit:

from .utils import deprecated

@deprecated("Use method yyy instead")
def some_method()"
 pass
Erika Dsouza
la source
Merci, j'utilise ceci pour envoyer l'utilisateur au bon endroit au lieu de simplement afficher le message d'obsolescence!
German Attanasio
3
ne répond pas à la question réelle.
Catskul
2

MISE À JOUR: Je pense que c'est mieux, lorsque nous affichons DeprecationWarning uniquement la première fois pour chaque ligne de code et lorsque nous pouvons envoyer un 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()

Résultat:

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

Nous pouvons simplement cliquer sur le chemin d'avertissement et aller à la ligne dans PyCharm.

ADR
la source
2
ne répond pas à la question réelle.
Catskul
0

En augmentant cette réponse de Steven Vascellaro :

Si vous utilisez Anaconda, installez d'abord le deprecationpackage:

conda install -c conda-forge deprecation 

Ensuite, collez ce qui suit en haut du fichier

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

Voir http://deprecation.readthedocs.io/ pour la documentation complète.

omerbp
la source
4
ne répond pas à la question réelle.
Catskul