decoradores na lib padrão do python (@ especificamente reprovados)


127

Preciso marcar rotinas como obsoletas, mas aparentemente não há decorador de biblioteca padrão para obsoleta. Estou ciente das receitas para ele e o módulo de avisos, mas minha pergunta é: por que não existe um decorador de biblioteca padrão para essa tarefa (comum)?

Pergunta adicional: existem decoradores padrão na biblioteca padrão?


13
agora há uma depreciação pacote
— múon

11
Eu entendo as maneiras de fazer isso, mas vim aqui para ter uma ideia de por que não está na lib std (como eu presumo que seja o caso do OP) e não vejo uma boa resposta para a pergunta real
— SwimBikeRun

4
Por que isso acontece com tanta frequência que as perguntas obtêm dezenas de respostas que nem sequer tentam responder à pergunta e ignoram ativamente coisas como "Estou ciente das receitas"? É enlouquecedor!
— Catskul

1
@Catskul por causa de pontos de internet falsos.
— Stefano Borini

1
Você pode usar a Biblioteca Descontinuada .
— Laurent LAPORTE 20/05/19

Respostas:


59

Aqui está um trecho modificado daqueles citados por 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

Como em alguns intérpretes, a primeira solução exposta (sem manuseio de filtro) pode resultar em uma supressão de aviso.


14
Por que não usar em functools.wrapsvez de definir o nome e o documento assim?
— Maximilian

1
@Maximilian: Editado acrescentar que, para salvar futuros copy-pasters deste código de fazê-lo errado também
— Eric

17
Não gosto de efeitos colaterais (ligar / desligar o filtro). Não é tarefa do decorador decidir isso.
— Kentzo

1
Rodar o filtro e fora gatilho pode bugs.python.org/issue29672
— gerrit

4
não responde à pergunta real.
— Catskul

44

Aqui está outra solução:

Este decorador (uma fábrica de decoradores de fato) permite que você dê uma mensagem de razão . Também é mais útil ajudar o desenvolvedor a diagnosticar o problema, fornecendo o nome do arquivo de origem e o número da linha .

EDIT : este código usa a recomendação de Zero: ele substitui a warnings.warn_explicitlinha por warnings.warn(msg, category=DeprecationWarning, stacklevel=2), que imprime o site de chamada de função em vez do site de definição de função. Isso facilita a depuração.

EDIT2 : Esta versão permite que o desenvolvedor especifique uma mensagem "motivo" opcional.

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

Você pode usar este decorador para funções , métodos e classes .

Aqui está um exemplo simples:

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

Você terá:

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: Este decorador agora faz parte da biblioteca Reprovada:

Nova versão estável v1.2.10 🎉


6
Funciona, bem - eu prefiro substituir a warn_explicitlinha pela warnings.warn(msg, category=DeprecationWarning, stacklevel=2)qual imprime o site de chamada de função, em vez do site de definição de função. Isso facilita a depuração.
— Zero

Olá, gostaria de usar seu snippet de código em uma biblioteca licenciada pela GPLv3 . Você gostaria de relicenciar seu código sob a GPLv3 ou qualquer licença mais permissiva , para que eu possa fazer isso legalmente?
— gerrit


1
@LaurentLAPORTE I know. O CC-BY-SO não permite o uso na GPLv3 (por causa do bit de compartilhamento), e é por isso que estou perguntando se você gostaria de liberar esse código especificamente adicionalmente sob uma licença compatível com a GPL. Caso contrário, tudo bem, e eu não usarei seu código.
— Gerrit 09/07

2
não responde à pergunta real.
— Catskul

15

Como o muon sugeriu , você pode instalar o deprecationpacote para isso.

A deprecationbiblioteca fornece um deprecateddecorador e um fail_if_not_removeddecorador para seus testes.

Instalação

pip install deprecation

Exemplo de uso

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

Consulte http://deprecation.readthedocs.io/ para obter a documentação completa.


4
não responde à pergunta real.
— Catskul

1
Note que o PyCharm não reconhece isso
— cz

11

Eu acho que o motivo é que o código Python não pode ser processado estaticamente (como foi feito para compiladores C ++), você não pode receber um aviso sobre o uso de algumas coisas antes de realmente usá-lo. Não acho que seja uma boa ideia enviar spam ao usuário do seu script com várias mensagens "Aviso: este desenvolvedor deste script está usando a API obsoleta".

Atualização: mas você pode criar um decorador que transformará a função original em outra. A nova função marcará / verificará a chave informando que essa função já foi chamada e mostrará mensagem apenas ao colocar a chave no estado ligado. E / ou na saída, pode imprimir uma lista de todas as funções obsoletas usadas no programa.


3
E você deve poder indicar a descontinuação quando a função é importada do módulo . Decorador seria uma ferramenta certa para isso.
— Janusz Lenar

@JanuszLenar, esse aviso será exibido mesmo se não usarmos essa função obsoleta. Mas acho que posso atualizar minha resposta com alguma dica.
— ony

8

Você pode criar um arquivo 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

E importe o decorador de descontinuação da seguinte maneira:

from .utils import deprecated

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

Obrigado, estou usando isso para enviar o usuário para o lugar certo, em vez de apenas mostrar a mensagem de descontinuação!
— German Attanasio

3
não responde à pergunta real.
— Catskul

2

UPDATE: Eu acho que é melhor quando mostramos o DeprecationWarning apenas pela primeira vez para cada linha de código e quando podemos enviar alguma mensagem:

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

Resultado:

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

Podemos apenas clicar no caminho de aviso e ir para a linha em PyCharm.


2
não responde à pergunta real.
— Catskul

0

Aumentando esta resposta por Steven Vascellaro :

Se você usa o Anaconda, primeiro instale o deprecationpacote:

conda install -c conda-forge deprecation 

Em seguida, cole o seguinte na parte superior do arquivo

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

Consulte http://deprecation.readthedocs.io/ para obter a documentação completa.


4
não responde à pergunta real.
— Catskul
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.