2010-03-29 13 views
57

tengo que marcar rutinas Python como obsoletos, pero no puedo encontrar un decorador de la biblioteca estándar de desaprobación. Soy consciente de recetas para él y el módulo de advertencias, pero mi pregunta es:¿Cómo desapruebo las funciones de Python?

Por qué no hay decorador de la biblioteca estándar de Python para la desaprobación?

+4

ahora hay una [desaprobación] (https://pypi.python.org/ paquete pypi/deprecation) – muon

+0

@muon ¿Puede publicar esto como una respuesta con un uso de ejemplo? –

+0

@StevenVascellaro sin duda! – muon

Respuesta

4

Supongo que la razón es que el código Python no se puede procesar de forma estática (como lo hace para los compiladores de C++), no se puede incorporar advertencias sobre el uso de algunas cosas antes de utilizarlo. No creo que sea una buena idea enviar correo no deseado al usuario de su secuencia de comandos con un montón de mensajes "Advertencia: este desarrollador de este script está usando API obsoleta".

Actualización: pero puede crear un decorador que transformará la función original en otra. La nueva función marcará/verificará el interruptor diciendo que ya se llamó a esta función y mostrará el mensaje solo al encender el interruptor. Y/o al salir puede imprimir la lista de todas las funciones obsoletas utilizadas en el programa.

+1

sí, pero si los usuarios soy yo quien está ejecutando mis testsuites ... –

+2

Y debería poder indicar desaprobación ** cuando la función se importa desde el módulo **. Decorator sería una herramienta adecuada para eso. –

+0

@JanuszLenar, esa advertencia se mostrará incluso si no usamos esa función obsoleta. Pero creo que puedo actualizar mi respuesta con alguna pista. – ony

28

He aquí algunos fragmento, modificado a partir de los 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 

Debido a que en algunos intérpretes la primera solución expuesta (sin manipulación filtro) puede dar lugar a una supresión de advertencia.

+10

¿Por qué no utilizar 'functools.wraps' en lugar de configurar el nombre y el documento de esa manera? – Maximilian

+1

@Maximilian: Editado para agregar que, para evitar que los futuros copiadores de este código lo hagan incorrecto también – Eric

+4

No me gustan los efectos secundarios (activar/desactivar el filtro). No es tarea del decorador decidir esto. – Kentzo

16

Aquí hay otra solución:

Este decorador (un decorator factory de hecho) le permiten dar una razón mensaje . También es más útil para ayudar al desarrollador para diagnosticar el problema dando la fuente nombre y número de línea.

EDITAR: Este código utiliza la recomendación de cero: se sustituya por la línea warnings.warn_explicitwarnings.warn(msg, category=DeprecationWarning, stacklevel=2), que imprime el sitio llamada a la función en lugar del sitio de definición de función. Hace que la depuración sea más fácil.

EDIT2: Esta versión permite al desarrollador especificar un mensaje de "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))) 

Se puede usar esta decorador de funciones, métodos y clases.

Aquí está un ejemplo sencillo:

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

que obtendrá:

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 es ahora parte de la biblioteca en desuso:

+4

Funciona, bien - Prefiero reemplazar la línea 'warn_expllicit' con' warnings.warn (msg, category = DeprecationWarning, stacklevel = 2) ' que imprime el sitio de llamada de función en lugar del sitio de definición de función. Hace que la depuración sea más fácil. – Zero

+0

Hola, me gustaría utilizar su fragmento de código en [una biblioteca con licencia GPLv3] (https://github.com/FIDUCEO/FCDR_HIRS/). ¿Estaría dispuesto a relicenciar su código bajo GPLv3 [o cualquier otra licencia permisiva] (https://opensource.stackexchange.com/a/3/33), para que yo pueda legalmente hacerlo? – gerrit

+0

@gerrit: [Todas las contribuciones de los usuarios están autorizadas bajo Creative Commons Attribution-Share Alike] (https://stackoverflow.com/help/licensing). –

1

UPDATE: Creo que es mejor, cuando mostramos DeprecationWarning sólo la primera vez para cada línea de código y cuando podemos enviar algún mensaje:

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 simplemente hacer clic en la ruta de advertencia y acceder a la línea en PyCharm.

+1

'warnings.warn' ya tiene una configuración de" una vez por línea de código ' – Eric

4

Se puede crear un archivo de utilidades

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 

y luego importar el decorador de desaprobación de la siguiente manera:

from .utils import deprecated 

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

¡Gracias, estoy usando esto para enviar al usuario al lugar correcto en lugar de solo mostrar el mensaje de obsolescencia! –

Cuestiones relacionadas