Nested decorators in Python defining simulation methods - python

Let's say we have three simulation methods:
def method1(func):
def wrapper(*args, **kwargs):
#Implementation of some simulator backend
#but as a toy model we just pass a string here
return func(*args, simulation_method='method1', **kwargs)
return wrapper
def method2(func):
def wrapper(*args, **kwargs):
#Implementation of some simulator backend
#but as a toy model we just pass a string here
return func(*args, simulation_method='method2', **kwargs)
return wrapper
def method3(func):
def wrapper(*args, **kwargs):
#Implementation of some simulator backend
#but as a toy model we just pass a string here
return func(*args, simulation_method='method3', **kwargs)
return wrapper
such that we can call a simulation function with a specific method via
#method3
def simulation(simulation_method):
#Implementation of some computation that needs to be simulated
#but as a toy model we just print the following statement:
print(f"Running simulation with {simulation_method} method")
which yields the output
"Running simulation with method3 method"
I now want to define a decorator called MultiSimulation that repeatedly calls the simulation function while using the given simulation methods with the following syntax:
#MultiSimulation
#method1
#method2
#method3
def simulation(simulation_method):
print(f"Running simulation with {simulation_method} method")
This should give the output:
"Running simulation with method1 method"
"Running simulation with method2 method"
"Running simulation with method3 method"
I am stuck with the definition of MultiSimulation and would be glad to get some help here. Thanks!
I tried different variations such as
def MultiSimulation(func):
def repeated_simulation(*args, **kwargs):
simulation_methods = []
if hasattr(func, '__wrapped__'):
simulation_methods = func.__wrapped__.simulation_methods
result = None
for simulation_method in simulation_methods:
kwargs['simulation_method'] = simulation_method
result = func(*args, **kwargs)
return result
repeated_simulation.simulation_methods = []
repeated_simulation.__wrapped__ = func
return repeated_simulation
but I don't get any output.

Decorator rework required to keep decorator stacking
With the rework, here what you can get:
#MultiSimulation
#method1
#method2
#method3
def simulation(simulation_method):
print(f"Running simulation with {simulation_method} method")
return simulation_method
print(simulation())
# Running simulation with method1 method
# Running simulation with method2 method
# Running simulation with method3 method
# ['method1', 'method2', 'method3']
You need to update your decorators this way:
def method1(func):
def wrapper1(*args, simulation_method="method1", **kwargs):
return func(*args, simulation_method=simulation_method, **kwargs)
return wrapper1
And you need this decorator:
def MultiSimulation(func):
def repeated_simulation(*args, **kwargs):
tmp_fct = func
results = []
while tmp_fct:
try:
results.append(tmp_fct(*args, **kwargs))
except TypeError:
pass
try:
tmp_fct = tmp_fct.__closure__[0].cell_contents
except TypeError:
break
return results
return repeated_simulation
With this rework of decorators, it's possible to use your original style while getting the return values of the different simulation if necessary.
def method1(func):
def wrapper1(*args, simulation_method="method1", **kwargs):
return func(*args, simulation_method=simulation_method, **kwargs)
return wrapper1
def method2(func):
def wrapper2(*args, simulation_method="method2", **kwargs):
return func(*args, simulation_method=simulation_method, **kwargs)
return wrapper2
def method3(func):
def wrapper3(*args, simulation_method="method3", **kwargs):
return func(*args, simulation_method=simulation_method, **kwargs)
return wrapper3
def MultiSimulation(func):
def repeated_simulation(*args, **kwargs):
tmp_fct = func
results = []
while tmp_fct:
try:
results.append(tmp_fct(*args, **kwargs))
except TypeError:
pass
try:
tmp_fct = tmp_fct.__closure__[0].cell_contents
except TypeError:
break
return results
return repeated_simulation
#MultiSimulation
#method1
#method2
#method3
def simulation(simulation_method):
print(f"Running simulation with {simulation_method} method")
return simulation_method
print(simulation())
# Running simulation with method1 method
# Running simulation with method2 method
# Running simulation with method3 method
# ['method1', 'method2', 'method3']

When you're stacking decorators you should note that they are decorated from bottom to the top. This means method3 decorates the simulation and method2 decorates "this decorated function" not the simulation itself. But as you've shown in your question you need to "repeat" the function with different decorators. Of course there are ways to do so but I would rather not doing it that way.
You can instead pass your simulation methods to MultiSimulation like:
#MultiSimulation(method1, method2, method3)
Here is an implementation:
def method1(func):
def wrapper(*args, **kwargs):
return func(*args, simulation_method="method1", **kwargs)
return wrapper
def method2(func):
def wrapper(*args, **kwargs):
return func(*args, simulation_method="method2", **kwargs)
return wrapper
def method3(func):
def wrapper(*args, **kwargs):
return func(*args, simulation_method="method3", **kwargs)
return wrapper
def MultiSimulation(*simulation_methods):
def decorator(fn):
def inner(*args, **kwargs):
return [m(fn)(*args, **kwargs) for m in simulation_methods]
return inner
return decorator
#MultiSimulation(method1, method2, method3)
def simulation(simulation_method):
print(f"Running simulation with {simulation_method} method")
simulation()
output:
Running simulation with method1 method
Running simulation with method2 method
Running simulation with method3 method

I dislike using an arbitrary number of decorators, since it tends to get messy and order matters when placing them. Here, I would use a class-based decorator that supports multiple decorators in it's constructor:
class MultiSimulation:
def __init__(self, *methods):
self.methods = methods
def __call__(self, func):
def wrapper(*args, **kwargs):
return [method(func)(*args, **kwargs) for method in self.methods]
return wrapper
def method1(func):
def wrapper(*args, **kwargs):
return func(*args, simulation_method='method1', **kwargs)
return wrapper
def method2(func):
def wrapper(*args, **kwargs):
return func(*args, simulation_method='method2', **kwargs)
return wrapper
def method3(func):
def wrapper(*args, **kwargs):
return func(*args, simulation_method='method3', **kwargs)
return wrapper
#MultiSimulation(method1, method2, method3)
def simulation(simulation_method):
print(f"Running simulation with {simulation_method} method")
simulation()
Running this code yields:
"Running simulation with method1 method"
"Running simulation with method2 method"
"Running simulation with method3 method"

Related

Decorators as classes

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

Using a decorator that returns a class on a method?

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

What is the correct way to define a python decorator in a class?

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.

Obtaining *args and **kwargs from passed func

In the below code, how would I obtain *args and **kwargs in function f without the need for the wrapper function?
def f(func):
def wrapper(*args, **kwargs):
print(args)
print(kwargs)
return func(*args,**kwargs)
return wrapper
#f
def write(text):
print(text)
# write = a(write)
write('dog')
Failed attempt 1:
def f(func):
a=func(*args)
k=func(**kwargs)
which causes error:
NameError: global name 'args' is not defined
Failed attempt 2:
def f(func(*args,**kwargs)):
a=func(*args)
k=func(**kwargs)
The wrapper function is necessary, and a standard part of how decorator definitions in Python work.
You can, however, help mask the existence of the wrapper function in tracebacks by using functools.wraps():
import functools
def f(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
This will update the wrapper function to have the name and docstring of the wrapped function.
--
Decorators are nothing more than functions which are passed a function. This code...
def dec(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
#dec
def myfunc(foo, bar):
return foo+bar
is equivalent to this code:
def dec(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def myfunc(foo, bar):
return foo+bar
myfunc = dec(myfunc)
Notice how the thing being passed to dec is a function which hasn't even been called yet - so there aren't any arguments passed at the time when dec is invoked. This is why the wrapper function is involved: it adds a layer which will be called when the original function is invoked which can capture arguments.

How do I make pytest fixtures work with decorated functions?

py.test seems to fail when I decorate test functions which has a fixture as an argument.
def deco(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
#pytest.fixture
def x():
return 0
#deco
def test_something(x):
assert x == 0
In this simple example, I get the following error:
TypeError: test_something() takes exactly 1 argument (0 given).
Is there a way to fix this, preferably without modifying the decorator too much? (Since the decorator is used outside testing code too.)
It looks like functools.wraps does not do the job well enough, so it breaks py.test's introspection.
Creating the decorator using the decorator package seems to do the trick.
import decorator
def deco(func):
def wrapper(func, *args, **kwargs):
return func(*args, **kwargs)
return decorator.decorator(wrapper, func)
Fixture feature depends on test function signature.
If you can change wrapper signature as follow, it will works.
def deco(func):
#functools.wraps(func)
def wrapper(x):
return func(x)
return wrapper
If you can't change it, make another decorator:
def deco(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def deco_x(func):
#functools.wraps(func)
def wrapper(x):
return func(x)
return wrapper
And decorate test_somthing with deco_x:
#deco_x
#deco
def test_something(x):
assert x == 0

Categories