cardamom
cardamom

Reputation: 7421

what happens if you import a decorated python function from another script?

I am looking at a function in a flask backend with a decorator on it and thinking of importing it into another script and decorating it in a different way. Does anyone know what happens when you import it, whether the decorator goes with it or not?

I had a look at this but it's discussing more what happens in the same script.

Upvotes: 4

Views: 3809

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1124170

No, importing a decorated function will not remove the decorator.

Importing retrieves the current object from the global namespace of the source module, and decorating a function causes the decorator return value to be stored in the global namespace.

Importing a module is mostly syntactic sugar for modulename = sys.modules['modulename'] (for import modulename) and objectname = sys.modules['modulename'].objectname assignments (for from modulename import objectname, in either case after first ensuring that sys.modules has the desired module loaded), and globals in a module are the same thing as attributes on a module object. Decorating is just syntactic sugar for functionname = decorator(functionobject).

If you need to add a new decorator to the imported function, just call the decorator:

from somemodule import somedecoratedfunction

newname_or_originalname = decorator(somedecoratedfunction)

If the imported decorated function doesn't lend itself to being decorated again in a new layer, or you want access to the original undecorated function, see if the object has a __wrapped__ attribute:

from somemodule import somedecoratedfunction

unwrapped_function = somedecoratedfunction.__wrapped__

A well-written decorators uses the @functools.wraps() decorator, which sets that attribute to point to the original:

>>> from functools import wraps
>>> def demodecorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwargs):
...         print("Decorated!")
...         return f(*args, **kwargs)
...     return wrapper
...
>>> @demodecorator
... def foo(name):
...     print(f"Hello, {name or 'World'}!")
...
>>> foo('cardamom')
Decorated!
Hello, cardamom!
>>> foo.__wrapped__('cardamom')
Hello, cardamom!

Upvotes: 6

chepner
chepner

Reputation: 532093

Decorating a function

@some_decorator
def some_func(...):
    ...

is equivalent to applying a function to another object:

def some_func(...):
    ...

some_func = some_decorator(some_func)

When you import the module, all you have access to is the object currently bound to some_func, which is the return value of some_decorator applied to the original function. Unless the thing returned some_decorator includes a reference to the original, undecorated function, you have no access to it from the imported module.

An example of exposing the original:

def some_decorator(f):
    def _(*args, *kwargs):
        # Do some extra stuff, then call the original function
        # ...
        return f(*args, **kwargs)
    _.original = f
    return _

@some_decorator
def some_func(...):
    ...

When you import the module, some_module.some_func refers to the decorated function, but the original undecorated function is available via some_module.some_func.original, but only because the decorator was written to make it available. (As Martijn Peters points out, the wraps decorator does this--and some other nice things--for you, but the decorator still needs to use wraps.)

Upvotes: 5

Related Questions