decoratori nella lib standard di python (@deprecato specificamente)


127

Devo contrassegnare le routine come deprecate, ma a quanto pare non esiste un decoratore di libreria standard per la deprecazione. Sono a conoscenza delle ricette per esso e per il modulo avvisi, ma la mia domanda è: perché non esiste un decoratore di librerie standard per questa attività (comune)?

Domanda aggiuntiva: ci sono decoratori standard nella libreria standard?


13
ora c'è un pacchetto deprecato
muon

11
Capisco i modi per farlo, ma sono venuto qui per avere un'idea del motivo per cui non è nella libreria std (come presumo sia il caso dell'OP) e non vedo una buona risposta alla domanda reale
SwimBikeRun

4
Perché accade così spesso che le domande ottengano dozzine di risposte che non tentano nemmeno di rispondere alla domanda e ignorano attivamente cose come "Sono a conoscenza delle ricette"? È esasperante!
Catskul

1
@Catskul a causa di falsi internet point.
Stefano Borini

1
Puoi utilizzare la libreria obsoleta .
Laurent LAPORTE

Risposte:


59

Ecco qualche snippet, modificato da quelli citati da 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

Perché in alcuni interpreti la prima soluzione esposta (senza la gestione del filtro) può comportare una soppressione dell'avviso.


14
Perché non utilizzare functools.wrapsinvece di impostare il nome e il documento in questo modo?
Massimiliano

1
@Maximilian: Modificato per aggiungerlo, per evitare che anche i futuri copy-pasters di questo codice lo facciano male
Eric

17
Non mi piacciono gli effetti collaterali (attivazione / disattivazione del filtro). Non è compito del decoratore decidere questo.
Kentzo

1
L'attivazione e la disattivazione del filtro potrebbe attivare bugs.python.org/issue29672
gerrit

4
non risponde alla domanda effettiva.
Catskul

44

Ecco un'altra soluzione:

Questo decoratore (una fabbrica di decoratori in effetti) ti consente di dare un messaggio di motivazione . È anche più utile aiutare lo sviluppatore a diagnosticare il problema fornendo il nome del file sorgente e il numero di riga .

EDIT : questo codice utilizza la raccomandazione di Zero: sostituisce la warnings.warn_explicitriga conwarnings.warn(msg, category=DeprecationWarning, stacklevel=2) , che stampa il sito della chiamata di funzione anziché il sito di definizione della funzione. Rende più facile il debug.

EDIT2 : questa versione consente allo sviluppatore di specificare un messaggio di "motivo" opzionale.

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

Puoi usare questo decoratore per funzioni , metodi e classi .

Qui c'è un semplice esempio:

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

Otterrai:

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: questo decoratore ora fa parte della libreria obsoleta:

Nuova versione stabile v1.2.10 🎉


6
Funziona bene - preferisco sostituire la warn_explicitriga con warnings.warn(msg, category=DeprecationWarning, stacklevel=2)cui stampa il sito della chiamata di funzione piuttosto che il sito di definizione della funzione. Rende più facile il debug.
Zero

Ciao, vorrei utilizzare il tuo frammento di codice in una libreria con licenza GPLv3 . Saresti disposto a rilasciare nuovamente la licenza del tuo codice sotto GPLv3 o qualsiasi licenza più permissiva , in modo che io possa farlo legalmente?
gerrit


1
@LaurentLAPORTE Lo so. CC-BY-SO non consente l'utilizzo all'interno della GPLv3 (a causa del bit share-alike), motivo per cui ti chiedo se sei disposto a rilasciare questo codice in modo specifico in aggiunta a una licenza compatibile con GPL. In caso contrario, va bene e non userò il tuo codice.
gerrit

2
non risponde alla domanda effettiva.
Catskul

15

Come suggerito da muon , puoi installare il deprecationpacchetto per questo.

La deprecationbiblioteca fornisce un deprecateddecoratore e un fail_if_not_removeddecoratore per i tuoi test.

Installazione

pip install deprecation

Utilizzo di esempio

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

Vedere http://deprecation.readthedocs.io/ per la documentazione completa.


4
non risponde alla domanda effettiva.
Catskul

1
Nota PyCharm non lo riconosce
cz

12

Immagino che il motivo sia che il codice Python non può essere elaborato staticamente (come fatto per i compilatori C ++), non puoi ricevere avvertimenti sull'utilizzo di alcune cose prima di usarlo effettivamente. Non penso che sia una buona idea inviare spam all'utente del tuo script con un mucchio di messaggi "Attenzione: questo sviluppatore di questo script sta usando API deprecate".

Aggiornamento: ma puoi creare decoratore che trasformerà la funzione originale in un'altra. La nuova funzione segnerà / controllerà l'interruttore indicando che questa funzione è già stata chiamata e mostrerà il messaggio solo quando lo si accende. E / o all'uscita può stampare un elenco di tutte le funzioni deprecate utilizzate nel programma.


3
E dovresti essere in grado di indicare la deprecazione quando la funzione viene importata dal modulo . Decorator sarebbe uno strumento giusto per questo.
Janusz Lenar

@JanuszLenar, l'avviso verrà mostrato anche se non usiamo quella funzione deprecata. Ma immagino di poter aggiornare la mia risposta con qualche suggerimento.
ony

8

Puoi creare un file 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

Quindi importa il decoratore deprecato come segue:

from .utils import deprecated

@deprecated("Use method yyy instead")
def some_method()"
 pass

Grazie, lo sto usando per inviare l'utente al posto giusto invece di mostrare semplicemente il messaggio di deprecazione!
Tedesco Attanasio

3
non risponde alla domanda effettiva.
Catskul

2

AGGIORNAMENTO: Penso che sia meglio, quando mostriamo DeprecationWarning solo la prima volta per ogni riga di codice e quando possiamo inviare un messaggio:

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

Risultato:

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

Possiamo semplicemente fare clic sul percorso di avviso e andare alla riga in PyCharm.


2
non risponde alla domanda effettiva.
Catskul

0

Aumentando questa risposta di Steven Vascellaro :

Se usi Anaconda, prima installa il deprecationpacchetto:

conda install -c conda-forge deprecation 

Quindi incolla quanto segue all'inizio del file

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

Vedere http://deprecation.readthedocs.io/ per la documentazione completa.


4
non risponde alla domanda effettiva.
Catskul
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.