I am attempting to copy the functionality of the built-in property class / decorator; a very basic example of what I want to is this:
# If a condition is met, run the first function, else, the second.
#godspeed()
def test():
print(1, 2, 3, 4)
#test.else_()
def test():
print(5, 6, 7, 8)
Here's what I have so far:
import inspect
class godspeed_class():
def __init__(
self,
func,
args,
kwargs,
value,
):
self.func = func
self.args = args
self.kwargs = kwargs
self.value = value
def __call__(self):
if self.value:
self.func(*self.args, **self.kwargs)
else:
self.else_func(*self.else_args, **self.else_kwargs)
def else_(self, *args, **kwargs):
def wrapper(func):
self.else_func = func
self.else_args = args
self.else_kwargs = kwargs
return wrapper
def godspeed(*args, value = 0, **kwargs):
def wrapper(func):
_ = godspeed_class(func, args, kwargs, value)
inspect.stack(1)[1][0].f_globals[func.__name__] = _
return wrapper
I already know how to implement the condition parsing, but I am having trouble with storing the function under the else_ decorator in the class, so that I can call it if the condition isn't met.
In addition, despite injecting the new class directly into the global namespace, when I run print(test), it tells me it's a NoneType object.
Note: Code has been updated; however, it still gives me the "NoneType object" error.
You need to change both of your wrapper functions to return a callable object, probably the instance of your class. Otherwise you're going to have None as the value for the method, since the decorator syntax will assign the return value to the name of the decorated function, which means that even if your inspect hack works, it will get overwritten.
I'd suggest:
class godspeed_class():
... # __init__ and __call__ can remain the same
def else_(self, *args, **kwargs):
def wrapper(func):
self.else_func = func
self.else_args = args
self.else_kwargs = kwargs
return self # add return here
return wrapper
def godspeed(*args, value = 0, **kwargs):
def wrapper(func):
return godspeed_class(func, args, kwargs, value) # and here (rather than inspect stuff)
return wrapper
This will do the job for your example with a top-level test function. If you want to be able to decorate methods, you'll also need to add a __get__ method to the class to add binding behavior (otherwise you'll not get the self argument passed in to the wrapped method).
It's a bit misleading to use wrapper as the name there, as the inner functions are the actual decorators being used here (the top level godspeed function and the else_ method are decorator factories). Normally you use wrapper as a name of a function returned by a decorator (but you're using your class for that instead).
I'd also note that it's a bit strange that you're passing the arguments for the functions to the decorator factories, rather than having __call__ accept arguments that it passes along to the relevant function. It's a bit unusual for a decorator that leaves behind a callable (rather than something like property that works differently) to dramatically change a function's calling convention, as it may end up hard for a caller to know what arguments they're expected to pass in, if the function signature isn't representative any more.
A decorator is nothing magical. Basically, the #decorator syntax is just syntactic sugar, so this:
#mydecorator
def func():
pass
is just a convenient shortcut for
def func():
pass
func = mydecorator(func)
IOW, a "decorator" is a callable object that takes a callable as input and returns a callable (well, it's supposed to return a callable at least - you can actually return whatever, but then you'll break everyone's expectations).
Most often, the decorator is written as a simple function returning a closure over the decorated function:
def trace(func):
def wrapper(*args, **kw):
result = func(*args, **kw)
print("{}({}, {}) => {}". format(func, args, kw, result))
return result
return wrapper
#trace
def foo(x):
return 42 * x
But (since closures are the poor man's classes and classes the poor man's closures) you can also implement it as a callable class, in which case the initializer will receive the decorated func, which in turn will be replaced by the instance:
class trace(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kw):
result = self.func(*args, **kw)
print("{}({}, {}) => {}". format(self.func, args, kw, result))
return result
#trace
def foo(x):
return 42 * x
Then you have "parameterized" decorators - the one that can take arguments. In this case you need two level of indirection, the top-level one (the one used as decorator) returning the actual decorator (the one that receives the function), ie:
def trace(out):
def really_trace(func):
def wrapper(*args, **kw):
result = func(*args, **kw)
out.write("{}({}, {}) => {}\n". format(func, args, kw, result))
return result
return wrapper
return really_trace
#trace(sys.stderr)
def foo(x):
return 42 * x
I leave the class-based implementation as an exercise to the reader ;-)
Now in your case, the fact that test ends up being None is quite simply due to the fact that your wrapper func forgets to return the godspeed_class instance as it should (instead messing with the function's f_globals, which, as you noticed, doesn't work as expected).
Since you didn't clearly explained what you're trying to achieve here ("something similar to property" isn't a proper spec), it's hard to provide a working solution, but as a starting point you may want to fix your godspeed func to behave as expected:
def godspeed(*args, value = 0, **kwargs):
def wrapper(func):
return godspeed_class(func, args, kwargs, value)
return wrapper
Related
I'm currently learning Python and i started digging decorators !
So basically i wanted to create a decorator (especially a callable class !) that could be used in both scenarios:
Case 1 :
#Decorator
def foo():
return True
Case 2 :
#Decorator(bar=2)
def foo():
return True
I'd like to use it with optional arguments and avoiding trailing parenthesis like this :
#Decorator()
def foo():
return True
So here's what i got about it (i gave it a shot with only my knowledge of decorators as functions):
class Decorator:
func: Callable # The decorated function
bar: int
def __init__(self, func: Callable = None, bar: int = 0):
self.func = func
# Decorator called without argument : func is not None (case 1)
# Decorator called with arguments : func is None (case 2)
self.bar = bar
def __call__(self, *args, **kwargs):
if self.func is None:
# Decorator called with arguments (case 2)
# The first arg must be the decorated function
if not args or not (callable(args[0])):
raise TypeError('First argument must be a callable')
self.func = args[0]
return self.wrapped
else:
# Decorator called without argument (case 1)
return self.wrapped(*args, **kwargs)
def wrapped(self, *args, **kwargs) -> Any:
# Encapsulating the decorated function's call
# Do some stuff
return self.func(*args, **kwargs)
This works as intended (definitely a win as far as my learning is concerned), but I'm still looking for improvement/advice (maybe there's a much better way to achieve this, or this specific case isn't used at all, is it ?).
Bonus question : how to add functools.wraps in this decorator, i read the documentation about functools.update_wrapper(self, func) but this won't work with my wrapped function, right ?
Thanks for your help.
This question already has answers here:
How can I decorate an instance method with a decorator class?
(2 answers)
Closed 4 years ago.
While there are plenty of resources about using classes as decorators, I haven't been able to find any that deal with the problem of decorating methods. The goal of this question is to fix that. I will post my own solution, but of course everyone else is invited to post theirs as well.
Why the "standard" implementation doesn't work
The problem with the standard decorator class implementation is that python will not create a bound method of the decorated function:
class Deco:
def __init__(self, func):
self.func= func
def __call__(self, *args):
self.func(*args)
class Class:
#Deco
def hello(self):
print('hello world')
Class().hello() # throws TypeError: hello() missing 1 required positional argument: 'self'
A method decorator needs to overcome this hurdle.
Requirements
Taking the classes from the previous example, the following things are expected to work:
>>> i= Class()
>>> i.hello()
hello world
>>> i.hello
<__main__.Deco object at 0x7f4ae8b518d0>
>>> Class.hello is Class().hello
False
>>> Class().hello is Class().hello
False
>>> i.hello is i.hello
True
Ideally, the function's __doc__ and signature and similar attributes are preserved as well.
Usually when a method is accessed as some_instance.some_method(), python's descriptor protocol kicks in and calls some_method.__get__(), which returns a bound method. However, because the method has been replaced with an instance of the Deco class, that does not happen - because Deco is not a descriptor. In order to make Deco work as expected, it must implement a __get__ method that returns a bound copy of itself.
Implementation
Here's basic "do nothing" decorator class:
import inspect
import functools
from copy import copy
class Deco(object):
def __init__(self, func):
self.__self__ = None # "__self__" is also used by bound methods
self.__wrapped__ = func
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
# if bound to an object, pass it as the first argument
if self.__self__ is not None:
args = (self.__self__,) + args
#== change the following line to make the decorator do something ==
return self.__wrapped__(*args, **kwargs)
def __get__(self, instance, owner):
if instance is None:
return self
# create a bound copy
bound = copy(self)
bound.__self__ = instance
# update __doc__ and similar attributes
functools.update_wrapper(bound, self.__wrapped__)
# add the bound instance to the object's dict so that
# __get__ won't be called a 2nd time
setattr(instance, self.__wrapped__.__name__, bound)
return bound
To make the decorator do something, add your code in the __call__ method.
Here's one that takes parameters:
class DecoWithArgs(object):
#== change the constructor's parameters to fit your needs ==
def __init__(self, *args):
self.args = args
self.__wrapped__ = None
self.__self__ = None
def __call__(self, *args, **kwargs):
if self.__wrapped__ is None:
return self.__wrap(*args, **kwargs)
else:
return self.__call_wrapped_function(*args, **kwargs)
def __wrap(self, func):
# update __doc__ and similar attributes
functools.update_wrapper(self, func)
return self
def __call_wrapped_function(self, *args, **kwargs):
# if bound to an object, pass it as the first argument
if self.__self__ is not None:
args = (self.__self__,) + args
#== change the following line to make the decorator do something ==
return self.__wrapped__(*args, **kwargs)
def __get__(self, instance, owner):
if instance is None:
return self
# create a bound copy of this object
bound = copy(self)
bound.__self__ = instance
bound.__wrap(self.__wrapped__)
# add the bound decorator to the object's dict so that
# __get__ won't be called a 2nd time
setattr(instance, self.__wrapped__.__name__, bound)
return bound
An implementation like this lets us use the decorator on methods as well as functions, so I think it should be considered good practice.
I have a decorator class validatekeys() and a Node3D() class.
The intention is for Node3D to hold coordinate values of x, y, and z which are retrieved using a #property decorator, and can be set using either a #coords.setter decorator (which calls set_coords()) or directly using set_coords() which is itself decorated with validatekeys(). I am using decorators to accomplish this so that I can add other classes later, like Node2D(), for example.
Code:
class validatekeys(object):
def __init__(self,*keysIterable):
self.validkeys = []
for k in keysIterable:
self.validkeys.append(k)
def __call__(self,f):
def wrapped_f(*args,**kwargs):
for a in kwargs:
if not a in self.validkeys:
raise Exception()
self.__dict__.update(kwargs)
return f(self,**kwargs)
return wrapped_f
class Node3D(object):
#property
def coords(self):
return self.__dict__
#coords.setter
def coords(self,Coords):
self.set_coords(**Coords)
#validatekeys('x','y','z')
def set_coords(self,**Coords):
pass
However, part of the output is not as expected:
n = Node2D()
n.coords #{} <--expected
n.set_coords(x=1,y=2)
n.coords #{} <--not expected
n.set_coords(a=1,b=2) #Exception <--expected
It looks like the self.__dict__ is not being updated correctly. However, I've been unable to figure out how to fix this. Any suggestions?
Note that although I am certainly interested in alternative formulations/approaches on solving this problem (validating keys input to a setter), this is mostly a learning exercise to understand how decorators, classes, etc etc work.
Your decorator is updating the wrong __dict__; self in your decorator __call__ is the decorator object itself.
You need to extract the bound self argument from the called wrapper:
def wrapped_f(*args, **kwargs):
for a in kwargs:
if not a in self.validkeys:
raise Exception()
instance = args[0]
instance.__dict__.update(kwargs)
return f(*args, **kwargs)
You can give your wrapped_f() an explicit first argument too:
def wrapped_f(instance, *args, **kwargs):
for a in kwargs:
if not a in self.validkeys:
raise Exception()
instance.__dict__.update(kwargs)
return f(instance, *args, **kwargs)
Here instance is bound to the Node3D instance. Note that there is no hard requirement to name this variable self; that is just a convention.
The self in your __call__ refers to the validator, not the Node3D object, so the validator is updating its own __dict__. Try this instead:
class validatekeys(object):
def __init__(self,*keysIterable):
self.validkeys = []
for k in keysIterable:
self.validkeys.append(k)
def __call__(validator_self,f):
def wrapped_f(self, *args,**kwargs):
for a in kwargs:
if not a in validator_self.validkeys:
raise Exception()
self.__dict__.update(kwargs)
return f(self, *args, **kwargs)
return wrapped_f
Here I've renamed the self in the __call__ to validator_self to make it clear that that self refers to the validator. I added a self to the wrapper function; this self will refer to the "real" self of the Node3D object where the validated method is.
Assuming the following structure:
class SetupTestParam(object):
def setup_method(self, method):
self.foo = bar()
#pytest.fixture
def some_fixture():
self.baz = 'foobar'
I use SetupTestParam as a parent class for test classes.
class TestSomething(SetupTestParam):
def test_a_lot(self, some_fixture):
with self.baz as magic:
with magic.fooz as more_magic:
blah = more_magic.much_more_magic() # repetative bleh
... # not repetative code here
assert spam == 'something cool'
Now, writing tests gets repetitive (with statement usage) and I would like to write a decorator to reduce the number of code lines. But there is a problem with pytest and the function signature.
I found out library which should be helpful but I can't manage to get it to work.
I made a classmethod in my SetupTestParam class.
#classmethod
#decorator.decorator
def this_is_decorator(cls, f):
def wrapper(self, *args, **kw):
with self.baz as magic:
with magic.fooz as more_magic:
blah = more_magic.much_more_magic() # repetative bleh
return f(self, *args)
return wrapper
After I decorate the test_a_lot method, I receive the error TypeError: transaction_decorator() takes exactly 1 argument (2 given)
Can someone explain me please what am I doing wrong? (I assume there is a problem with self from the test method?)
Chaining decorators is not the simplest thing to do. One solution might be to separate the two decorators. Keep the classmethod but move decorator.decorator to the end:
#classmethod
def this_is_decorator(cls, f):
def wrapper(self, *args, **kw):
with self.baz as magic:
with magic.fooz as more_magic:
blah = more_magic.much_more_magic() # repetative bleh
return f(self, *args)
return decorator.decorator(wrapper, f)
Maybe this works for you.
After some tweaking and realizing that I need to pass a parameter to decorator I choosed to write it as a class:
class ThisIsDecorator(object):
def __init__(self, param):
self.param = param # Parameter may vary with the function being decorated
def __call__(self, fn):
wraps(fn) # [1]
def wrapper(fn, fn_self, *args): # [2] fn_self refers to original self param from function fn (test_a_lot) [2]
with fn_self.baz as fn_self.magic: # I pass magic to fn_self to make magic accesible in function fn (test_a_lot)
with fn_self.magic.fooz as more_magic:
blah = self.param.much_more_magic() # repetative bleh
return fn(fn_self, *args)
return decorator.decorator(wrapper, fn)
[1] I use wraps to have original fn __name__, __module__ and __doc__.
[2] Params passed to wrapper were self = <function test_a_lot at 0x24820c8> args = (<TestSomething object at 0x29c77d0>, None, None, None, None), kw = {} so I took out the args[0] as a fn_self.
Original version (without passing a parameter):
#classmethod
def this_is_decorator(cls, fn):
#wraps(fn)
def wrapper(fn, fn_self, *args):
with fn_self.baz as fn_self.magic:
with fn_self.magic.fooz as more_magic:
blah = more_magic.much_more_magic() # repetative bleh
return fn(fn_self, *args)
return decorator.decorator(wrapper,fn)
Thanks go to Mike Muller for pointing out the right direction.
Here's what happens in time order as this method is defined.
this_is_decorator is created (not called).
decorator.decorator(this_is_decorator) is called. This returns a new function which becomes this_is_decorator and has the same usage.
classmethod(this_is_decorator) is called, and the result of that is a classmethod that accepts (cls, f) and returns wrapper.
Later at runtime, a call to this_is_decorator will return wrapper.
But considering that this_is_decorator is a class method, it's not clear to me that this is what you want. I'm guessing that you may want something more like this:
from decorator import decorator
#decorator
def mydecorator(f):
def wrapper(cls, *args, **kw):
# ... logging, reporting, caching, whatever
return f(*args, **kw)
return wrapper
class MyClass(object):
#classmethod
#mydecorator
def myclsmethod(a, b, c):
# no cls or self value accepted here; this is a function not a method
# ...
Here your decorator is defined outside your class, because it's changing an ordinary function into a classmethod (and because you may want to use it in other places). The order of execution here is:
mydecorator is defined, not called.
decorator(mydecorator) is called, and the result becomes mydecorator.
Creation of MyClass starts.
myclsmethod is created. It is an ordinary function, not a method. There is a difference within the VM, so that you do not have to explicitly supply cls or self arguments to methods.
myclsmethod is passed to mydecorator (which has itself been decorated before) and the result (wrapper) is still a function not a method.
The result of mydecorator is passed to classmethod which returns an actual class method that is bound to MyClass.myclsmethod.
Definition of MyClass finishes.
Later when MyClass.myclsmethod(a, b, c) is called, wrapper executes, which then calls the original myclsmethod(a, b, c) function (which it knows as f) without supplying the cls argument.
Since you have an additional need to preserve the argument list exactly, so that even the names of the arguments are preserved in the decorated function, except with an extra initial argument cls, then you could implement mydecorator this way:
from decorator import decorator
from inspect import getargspec
#decorator
def mydecorator(func):
result = [None] # necessary so exec can "return" objects
namespace = {'f': func, 'result': result}
source = []
add = lambda indent, line: source.append(' ' * indent + line) # shorthand
arglist = ', '.join(getargspec(func).args) # this does not cover keyword or default args
add(0, 'def wrapper(cls, %s):' % (arglist,))
add(2, 'return f(%s)' % (arglist,))
add(0, 'result[0] = wrapper') # this is how to "return" something from exec
exec '\n'.join(source) in namespace
return result[0] # this is wrapper
It's kinda ugly, but this is the only way I know to dynamically set the argument list of a function based on data. If returning a lambda is OK, you could use eval instead of exec, which eliminates the need for an array that gets written into but is otherwise about the same.
I'm looking to build a caching decorator that given a function caches the result of the function to a location specified in the decoration. Something like this:
#cacheable('/path/to/cache/file')
def my_function(a, b, c):
return 'something'
The argument to the decorator is completely separate from the argument to the function it's wrapping. I've looked at quite a few examples but I'm not quite getting how to do this - is it possible to have an argument for the decorator that's unrelated to and not passed to the wrapped function?
The idea is that your decorator is a function returning a decorator.
FIRST Write your decorator as if you knew your argument was a global variable. Let's say something like:
-
def decorator(f):
def decorated(*args,**kwargs):
cache = Cache(cachepath)
if cache.iscached(*args,**kwargs):
...
else:
res = f(*args,**kwargs)
cache.store((*args,**kwargs), res)
return res
return decorated
THEN Write a function that takes cachepath as an arg and return your decorator.
-
def cache(filepath)
def decorator(f):
def decorated(*args,**kwargs):
cache = Cache(cachepath)
if cache.iscached(*args,**kwargs):
...
else:
res = f(*args,**kwargs)
cache.store((*args,**kwargs), res)
return res
return decorated
return decorator
Yes it is. As you know, a decorator is a function. When written in the form:
def mydecorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
#mydecorator
def foo(a, b, c):
pass
the argument passed to mydecorator is the function foo itself.
When the decorator accepts an argument, the call #mydecorator('/path/to') is actually going to call the mydecorator function with '/path/to' first. Then the result of the call to mydecorator(path) will be called to receive the function foo. You're effectively defining a dynamic wrapper function.
In a nutshell, you need another layer of decorator functions.
Here is this slightly silly example:
def addint(val):
def decorator(func):
def wrapped(*args, **kwargs):
result = func(*args, **kwargs)
return result + val
return wrapped # returns the decorated function "add_together"
return decorator # returns the definition of the decorator "addint"
# specifically built to return an extra 5 to the sum
#addint(5)
def add_together(a, b):
return a + b
print add_together(1, 2)
# prints 8, not 3
Paul's answer is good, I would move the cache object so it doesn't need to be built every time, and design your cache so that it raises KeyError when there is a cache miss:
def cache(filepath):
def decorator(f):
f._cache = Cache(cachepath)
def decorated(*args,**kwargs):
try:
key = (args, kwargs)
res = f._cache.get(key)
except KeyError:
res = f(*args, **kwargs)
f._cache.put(key, res)
return res
return decorated
return decorator