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!
Related
So I have a decorator that is something like this:
def add_exclamation(method):
def wrapper(self, *method_args, **method_kwargs):
method_output = method(self, *method_args, **method_kwargs)
return method_output + "!"
return wrapper
Then I use it to decorate a method of my class that is only called with keyword arguments:
class API(object):
#add_exclamation
def greet(self, word):
return "Hello {}".format(word)
def hello_world(self):
return self.greet(word="World")
This works fine. so
api = API()
print(api.hello_world())
prints "Hello World!" as expected
Unfortunately my IDE (PyCharm) complains that my call self.greet(word="World") is missing a required positional parameter "self". This is presumably because it doesn't recognise the function wrapper as being bound to an IDE instance, so it think self is just any old parameter. Since it works at runtime, I could just leave it, but that little yellow warning is irritating me
Is there something that I can do to tell PyCharm/linters, that the self parameter is wrapper is not expected to be in the call.
self is just a method_arg ... so just use that ...
def add_exclamation(method):
def wrapper(*method_args, **method_kwargs):
method_output = method(*method_args, **method_kwargs)
if isinstance(method_output,str): # be safe
return method_output + "!"
return method_output
return wrapper
class API(object):
#add_exclamation # by using the * now it only accepts kwargs
def greet(self, *, word):
return "Hello {}".format(word)
a = API()
print(a.greet(word="asd"))
that said i dont get any warnings if i do explicitly call out self in the wrapper either... I just dont understand why you would
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).
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()
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.
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.