2010-03-29 9 views
57

Muszę oznaczyć podprogramy Pythona jako przestarzałe, ale nie mogę znaleźć standardowego dekoratora biblioteki do wycofania. Jestem świadomy receptur dla niego i modułu ostrzeżenia, ale moje pytanie brzmi:Jak mogę wycofać funkcje Pythona?

Dlaczego w Pythonie nie ma standardowego dekoratora biblioteki do deprecjonacji?

+4

teraz jest [deprecation] (https://pypi.python.org/ pypi/deprecation) pakiet – muon

+0

@muon Czy możesz to opublikować jako odpowiedź z użyciem przykładu? –

+0

@StevenVascellaro z pewnością! – muon

Odpowiedz

4

Sądzę, że powodem jest to, że kodu Pythona nie można przetwarzać statycznie (tak jak w przypadku kompilatorów C++), nie można uzyskać ostrzeżenia o korzystaniu z niektórych rzeczy przed jego użyciem. Nie sądzę, że dobrym pomysłem jest spamowanie użytkownika skryptu za pomocą kilku wiadomości "Uwaga: ten twórca tego skryptu używa przestarzałego API".

Aktualizacja:, ale można utworzyć dekorator, który przekształci oryginalną funkcję w inną. Nowa funkcja zaznaczy/sprawdzi przełącznik, informując, że ta funkcja została już wywołana i wyświetli komunikat tylko po przełączeniu przełącznika w stan włączony. I/lub przy wyjściu może wydrukować listę wszystkich nieaktualnych funkcji używanych w programie.

+1

tak, ale jeśli użytkownicy to ja, ja używam swoich zestawów testowych ... –

+2

Powinieneś być w stanie wskazać wycofanie ** gdy funkcja jest importowana z modułu **. Dekorator byłby odpowiednim narzędziem do tego. –

+0

@JanuszLenar, to ostrzeżenie pojawi się, nawet jeśli nie użyjemy tej przestarzałej funkcji. Ale myślę, że mogę zaktualizować moją odpowiedź z pewną wskazówką. – ony

28

Oto niektóre fragment, modyfikowany cytowane 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 

Ponieważ w niektórych tłumaczy pierwsze rozwiązanie narażone (bez przenoszenia filtra) może prowadzić do supresji ostrzegawczego.

+10

Dlaczego nie używać 'functools.wraps' zamiast ustawiania nazwy i dokumentu w ten sposób? – Maximilian

+1

@Maximilian: Edytowane, aby to dodać, aby uratować przyszłych pasterzy tego kodu przed robieniem tego zbyt źle – Eric

+4

Nie podoba mi się efekt uboczny (włączanie/wyłączanie filtra). Zadecydowanie o tym nie jest zadaniem dekoratora. – Kentzo

16

Oto inne rozwiązanie:

ten dekorator (a decorator factory w rzeczywistości) pozwalają dać powód wiadomość. Przydatne jest również pomóc programistom w zdiagnozowaniu problemu, podając nazwę pliku i o numerze linii.

EDIT: Ten kod używać rekomendację zera: zastąpić warnings.warn_explicit linia po warnings.warn(msg, category=DeprecationWarning, stacklevel=2), który drukuje stronę wywołania funkcji zamiast witrynę definicja funkcji. Ułatwia debugowanie.

EDIT2: Ta wersja umożliwia programistom określenie opcjonalnego komunikatu "przyczyna".

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

Można użyć tej funkcji dekorator dla , metod i klas.

Oto prosty przykład:

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

Dostaniesz:

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: ten dekorator jest obecnie częścią przestarzałej biblioteki:

+4

Działa, cóż - wolę zastąpić linię 'warn_explicit' z' warnings.warn (msg, category = DeprecationWarning, stacklevel = 2) która drukuje stronę wywołania funkcji, a nie stronę definicji funkcji. Ułatwia debugowanie. – Zero

+0

Witam, chciałbym użyć fragmentu kodu w [bibliotece licencjonowanej GPLv3] (https://github.com/FIDUCEO/FCDR_HIRS/). Czy byłbyś skłonny do reasekuracji swojego kodu na GPLv3 [lub jakiejkolwiek bardziej permisywnej licencji] (https://opensource.stackexchange.com/a/3/33), abym mógł to legalnie zrobić? – gerrit

+0

@gerrit: [Wszystkie udziały użytkownika są licencjonowane na licencji Creative Commons Uznanie autorstwa-na tych samych warunkach] (https://stackoverflow.com/help/licensing). –

1

UPDATE: Myślę, że jest lepiej, gdy pokażemy DeprecationWarning tylko za pierwszym razem na każdej linii kodu i kiedy możemy wysłać jakąś wiadomość:

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

Wynik:

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 

Możemy po prostu kliknąć ścieżkę ostrzegawczą i przejść do linii w PyCharm.

+1

'warnings.warn' już ma ustawienie" raz na linię kodu " – Eric

4

Można tworzyć a utils złożyć

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 

a następnie zaimportować dekorator Deprecation następująco:

from .utils import deprecated 

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

Dzięki, używam tego, aby wysłać użytkownika we właściwe miejsce zamiast pokazywać komunikat o wycofaniu! –