Decorators in class methods: compatibility with 'getattr' - python

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.

Related

Python decorator within function, needs class within method?

Going off of How can I define decorator method inside class? and Accessing self within decorator function within a Python class, I have the following code:
class Custom():
def __init__(self, var):
self.var = var
def renew(func):
#wraps(func)
def wrapper(self, *args, **kwargs):
try:
return func(*args, **kwargs)
except:
print('refreshing')
self.refresh()
return func(*args, **kwargs)
return wrapper
def refresh(self):
self.var = 'refreshed'
#renew
def get_something(self):
print(self.var)
raise Exception
test = Custom('a')
test.get_something(test)
Which returns exactly what I want (pretend that the raise Exception is a expiration timeout, where var expires after some time, am just forcing an Exception here):
a
refreshing
refreshed
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
...
However, is there any way to not require the class instance test to be in the class method, namely test.get_something(test) -> test.get_something()? Or is this best practice? What is the best way to handle something like this?
In your renew decorator you are not passing instance parameter when calling the decorated function from wrapper. Wherever you want to call your func from wrapper, call it like this:
func(self, *args, **kwargs)
Once you correct the invocation call inside wrapper, you no longer need to pass class instance object when invoking your decorated function. So test.get_something() would work now.

Decorator on class makes class NoneType when importing

I'm trying to understand why a decorator on my class modifies the class in such way that it appears to be 'NoneType' when trying to import the class from another script.
In my_class.py I have:
my_dict = dict()
def register(cls):
name = cls.__name__
my_dict[name] = cls
#register # will be commented
class MyClass:
def my_method(self):
print("running class method")
print("my_dict: ", my_dict)
In another module my_main.py I import the class like
from my_class import MyClass
print(type(MyClass))
print(MyClass.my_method)
If I run it with $ python3 my_main.py I get the following output:
my_dict: {'MyClass': <class 'my_class.MyClass'>}
<class 'NoneType'>
Traceback (most recent call last):
File "my_main.py", line 4, in <module>
print(MyClass.my_method)
AttributeError: 'NoneType' object has no attribute 'my_method'
By commenting the #register line in my_class.py the my_main.py runs without error and outputs:
my_dict: {}
<class 'type'>
<function MyClass.my_method at 0x7ff06f254f28>
..but obviously my_dict is no longer filled. Is there a way of registering my_class with the given decorator AND accessing the attributes of the class after importing it in another script?
A decorator is no more than just a normal function.
I don't want to describe a lot about how to write a decorator rightly. But at least, you should let your decorator return a class.
That is similar when you write a decorator for a function. Decorator should at least return a function.
For example:
def decorator(method):
#functools.wraps(method)
def wrapper(*args, **kwargs):
print("Hi")
return method(*args, **kwargs)
return wrapper
When you use this decorator to decorate a function, you are actually pass that function to decorator: new_function = decorator(original_function).
Which means new_function is wrapped by wrapper. That is how decorator works. When you execute decorated function, it actually execute the wrapper:
print("Hi")
return method(*args, **kwargs) # pass all args to original function and return its return value.
In your code, your decorator just returns None.

Python decorator to keep signature and user defined attribute

I have my simple decorator my_decorator which decorates the my_func.
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._decorator_name_ = 'my_decorator'
return wrapper
#my_decorator
def my_func(x):
print('hello %s'%x)
my_func._decorator_name_
'my_decorator'
Till here things work, but I can't see the actual signature of the function.
my_func?
Signature: my_func(*args, **kwargs)
Docstring: <no docstring>
File: ~/<ipython-input-2-e4c91999ef66>
Type: function
If I decorate my decorator with python's decorator.decorator, I can see the signature of my function but I can't have the new property which I have defined.
import decorator
#decorator.decorator
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._decorator_name_ = 'my_decorator'
return wrapper
#my_decorator
def my_func(x):
print('hello %s'%x)
my_func?
Signature: my_func(x)
Docstring: <no docstring>
File: ~/<ipython-input-8-934f46134434>
Type: function
my_func._decorator_name_
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-10-7e3ef4ebfc8b> in <module>()
----> 1 my_func._decorator_name_
AttributeError: 'function' object has no attribute '_decorator_name_'
How can I have both in python2.7?
For Python 3, using functools.wraps in standard library:
from functools import wraps
def my_decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._decorator_name_ = 'my_decorator'
return wrapper
#my_decorator
def my_func(x):
print('hello %s'%x)
print(my_func._decorator_name_)
#decorator.decorator returns a function which takes another function as input. In your case you want the attribute on the returned function.
For it to work on Python 2.7, you just need some tweak
import decorator
def my_dec2(func):
#decorator.decorator
def my_decorator(func, *args, **kwargs):
print("this was called")
return func(*args, **kwargs)
test = my_decorator(func)
test._decorator_name_ = "my_decorator"
return test
#my_dec2
def my_func(x):
print('hello %s'%x)
my_func(2)
print(my_func._decorator_name_)
And then when you test it works
In [1]: my_func?
Signature: my_func(x)
Docstring: <no docstring>
File: ~/Desktop/payu/projects/decotest/decos.py
Type: function
In [2]: my_func._decorator_name_
Out[2]: 'my_decorator'
You only need to define a wrapper function if you somehow want to alterate the behaviour of your function. So in the case you really just want to add some attribute to your function without changing its behaviour, you can simply do the following.
def my_decorator(func):
func._decorator_name_ = 'my_decorator'
return func
In the more complex case where you need to have a wrapper, what I suggest is following the accepted answer for this question which explains how to create a function that behaves the same as another, but has a customised signature. Using inspect.getargspec you can recover the signature from my_func and transpose it to your wrapper.
As others have pointed out, you do not seem to use decorator correctly.
Alternately you can use my library makefun to create your signature-preserving wrapper, it relies on the same trick than decorator to preserve signature, but is more focused on dynamic function creation and is more generic (you can change the signatures):
from makefun import wraps
def my_decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._decorator_name_ = 'my_decorator'
return wrapper
You can check that it works as expected:
#my_decorator
def my_func(x):
"""my function"""
print('hello %s' % x)
assert my_func._decorator_name_ == 'my_decorator'
help(my_func)
For what it's worth, if you wish to later add optional arguments to your decorator without making the code look more complex, have a look at decopatch. For example if you want _decorator_name_ to be an optional argument of the decorator:
from decopatch import function_decorator, DECORATED
from makefun import wraps
#function_decorator
def my_decorator(name='my_decorator', func=DECORATED):
#wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._decorator_name_ = name
return wrapper

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

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

Categories