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?

Stefano Borini
fonte
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.

Patrizio Bertoni
fonte
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 🎉

Laurent LAPORTE
fonte
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.

Stevoisiak
fonte
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.

ony
fonte
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
Erika Dsouza
fonte
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.

ADR
fonte
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.

omerbp
fonte
4
não responde à pergunta real.
Catskul