Dekorateure in der Python-Standardbibliothek (@deprecated speziell)

127

Ich muss Routinen als veraltet markieren, aber anscheinend gibt es keinen Standard-Bibliotheksdekorateur für veraltet. Ich kenne die Rezepte dafür und das Warnmodul, aber meine Frage ist: Warum gibt es keinen Standard-Bibliotheksdekorateur für diese (allgemeine) Aufgabe?

Zusätzliche Frage: Gibt es überhaupt Standarddekorateure in der Standardbibliothek?

Stefano Borini
quelle
13
jetzt gibt es eine deprecation Paket
Myon
11
Ich verstehe die Möglichkeiten, aber ich bin hierher gekommen, um einen Einblick zu bekommen, warum es nicht in der Standardbibliothek enthalten ist (wie ich
annehme, dass
4
Warum kommt es so oft vor, dass Fragen Dutzende von Antworten erhalten, die nicht einmal versuchen, die Frage zu beantworten, und Dinge wie "Ich kenne Rezepte" aktiv ignorieren? Es ist verrückt!
Catskul
1
@Catskul wegen gefälschter Internetpunkte.
Stefano Borini
1
Sie können die veraltete Bibliothek verwenden.
Laurent LAPORTE

Antworten:

59

Hier ist ein Ausschnitt, der von den von Leandro zitierten geändert wurde:

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

Denn bei einigen Dolmetschern kann die erste exponierte Lösung (ohne Filterhandhabung) zu einer Warnunterdrückung führen.

Patrizio Bertoni
quelle
14
Warum nicht verwenden, functools.wrapsanstatt den Namen und das Dokument so festzulegen?
Maximilian
1
@ Maximilian: Bearbeitet, um das hinzuzufügen, um zukünftige Kopierer dieses Codes davor zu bewahren, es auch falsch zu machen
Eric
17
Ich mag keine Nebenwirkungen (Ein- / Ausschalten des Filters). Es ist nicht die Aufgabe des Dekorateurs, dies zu entscheiden.
Kentzo
1
Das Ein- und Ausschalten des Filters kann bugs.python.org/issue29672
gerrit
4
beantwortet die eigentliche Frage nicht.
Catskul
44

Hier ist eine andere Lösung:

Mit diesem Dekorateur ( in der Tat eine Dekorateurfabrik ) können Sie eine Begründungsnachricht geben . Es ist auch nützlicher, dem Entwickler bei der Diagnose des Problems zu helfen, indem Sie die Quelle angeben Dateinamen und Zeilennummer .

BEARBEITEN : Dieser Code verwendet die Empfehlung von Zero: Er ersetzt die warnings.warn_explicitZeile durch warnings.warn(msg, category=DeprecationWarning, stacklevel=2), wodurch die Funktionsaufrufsite anstelle der Funktionsdefinitionssite gedruckt wird. Es erleichtert das Debuggen.

EDIT2 : In dieser Version kann der Entwickler eine optionale "Grund" -Nachricht angeben.

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

Sie können diesen Dekorator für Funktionen verwenden , Methoden und Klassen verwenden .

Hier ist ein einfaches Beispiel:

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

Du wirst kriegen:

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: Dieser Dekorateur ist jetzt Teil der veralteten Bibliothek:

Neue stabile Version v1.2.10 🎉

Laurent LAPORTE
quelle
6
Funktioniert gut - ich bevorzuge es, die warn_explicitZeile zu ersetzen , durch warnings.warn(msg, category=DeprecationWarning, stacklevel=2)die die Funktionsaufruf-Site gedruckt wird, anstatt die Funktionsdefinitions-Site. Es erleichtert das Debuggen.
Null
Hallo, ich möchte Ihr Code-Snippet in einer GPLv3-lizenzierten Bibliothek verwenden . Wären Sie bereit, Ihren Code unter GPLv3 oder einer anderen zulässigen Lizenz neu zu lizenzieren , damit ich dies legal tun kann?
Gerrit
1
@LaurentLAPORTE Ich weiß. CC-BY-SO erlaubt keine Verwendung innerhalb von GPLv3 (aufgrund des Share-Alike-Bits), weshalb ich frage, ob Sie bereit wären, diesen Code speziell zusätzlich unter einer GPL-kompatiblen Lizenz freizugeben. Wenn nicht, ist das in Ordnung und ich werde Ihren Code nicht verwenden.
Gerrit
2
beantwortet die eigentliche Frage nicht.
Catskul
15

Wie von Myon vorgeschlagen , können Sie das deprecationPaket dafür installieren .

Die deprecationBibliothek bietet einen deprecatedDekorateur und einen fail_if_not_removedDekorateur für Ihre Tests.

Installation

pip install deprecation

Beispiel Verwendung

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

Die vollständige Dokumentation finden Sie unter http://deprecation.readthedocs.io/ .

Stevoisiak
quelle
4
beantwortet die eigentliche Frage nicht.
Catskul
1
Hinweis PyCharm erkennt dies nicht
cz
11

Ich denke, der Grund dafür ist, dass Python-Code nicht statisch verarbeitet werden kann (wie dies bei C ++ - Compilern der Fall ist). Sie können keine Warnung über die Verwendung einiger Dinge erhalten, bevor Sie ihn tatsächlich verwenden. Ich denke nicht, dass es eine gute Idee ist, Benutzer Ihres Skripts mit einer Reihe von Meldungen zu spammen. "Warnung: Dieser Entwickler dieses Skripts verwendet eine veraltete API."

Update: Sie können jedoch einen Dekorator erstellen, der die ursprüngliche Funktion in eine andere umwandelt. Die neue Funktion markiert / prüft den Schalter und zeigt an, dass diese Funktion bereits aufgerufen wurde. Die Meldung wird nur angezeigt, wenn der Schalter in den Ein-Zustand versetzt wird. Und / oder beim Beenden wird möglicherweise eine Liste aller im Programm verwendeten veralteten Funktionen gedruckt.

nur
quelle
3
Und Sie sollten in der Lage sein, Verfall anzuzeigen, wenn die Funktion aus dem Modul importiert wird . Der Dekorateur wäre dafür das richtige Werkzeug.
Janusz Lenar
@ JanuszLenar, diese Warnung wird angezeigt, auch wenn wir diese veraltete Funktion nicht verwenden. Aber ich denke, ich kann meine Antwort mit einem Hinweis aktualisieren.
Nur
8

Sie können eine Utils-Datei erstellen

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

Importieren Sie dann den Verfallsdekorateur wie folgt:

from .utils import deprecated

@deprecated("Use method yyy instead")
def some_method()"
 pass
Erika Dsouza
quelle
Vielen Dank, ich verwende dies, um den Benutzer an den richtigen Ort zu senden, anstatt nur die Verfallsmeldung anzuzeigen!
Deutsch Attanasio
3
beantwortet die eigentliche Frage nicht.
Catskul
2

UPDATE: Ich denke, es ist besser, wenn wir DeprecationWarning nur zum ersten Mal für jede Codezeile anzeigen und wenn wir eine Nachricht senden können:

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

Ergebnis:

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

Wir können einfach auf den Warnpfad klicken und zur Zeile in PyCharm gehen.

ADR
quelle
2
beantwortet die eigentliche Frage nicht.
Catskul
0

Ergänzen Sie diese Antwort von Steven Vascellaro :

Wenn Sie Anaconda verwenden, installieren Sie zuerst das deprecationPaket:

conda install -c conda-forge deprecation 

Fügen Sie dann Folgendes oben in die Datei ein

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

Die vollständige Dokumentation finden Sie unter http://deprecation.readthedocs.io/ .

omerbp
quelle
4
beantwortet die eigentliche Frage nicht.
Catskul