Decorators are used using @
syntax. For example,
@log
def a_fun(a, b, c):
...
This calls, same as (that is, equivalent to but more handier than):
a_fun = log(a_fun)
From below:
def log(func):
def wrapper(*args, **kwargs):
print(args)
val = func(*args, **kwargs)
return wrapper
@log
def run(a, b, c):
print(a+b+c)
run(1,5,c=9)
To start with :
def log(func):
This is defining a function, log
. It take a single argument, func
. Here it is equal to a callable (a function).
Note from above, we had:
... = log(a_fun)
Hence, it can be clearly understood that the value of func
, in this specific example, is equal to a_fun
exactly (passed in value).
Next, we go to :
def wrapper(*args, **kwargs):
This is declaring a function, of name wrapper
. It takes generic parameters, that is *args, **kwargs
which is familiar ppython syntax, when you don't necessarily know which arguments or keywords that a function is going to accept. Thus, it is defined as such in order to be as generic and inclusive as possible, so as to apply to any kind of function (taking zero to hundred+ arguments, and same with keyword arguments for example).
Note that type of *args
is a tuple[Any...]
meaning it is a tuple of one of more elements (of any type). Type of **kwargs
is a dict[str, Any]
, meaning it is a dict
containing parameter name as key (str
obviously, as we cannot define a func like def my_fun(123: int)
for instance) and value as any type (since it is not known here).
This can be confirm with:
assert type(args) is tuple
assert type(kwargs) is dict
Finally:
val = func(*args, **kwargs)
This is saying, pass or forward the captured input *args
and **kwargs
, and unpack and pass them as-is to the callable or function func
.
Then val
here is the return value of the function func
- if it does not return anything, this will be None
, else it will be the returned value of the function itself.
As mentioned from above, the value of func
in this context is exactly equal to run
. Thusly, the value of *args
is equal to the evaluated value of (a, b, c)
and the value of **kwargs
depends on how the function is invoked - for example, run(..., 0, c=2)
would result in the kwargs
object being populated in this specific use case as dict(c=2)
as that is how the function or callable is being invoked. Conversely, the args
object in this case would then be a tuple (..., 0)
as those are the specific positional arguments (separate from named or keyword arguments, which are captured into kwargs
as mentioned above).
Note that in this example, run
does not return anything:
def run(a, b, c):
print(a+b+c)
More specifically, it returns None
, as print(...)
always returns None
.
Hence,
val = ...
from the above, the value of val
will thus be captured as None
.
assert val is None
This can be used to confirm (or disprove) the above point.
Lastly but not leastly, we cannot forget the return
statement that also comes into play here:
return wrapper
This is saying, from the decorator function log
, I want to return a new function wrapper
. So that, the meaning is, the original (or decorated) function will have its value overwritten.
So to clarify, remember from above the following syntax:
@log
def a_fun(a, b, c):
...
That as mentioned, this is then equivalent to:
a_fun = log(a_fun)
So the value log(a_fun)
, is whatever the return value of the decorator function log
is.
In this specific case, as mentioned the return value of log
is wrapper
, as the return statement does indicate that as well.
Thus, the evaluated value of the original (decorated) function a_fun
would then be:
a_fun = wrapper
Hence, we have prove that the original or un-decorated function a_fun
(the underlying callable that is) now have its value overwritten by the decorator, and that its new assigned value is thus wrapper
. So we have an equivalency here of a_fun == wrapper
after the decorator @log
is applied.
Hope that is helpful, or at least clarifies it rather than obfuscates the problem statement and what is happening exactly in the code. At least, the goal here is to not muddy the waters, but rather to provide clarity through openness and the sharing of information such not necessarily that understanding is achieved, but that at the least we thus end up at a better place and situation than where we initially started or originated from.