I have a decorator to register some class methods. How can I get both self and run parameters correctly?
class Task(object):
_tasks = []
#staticmethod
def register(name):
def decorator(fn):
#wraps(fn)
def wrapper(self=None, run=True, *args, **kwargs):
if not run:
task = defaultdict()
task['name'] = name
task['fn'] = getattr(self, fn.__name__, None)
task['obj'] = self
task['args'] = deepcopy(args)
task['kwargs'] = deepcopy(kwargs)
Task._tasks.append(task)
else:
return fn(self, *args, **kwargs)
return wrapper
return decorator
class Test(object):
def __init__(self, name):
self.name = name
#Task.register('foo')
def foo(self, v1, v2):
print 'running foo in object {} with arguments {} {}'.format(self.name, v1, v2)
#Task.register('hello')
def hello(self):
print 'running hello in object {} '.format(self.name)
def load(self):
self.foo('1', '2', run=False)
self.hello(run=False)
t1=Test('t1')
t1.load()
Traceback (most recent call last):
TypeError: wrapper() got multiple values for keyword argument 'run'
your problem has nothing to do with the decorator. in a simpler form: what you are doing is this:
def foo(run=False, *args, **kwargs):
print(run, args, kwargs)
foo(1, 2, run=True) # TypeError: foo() got multiple values for argument 'run'
from your function signature, python will try to set run=1, args = (2,) and then run into the TypeError.
a fix - though not a very nice one - could be:
def foo(*args, **kwargs):
run = kwargs.pop('run', False) # run defaults to False; remove from kwargs
print(run, args, kwargs)
The run parameter is from the fun, so try to get it from function's parameter:
from collections import defaultdict
from copy import deepcopy
from functools import wraps
class Task(object):
_tasks = []
#staticmethod
def register(name):
def decorator(fn):
#wraps(fn)
def wrapper(self=None, *args, **kwargs):
run = kwargs.pop('run', True)
if not run:
task = defaultdict()
task['name'] = name
task['fn'] = getattr(self, fn.__name__, None)
task['obj'] = self
task['args'] = deepcopy(args)
task['kwargs'] = deepcopy(kwargs)
Task._tasks.append(task)
else:
return fn(self, *args, **kwargs)
return wrapper
return decorator
Python3 seems has better hanlder for the parameters, but don't know how to do this in python2:
from functools import wraps
def optional_debug(func):
#wraps(func)
def wrapper(*args, debug=False, **kwargs):
if debug:
print('Calling', func.__name__)
return func(*args, **kwargs)
return wrapper
#optional_debug
def spam(a,b,c):
print(a,b,c)
spam(1,2,3) # 1,2,3
spam(1,2,3, debug=True) # Calling spam # 1 2 3
Related
Trying to rewrite a decorator as a Class isn't working as expected. My actual decorator is:
def check(callback):
def decorator(function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
cb_result = callback()
return result
return wrapper
return decorator
My approach to class format is
class Check(object):
def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
def __call__(self, *call_args, **call_kwargs):
function = call_args[0]
return self.__param__call__(function)
def __param__call__(self, function):
def wrapper(*args, **kwargs):
result = function(*args, **kwargs)
cb_result = callback()
return result
return wrapper
I expect to use the decorator as in:
#Check(callback=a_function_callback)
def my_function():
...
What is my mistake while rewriting it as a class, I'm also always trying to keep backwards compatibility (aka Python 2.7 compliance).
You should accept callback as a parameter in your Check class' __init__ method, so that your wrapper function can actually reference it as a callback function:
class Check(object):
def __init__(self, callback):
self.callback = callback
def __call__(self, func):
return self.__param__call__(func)
def __param__call__(self, func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
cb_result = self.callback()
return result
return wrapper
I currently have a decorator that wraps a function into a class.
(We are currently using this weird, custom async framework where each async call is defined as a class with a ton of boilerplate code. My idea was to just decorate functions and then return the appropriate class.)
This decorator works fine on functions outside of classes. However, when using it with methods, the self argument is no longer implicitly passed, and I'm not sure why.
Here is the best example I could put together
from __future__ import print_function
import functools
def test_wrap(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
func(*args, **kwargs)
return wrapper
def test_class_wrap(func):
"""Return a Command object for use with the custom framework we are using."""
#functools.wraps(func, assigned=('__name__', '__module__'), updated=())
class Command(object):
def __init__(self, *args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
func(*args, **kwargs)
return Command
class MyObject(object):
def __init__(self):
self.value = 100
#test_wrap
def foo(self):
print(self.value)
#test_class_wrap
def bar(self):
print(self.value)
if __name__ == '__main__':
obj = MyObject()
obj.foo()
print()
obj.bar(obj) # works
# obj.bar() # TypeError: bar() takes exactly 1 argument (0 given)
# Why is self implicitly passed as an argument like with outher methods?
# Output
# Args: (<__main__.MyObject object at 0x7fe2bf9bb590>,)
# Kwargs: {}
# 100
# Args: (<__main__.MyObject object at 0x7fe2bf9bb590>,)
# Kwargs: {}
# 100
test_class_wrap does nothing, just returning a class so __init__ isn't called. Try to wrap the class with a function passing args and kwargs:
def test_class_wrap(func):
"""Return a Command object for use with the custom framework we are using."""
#functools.wraps(func, assigned=('__name__', '__module__'), updated=())
def wrapper(*args, **kwargs):
class Command(object):
def __init__(self, *args, **kwargs):
print("Args:", args)
print("Kwargs:", kwargs)
func(*args, **kwargs)
return Command(*args, **kwargs)
return wrapper
...
if __name__ == '__main__':
obj = MyObject()
obj.foo()
print()
obj.bar()
I'm trying to decorate all methods in class and i succeded with this code, but i'm also trying to log calls to operators like * + - / , is there any way to decorate them or something like getattr(self,"*") to log the calls ?
class Logger(object):
def __init__(self, bool):
self.bool = bool
def __call__(self, cls):
class DecoratedClass(cls):
def __init__(cls, *args, **kwargs):
super().__init__(*args, **kwargs)
if not(self.bool):
return
methods = [func for func in dir(cls)
if callable(getattr(cls, func))
and not func.startswith("__class")]
for func in methods:
old_func = getattr(cls, func)
def decorated_function(fname, fn):
def loggedFunction(*args, **kwargs):
print("Calling {0} from {3} with params {1} and kwargs {2}".format(fname.upper(), args, kwargs, cls))
return fn(*args, **kwargs)
return loggedFunction
setattr(cls, func, decorated_function(func, old_func))
return DecoratedClass
#Logger(True)
class DummyClass():
def __init__(self,foo):
self.foo = foo
def bar(self):
print(self.foo)
def __mul__(self,other):
print("Hello",other)
if __name__ == '__main__':
a = DummyClass('hola')
a.method()
a.__mul__(a) #this is logged
print(a*a) #this is not logged by decorator
Thanks to Ćukasz, here is a working script.
A difficulty I encountered is to handle multiple instances and avoid to decorate multiple times the same class methods. To handle this problem, I keep track of the decorated class methods (cls.__logged).
Another difficulty is to deal with the magic methods like __setattr__, __getattribute__, __repr__, ... My solution is to ignore them, except for a list that you must define at start (loggable_magic_methods).
from functools import wraps
loggable_magic_methods = ['__mul__',]
def is_magic_method(method):
return method.startswith('__')
class Logger(object):
def __init__(self, bool):
self.bool = bool
def __call__(self, cls):
class LoggedClass(cls):
cls.__logged = []
def __init__(instance, *args, **kwargs):
super().__init__(*args, **kwargs)
if not(self.bool):
return
methods = [funcname for funcname in dir(instance)
if callable(getattr(instance, funcname))
and (funcname in loggable_magic_methods or not is_magic_method(funcname))]
def logged(method):
#wraps(method)
def wrapper(*args, **kwargs):
print (method.__name__, args, kwargs, cls)
return method(*args, **kwargs)
return wrapper
for funcname in methods:
if funcname in cls.__logged:
continue
if is_magic_method(funcname):
setattr(cls, funcname, logged(getattr(cls, funcname)))
cls.__logged.append(funcname)
else:
setattr(instance, funcname, logged(getattr(instance, funcname)))
return LoggedClass
#Logger(True)
class DummyClass():
def __init__(self, foo, coef):
self.foo = foo
self.coef = coef
def bar(self):
print(self.foo)
def __mul__(self, other):
print(self.foo)
print(other.foo)
return self.coef * other.coef
if __name__ == '__main__':
a = DummyClass('hola', 1)
a.bar()
print()
print(a.__mul__(a))
print()
print(a*a)
print()
b = DummyClass('gracias', 2)
b.bar()
print()
print(b.__mul__(a))
print()
print(b*a)
Currently you are patching values on instance. Your usage of cls in __init__ signature is false friend - actually it's old plain self in this case.
If you want to override magic methods, interpreter looks for them on class objects, not on instances.
Minimal example:
class DummyClass:
def __init__(self, foo):
self.foo = foo
def __mul__(self, other):
return self.foo * other.foo
def logged(method):
def wrapper(*args, **kwargs):
print (method.__name__, args, kwargs)
return method(*args, **kwargs)
return wrapper
DummyClass.__mul__ = logged(DummyClass.__mul__)
a = DummyClass(1)
b = DummyClass(2)
assert a * a == 1
assert a * b == 2
assert b * b == 4
Each call is logged.
>>> a = DummyClass(1)
>>> b = DummyClass(2)
>>> assert a * a == 1
__mul__ (<__main__.DummyClass object at 0x00000000011BFEB8>, <__main__.DummyClass object at 0x00000000011BFEB8>) {}
>>> assert a * b == 2
__mul__ (<__main__.DummyClass object at 0x00000000011BFEB8>, <__main__.DummyClass object at 0x00000000011BF080>) {}
>>> assert b * b == 4
__mul__ (<__main__.DummyClass object at 0x00000000011BF080>, <__main__.DummyClass object at 0x00000000011BF080>) {}
I'll leave a task of rewriting monkey-patching approach to you.
How do I pass self.key below into the decorator?
class CacheMix(object):
def __init__(self, *args, **kwargs):
super(CacheMix, self).__init__(*args, **kwargs)
key_func = Constructor(
memoize_for_request=True,
params={'updated_at': self.key}
)
#cache_response(key_func=key_func)
def list(self, *args, **kwargs):
pass
class ListView(CacheMix, generics.ListCreateAPIView):
key = 'test_key'
I get the error:
'self' is not defined
Here's an example of doing it with a class decorator as I tried to describe to you in the comments. I filled-in a few undefined references in your question and used a super-simplified version of your cache_response function decorator, but hopefully this will convey the idea concretely enough for you to be able adapt it to your real code.
import inspect
import types
class Constructor(object):
def __init__(self, memoize_for_request=True, params=None):
self.memoize_for_request = memoize_for_request
self.params = params
def __call__(self):
def key_func():
print('key_func called with params:')
for k, v in self.params.items():
print(' {}: {!r}'.format(k, v))
key_func()
def cache_response(key_func):
def decorator(fn):
def decorated(*args, **kwargs):
key_func()
fn(*args, **kwargs)
return decorated
return decorator
def example_class_decorator(cls):
key_func = Constructor( # define key_func here using cls.key
memoize_for_request=True,
params={'updated_at': cls.key} # use decorated class's attribute
)
# create and apply cache_response decorator to marked methods
# (in Python 3 use types.FunctionType instead of types.UnboundMethodType)
decorator = cache_response(key_func)
for name, fn in inspect.getmembers(cls):
if isinstance(fn, types.UnboundMethodType) and hasattr(fn, 'marked'):
setattr(cls, name, decorator(fn))
return cls
def decorate_me(fn):
setattr(fn, 'marked', 1)
return fn
class CacheMix(object):
def __init__(self, *args, **kwargs):
super(CacheMix, self).__init__(*args, **kwargs)
#decorate_me
def list(self, *args, **kwargs):
classname = self.__class__.__name__
print('list() method of {} object called'.format(classname))
#example_class_decorator
class ListView(CacheMix):
key = 'test_key'
listview = ListView()
listview.list()
Output:
key_func called with params:
updated_at: 'test_key'
list() method of ListView object called
I just found out that if you write the decorator function like so:
def decorator(the_func):
#wraps(the_func)
def wrapper(*args, **kwargs):
the_func(*args, **kwargs)
return wrapper
and decorate any method which takes self as an argument, self will appear in args. Therefore you can do this:
from functools import wraps
class myClass:
def __init__(self):
self.myValue = "Hello"
def decorator(the_func):
#wraps(the_func)
def wrapper(*args, **kwargs):
print(args[0].myValue)
the_func(*args, **kwargs)
return wrapper
#decorator
def myFunction(self):
print("World")
Call it like you normally would
foo = myClass()
foo.myFunction()
and you should get
Hello
World
What I'd like to achieve is that the following code out puts the following:
Here1
Here2
Here3 argOne argTwo
I'm wondering if my use of __call__ is somehow clobbering functools.wraps; it also appears that the arguments are lost at some point.
Is what I'm trying to achieve possible?
from functools import wraps
class Decorator():
def __init(self, something=None):
self.something = something
def __call__(self, func):
print 'Here1'
#wraps(func)
def _wrapper(*args, **kwargs):
return self.call(func, *args, **kwargs)
return _wrapper
def call(self, func, *args, **kwargs):
print 'Here2'
retsult = func(*args, **kwargs)
return result
if __name__ == '__main__':
decorator = Decorator()
#decorator
def do_the_thing(arg1='argOne', arg2='argTwo'):
print 'Here3 {0} {1}'.format(arg1, arg2)
return
Seems you just had a few typos and weren't actually calling the function do_the_thing.
Changed it to this and worked just fine.
from functools import wraps
class Decorator():
def __init__(self, something=None): # you are missing the __ on the right
self.something = something
def __call__(self, func):
print 'Here1'
#wraps(func)
def _wrapper(*args, **kwargs):
return self.call(func, *args, **kwargs)
return _wrapper
def call(self, func, *args, **kwargs):
print 'Here2'
result = func(*args, **kwargs) # result was misspelled
return result
if __name__ == '__main__':
#Decorator() # Just a bit cleaner
def do_the_thing(arg1='argOne', arg2='argTwo'):
print 'Here3 {0} {1}'.format(arg1, arg2)
do_the_thing() # func was never called.