Lets say decorator has this definition:
def decorator(func: Callable[[], Any]) -> Callable[[], Any]:
def wrapper():
...
func()
...
return wrapperWhen doing @decorator with, say a function like:
@decorator
def thing():
passThis pretty much gets converted to
def thing():
pass
thing = decorator(thing)When calling decorator, this will define a function that wraps the function we just passed (the decorated one), and then it immediately returns that function definition that was defined. Now thing gets redefined to a completely different function:
def thing():
...
func() # original thing()
...Now a bit of a more complex decorator:
def decorator2(name: str):
def outer_wrapper(func: Callable[[str], Any]) -> Callable[[], None]:
def wrapper() -> None:
func(name)
return wrapper
return outer_wrapperWhich may be used like this:
@decorator2("juan")
def thing(name: str):
print(f"name: {name}")This is what actually happens when doing this:
def thing(name: str):
print(f"name: {name}")
thing = decorator2("juan")(thing)First, the decorator gets called with the parameter that was passed into it (name). On this call, decorator2 returns the outer_wrapper function. Which wraps another function (that's it entire purpose).
As seen in the definition, outer_wrapper takes a function as a parameter, which is the function that is being decorated.
# > decorator2(name)
# > outer_wrapper(func)
thing = decorator2("juan")(thing)At this point, thing becomes:
def thing():
...
func("juan") # original thing(name)
...Important to note that calling thing now will always return None, because the wrapper decorator doesn't return the result of func().