Reputation: 7421
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
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
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