getting arguments of the decorated function in python - python

How does the wrapper in a Python decorator reach the decorated function argument's?
For example:
def decorate(f):
def wrapped_function(*args):
print args
f()
return wrapped_function
#decorate
def my_function(*args):
print("Hello world")
my_function("a")
# output
('a',)
Hello world
So, I know that wrapped_function is a closure,
because it retains access to my_function as variables from the upper scope.
But how could it bring it's arguments in theory?

This:
#decorate
def my_function(*args):
pass
is essentially the same as this:
my_function = decorate(my_function)
Therefore, wrapped_function replaces my_function.
That means:
my_function("a")
actually does this:
wrapped_function("a")
So you hand in the argument to wrapped_function already.
You can see this looking at the attribute __name__
>>> my_function.__name__
'wrapped_function'
Without decoration __name__ would be my_function.

Related

Understanding decorator behaviour

I want to understand the decorator behaviour in this code
abc.py
def my_decorator_module(some_function):
def wrapper():
num = 10
if num == 10:
print('yess')
else:
print('no')
some_function()
print('print after some_function() called')
return wrapper()
and call this function as decorator
x.py
from abc import my_decorator_module
#my_decorator_module
def just_some_function():
print("Wheee!")
output is
yess
Wheee!
print after some_function() called
event I did not call just_some_function() in x.py file when I execute the x.py file return me output, why?
Because you've called wrapper before returning it from your outer decoration function. Don't do that.
return wrapper # not wrapper()
The tutorial you're following had earlier introduced the concept of returning functions rather than calling them and returning their results; that's what you need to be doing here.
You did not explicitely called just_some_function() but your "decorator" does, cf the last line:
def my_decorator_module(some_function):
def wrapper():
num = 10
if num == 10:
print('yess')
else:
print('no')
some_function()
print('print after some_function() called')
# here !!!
return wrapper()
This is actually a faulty implementation - your decorator should not return the result of calling wrapper, but return the wrapper function itself:
return wrapper
If you don't understand why: the #decorator syntax is only syntaxic sugar so this:
#decorator
def somefunc():
print("somefunc")
is actually only a shortcut for this:
def somefunc():
print("somefunc")
somefunc = decorator(somefunc)
so your decorator has to return a function object (or any callable FWIW), usually - but not necessarily - some wrapper that will take care of calling the decorated function (and preferably returning the result).

Python decorator accessing the wrong function

My Understanding:
Based on what I understand about decorator, the following code
def myDecorator(func):
...
#myDecorator
def myFunc()
is equivalent to
myFunc = myDecorator(myFunc)
My Experiment
So I'm playing around with this concept with the following code
def myDecorator(func):
func()
#myDecorator
def myFunc():
print("MyFunc is run")
#myDecorator
def myFunc2():
print("MyFunc2 is run")
myFunc
The output is
MyFunc is run
MyFunc2 is run
My Question
What happen? Why the line MyFunc2 is run is printed? Aren't myFunc is equivalent to myFunc = myDecorator(myFunc)? If this is the case why myFunc2 statement is run?
You are passing a function object to the myDecorator() function. That function receives the function object as the func parameter. You then call that function with func().
You are right that #myDecorator on a function object causes that decorator to be called, with the function object being passed in. But you seem to be confused about when that happens. It happens the moment Python executes the def statement:
>>> def myDecorator(func):
... func()
...
>>> #myDecorator
... def foo():
... print('The foo() function is called')
...
The foo() function is called
Note that because myDecorator() has no return statement, foo is now bound to None:
>>> foo is None
True
Your last line, myFunc, does nothing more than just reference the None object. You didn't call it, so that expression does not cause anything to be printed. You can't call it, because None is not callable.
For the sake of completness - a "generic" correct decorator returns a new function object, taht then replaces the original function in the scope it was declared:
def myDecorator(func):
def wrapper(*args, **kwargs):
"""Calls original function with whatever parameters
and returns its return value.
"""
print("Running decorator code")
return func(*ars, **kwargs)
# Returns the newly created 'wrapper' function
# that will replace the original "func"
return wrapper
#myDecorator
def myFunc():
print("MyFunc is run")
#myDecorator
def myFunc2():
print("MyFunc2 is run")
myFunc()

Python - Multiple decorators to a method malfunctioning

I am trying to learn decorators and have overcome a strange condition while having multiple decorators for a method. I have two decorators #makeupper and #decorator_maker_with_arguments.
#decorator_maker_with_arguments demonstrates how the arguments are accessed inside a decorator. This works perfectly fine by printing the supplied args but I see #makeupper malfunctioning. It prints None. I have put a print statement next to its method definition to see if its called and it printed but never prints the letters in hello() uppercase.
When I comment out #decorator_maker_with_arguments("swadhikar", "c") I see the #makeupper works good. Can someone explain what I am tangling here?
def makeupper(some_fun):
def wrapper(arg1, arg2):
return some_fun(arg1, arg2).upper()
return wrapper
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
"""Decorator make that demonstrates decorators with arguments"""
print "I am a decorator maker and you passed \"{}:{}\" while calling me...".format(decorator_arg1, decorator_arg2)
def my_decorator(fn):
def wrapper(fn_arg1, fn_arg2):
print "I am the wrapper and I can access the method args \"{}:{}\"".format(fn_arg1, fn_arg2)
return wrapper
return my_decorator
#decorator_maker_with_arguments("swadhikar", "c")
#makeupper
def hello(ar1, ar2):
return "Hello User!"
Result:
I am a decorator maker and you passed "swadhikar:c" while calling me...
I am the wrapper and I can access the method args "hello_arg1:another hello arg..."
None
but I see #makeupper malfunctioning. It prints None
makeupper isn't malfunctioning. The outer decorator decorator_maker_with_arguments isn't calling the wrapper of makeupper.
And then you have a None because you're not returning anything from the wrapper of decorator_maker_with_arguments.
The following modifications to decorator_maker reflect the proposed adjustments:
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
"""Decorator make that demonstrates decorators with arguments"""
print "I am a decorator maker and you passed \"{}:{}\" while calling me...".format(decorator_arg1, decorator_arg2)
def my_decorator(fn):
def wrapper(fn_arg1, fn_arg2):
out = fn(fn_arg1, fn_arg2)
print "I am the wrapper and I can access the method args \"{}:{}\"".format(fn_arg1, fn_arg2)
return out
return wrapper
return my_decorator
Output:
I am a decorator maker and you passed "swadhikar:c" while calling me...
I am the wrapper and I can access the method args "hello_arg1:another hello arg..."
HELLO USER!
You could add some syntactic sugar by decorating your wrappers with functool.wraps, but arguably it's necessary, at least if you want to keep things like function names, docstrings, etc.
You can add return statement inside my_decorator wrapper function.
Like following:
def makeupper(some_fun):
def wrapper(arg1, arg2):
return some_fun(arg1, arg2).upper()
return wrapper
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
"""Decorator make that demonstrates decorators with arguments"""
print "I am a decorator maker and you passed \"{}:{}\" while calling me...".format(decorator_arg1, decorator_arg2)
def my_decorator(fn):
def wrapper(fn_arg1, fn_arg2):
print "I am the wrapper and I can access the method args \"{}:{}\"".format(fn_arg1, fn_arg2)
return fn(fn_arg1, fn_arg2)
return wrapper
return my_decorator
#decorator_maker_with_arguments("swadhikar", "c")
#makeupper
def hello(ar1, ar2):
return "Hello User!"
print hello('arg1', 'arg2')
Output:
I am a decorator maker and you passed "swadhikar:c" while calling me...
I am the wrapper and I can access the method args "arg1:arg2"
HELLO USER!

How can I get a Python decorator to run after the decorated function has completed?

I want to use a decorator to handle auditing of various functions (mainly Django view functions, but not exclusively). In order to do this I would like to be able to audit the function post-execution - i.e. the function runs as normal, and if it returns without an exception, then the decorator logs the fact.
Something like:
#audit_action(action='did something')
def do_something(*args, **kwargs):
if args[0] == 'foo':
return 'bar'
else:
return 'baz'
Where audit_action would only run after the function has completed.
Decorators usually return a wrapper function; just put your logic in the wrapper function after invoking the wrapped function.
def audit_action(action):
def decorator_func(func):
def wrapper_func(*args, **kwargs):
# Invoke the wrapped function first
retval = func(*args, **kwargs)
# Now do something here with retval and/or action
print('In wrapper_func, handling action {!r} after wrapped function returned {!r}'.format(action, retval))
return retval
return wrapper_func
return decorator_func
So audit_action(action='did something') is a decorator factory that returns a scoped decorator_func, which is used to decorate your do_something (do_something = decorator_func(do_something)).
After decorating, your do_something reference has been replaced by wrapper_func. Calling wrapper_func() causes the original do_something() to be called, and then your code in the wrapper func can do things.
The above code, combined with your example function, gives the following output:
>>> do_something('foo')
In wrapper_func, handling action 'did something' after wrapped function returned 'bar'
'bar'
Your decorator can handle it here itself, like
def audit_action(function_to_decorate):
def wrapper(*args, **kw):
# Calling your function
output = function_to_decorate(*args, **kw)
# Below this line you can do post processing
print "In Post Processing...."
return output
return wrapper

Is it possible to use reflection to examine a function's decorators in Python 2.5?

This is what i want to do:
#MyDecorator
def f():
pass
for d in f.decorators:
print d
This is not generally possible without the cooperation of the decorators. For example,
def my_decorator(f):
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
wrapper.decorators = [wrapper]
if hasattr(f, 'decorators'):
wrapper.decorators.extend[f.decorators]
return wrapper
Essentially, all the decorator does is wrap the function as usual and then put a decorators attribute on the wrapper function. It then checks the wrapped function for a similar list and propagates it upwards.
This is pretty useless though
What I think you want is
def my_decorator(f):
def wrapper(args):
return f(args)
wrapper.decorated = f
return wrapper
This will allow you to do stuff like
#my_other_decorator # another decorator following the same pattern
#my_decorator
def foo(args):
print args
foo.decorated(args) # calls the function with the inner decorated function (my_decorator)
foo.decorated.decorated(args) # original function
You can actually abstract this pattern into a decorator
def reversable(decorator):
def wrapper(func):
ret = decorator(func) # manually apply the decorator
ret.decorated = func # save the original function
return ret
return wrapper
Now when you are writing your decorator:
#reversable
def my_decorator(f):
def wrapper(x):
return f(x + 1)
return wrapper
The #MyDecorator syntax is just shorthand for writing the following Python code:
def f():
pass
f = MyDecorator(f)
Written in this form, you can see that the decorators applied to the function are not kept track of in any way. You could make your decorators remember when they're applied (Aaron's answer has a couple good ideas on how to do this), but you'd have to wrap all third-party decorators with your own.

Categories