Python decorator accessing the wrong function - python

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()

Related

How can Interceptor in python be applied

I need to know when a function is called and do something after calling the function. It seems Interceptor can do it.
How can I use Interceptor in python ?
This can be done using decorators:
from functools import wraps
def iterceptor(func):
print('this is executed at function definition time (def my_func)')
#wraps(func)
def wrapper(*args, **kwargs):
print('this is executed before function call')
result = func(*args, **kwargs)
print('this is executed after function call')
return result
return wrapper
#iterceptor
def my_func(n):
print('this is my_func')
print('n =', n)
my_func(4)
Output:
this is executed at function definition time (def my_func)
this is executed before function call
this is my_func
n = 4
this is executed after function call
#iterceptor replaces my_func with the result of execution of the iterceptor function, that is with wrapper function. wrapper wraps the given function in some code, usually preserving the arguments and execution result of wrappee, but adds some additional behavior.
#wraps(func) is there to copy the signature/docstring data of the function func onto the newly created wrapper function.
More info:
http://python-3-patterns-idioms-test.readthedocs.io/en/latest/PythonDecorators.html
https://www.python.org/dev/peps/pep-0318/

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).

Decorators in class methods: compatibility with 'getattr'

I need to make wrappers for class methods, to be executed before and/or after the call of a specific method.
Here is a minimal example:
class MyClass:
def call(self, name):
print "Executing function:", name
getattr(self, name)()
def my_decorator(some_function):
def wrapper():
print("Before we call the function.")
some_function()
print("After we call the function.")
return wrapper
#my_decorator
def my_function(self):
print "My function is called here."
engine = MyClass()
engine.call('my_function')
This gives me an error at the line getattr(self, name)():
TypeError: 'NoneType' object is not callable
If I comment out the decorator before the class method, it works perfectly:
class MyClass:
def call(self, name):
print "Executing function:", name
getattr(self, name)()
def my_decorator(some_function):
def wrapper():
print("Before we call the function.")
some_function()
print("After we call the function.")
return wrapper
# #my_decorator
def my_function(self):
print "My function is called here."
engine = MyClass()
engine.call('my_function')
The output is:
Executing function: my_function
My function is called here.
The decorator itself is identical to textbook examples. It looks like something goes wrong at a low level when calling a decorated method in Python with getattr.
Do you have any ideas on how to fix this code?
This has nothing to do with getattr(). You get the exact same error when you try to call my_function() directly:
>>> engine.my_function()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable
You have 2 problems:
Your decorator never returns the wrapper, so None is returned instead. This return value replaces my_function and is the direct cause of your error; MyClass.my_function is set to None:
>>> MyClass.my_function is None
True
Your wrapper takes no arguments, including self. You'll need this for it to work once you do return it properly.
The first problem is fixed by un-indenting the return wrapper line; it is currently part of the wrapper function itself, and should be part of my_decorator instead:
def my_decorator(some_function):
def wrapper(self):
print("Before we call the function.")
# some_function is no longer bound, so pass in `self` explicitly
some_function(self)
print("After we call the function.")
# return the replacement function
return wrapper
Your question was only partially answered. Here's how to to modify the wrapper (as well as call()) methods so they accept additional arguments—that will make it work completely (as well as in both Python 2 and 3):
class MyClass:
def call(self, name, *args, **kwargs):
print("Executing function: {!r}".format(name))
getattr(self, name)(*args, **kwargs)
def my_decorator(some_function):
def wrapper(self, *args, **kwargs):
print("Before we call the function.")
retval = some_function(self, *args, **kwargs)
print("After we call the function.")
return retval
return wrapper
#my_decorator
def my_function(self):
print("My function is called here.")
del my_decorator # Not a permanent part of class.
engine = MyClass()
engine.call('my_function')
Output:
Executing function: 'my_function'
Before we call the function.
My function is called here.
After we call the function.

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!

getting arguments of the decorated function in 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.

Categories