¿Cómo marcar un global como obsoleto en Python?

He visto decoradores que le permiten marcar una función en desuso para que se emita una advertencia cada vez que se use esa función. Me gustaría hacer lo mismo pero para una variable global, pero no puedo pensar en una forma de detectar accesos de variables globales. Sé sobre la función globals (), y podría verificar su contenido, pero eso solo me diría si el global está definido (que seguirá siendo si la función está obsoleta y no se ha eliminado del todo) no si realmente se está utilizando . La mejor alternativa que se me ocurre es algo como esto:

# myglobal = 3 myglobal = DEPRECATED(3) 

Pero además del problema de cómo DEPRECATED actuar exactamente como un ‘3’, no estoy seguro de qué DEPRECATED podría hacer que te permita detectar cada vez que se accede. Creo que lo mejor que podría hacer es recorrer todos los métodos globales (ya que todo en Python es un objeto, por lo que incluso ‘3’ tiene métodos, para convertirlos en cadenas y similares) y ‘decorarlos’ para que queden obsoletos. Pero eso no es lo ideal.

¿Algunas ideas? ¿Alguien más ha abordado este problema?

No puede hacer esto directamente, ya que no hay forma de interceptar el acceso al módulo. Sin embargo, puede reemplazar ese módulo con un objeto de su elección que actúe como un proxy, buscando accesos a ciertas propiedades:

 import sys, warnings def WrapMod(mod, deprecated): """Return a wrapped object that warns about deprecated accesses""" deprecated = set(deprecated) class Wrapper(object): def __getattr__(self, attr): if attr in deprecated: warnings.warn("Property %s is deprecated" % attr) return getattr(mod, attr) def __setattr__(self, attr, value): if attr in deprecated: warnings.warn("Property %s is deprecated" % attr) return setattr(mod, attr, value) return Wrapper() oldVal = 6*9 newVal = 42 sys.modules[__name__] = WrapMod(sys.modules[__name__], deprecated = ['oldVal']) 

Ahora, puedes usarlo como:

 >>> import mod1 >>> mod1.newVal 42 >>> mod1.oldVal mod1.py:11: UserWarning: Property oldVal is deprecated warnings.warn("Property %s is deprecated" % attr) 54 

El inconveniente es que ahora está realizando dos búsquedas cuando accede al módulo, por lo que hay un pequeño impacto en el rendimiento.

Podría convertir su módulo en una clase (consulte, por ejemplo, esta pregunta SO ) y convertir ese desaprobado global en una propiedad, de modo que pueda ejecutar parte de su código cuando se acceda a él y brindarle la advertencia que desee. Sin embargo, esto parece un poco excesivo.

Mirad:

Código

 from types import * def wrapper(f, warning): def new(*args, **kwargs): if not args[0].warned: print "Deprecated Warning: %s" % warning args[0].warned = True return f(*args, **kwargs) return new class Deprecated(object): def __new__(self, o, warning): print "Creating Deprecated Object" class temp(o.__class__): pass temp.__name__ = "Deprecated_%s" % o.__class__.__name__ output = temp.__new__(temp, o) output.warned = True wrappable_types = (type(int.__add__), type(zip), FunctionType) unwrappable_names = ("__str__", "__unicode__", "__repr__", "__getattribute__", "__setattr__") for method_name in dir(temp): if not type(getattr(temp, method_name)) in wrappable_types: continue if method_name in unwrappable_names: continue setattr(temp, method_name, wrapper(getattr(temp, method_name), warning)) output.warned = False return output 

Salida

 >>> a=Deprecated(1, "Don't use 1") Creating Deprecated Object >>> a+9 Deprecated Warning: Don't use 1 10 >>> a*4 4 >>> 2*a 2 

Esto obviamente puede ser refinado, pero la esencia está ahí.

Esta es una de las razones principales de PEP 562 (implementado en Python 3.7):

Las soluciones típicas son asignar __class__ de un objeto de módulo a una subclase de types.ModuleType o reemplazar el elemento sys.modules con una instancia de contenedor personalizado. Sería conveniente simplificar este procedimiento reconociendo __getattr__ definido directamente en un módulo que actuaría como un método normal de __getattr__ , excepto que se definirá en las instancias del módulo. Por ejemplo:

 # lib.py from warnings import warn deprecated_names = ["old_function", ...] def _deprecated_old_function(arg, other): ... def __getattr__(name): if name in deprecated_names: warn(f"{name} is deprecated", DeprecationWarning) return globals()[f"_deprecated_{name}"] raise AttributeError(f"module {__name__} has no attribute {name}") # main.py from lib import old_function # Works, but emits the warning