Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mark a global as deprecated in Python?

I've seen decorators that let you mark a function a deprecated so that a warning is given whenever that function is used. I'd like to do the same thing but for a global variable, but I can't think of a way to detect global variable accesses. I know about the globals() function, and I could check its contents, but that would just tell me if the global is defined (which it still will be if the function is deprecated and not all out removed) not if it's actually being used. The best alternative I can think of is something like this:

# myglobal = 3
myglobal = DEPRECATED(3)

But besides the problem of how to get DEPRECATED to act exactly like a '3', I'm not sure what DEPRECATED could do that would let you detect every time it's accessed. I think the best it could do is iterate through all of the global's methods (since everything in Python is an object, so even '3' has methods, for converting to string and the like) and 'decorate' them to all be deprecated. But that's not ideal.

Any ideas? Has anyone else tackled this problem?

like image 662
Joseph Garvin Avatar asked May 28 '09 18:05

Joseph Garvin


People also ask

How do you mark a python file as deprecated?

To mark a function or method as deprecated, wrap it in the deprecated() decorator. This does several things for you: The docstring of the wrapped function will have details appended to it from the arguments you set on deprecated() .

What is deprecated function in Python?

Usage of a module may be 'deprecated', which means that it may be removed from a future Python release. The rationale for deprecating a module is also collected in this PEP. If the rationale turns out faulty, the module may become 'undeprecated'.

What is a deprecated module?

Deprecated Modules is a module that will warn game masters whenever they use a module that they shouldn't be using anymore. This includes modules that have been integrated into core Foundry, modules that have been entirely replaced by other modules, and modules that have broken and are no longer being maintained.


2 Answers

This is one of the main rationale for PEP 562 (implemented in Python 3.7):

Typical workarounds are assigning __class__ of a module object to a custom subclass of types.ModuleType or replacing the sys.modules item with a custom wrapper instance. It would be convenient to simplify this procedure by recognizing __getattr__ defined directly in a module that would act like a normal __getattr__ method, except that it will be defined on module instances. For example:

# 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
like image 98
AXO Avatar answered Oct 18 '22 23:10

AXO


You can't do this directly, since theres no way of intercepting the module access. However, you can replace that module with an object of your choosing that acts as a proxy, looking for accesses to certain properties:

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

Now, you can use it as:

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

The downside is that you are now performing two lookups when you access the module, so there is a slight performance hit.

like image 27
Brian Avatar answered Oct 18 '22 23:10

Brian