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.
Related
I have two classes, one of which is a wrapper of the other. A function in the original class uses a method called forward, but I want it to use the forward method of the wrapper class after it has been wrapped, not the original. For example:
class A:
def __init__(self):
self.a = 1
def forward(self, x):
return self.a + x
def main(self, x):
return self.forward(x) + 100
class Wrapper:
def __init__(self, A):
self.module = A
def forward(self, x):
# Long convoluted code.
# ..
# ..
return self.module.forward(x)
classA = A()
wrapperA = Wrapper(classA)
# Goal: Make classA.main(..) use the forward function from Wrapper instead.
Because the wrapper class has the long and convoluted code that needs to be run, I want all calls of forward from main to be such that it calls the forward from the wrapper class, not from the original.
Is there a way to do this in Python?
Reasons why I did not use inheritance:
Instantiating class A is memory intensive. If I receive class A object as input, I want to modify its core behavior. without instantiating another object.
classA can be of different object types in runtime.
--
An alternative way I thought of is to redefine main in Wrapper. However, the problem is doing this automatically for every method defined in A without hard coding.
In Python "everything is an object". Including classes, functions, and methods
on objects.
As such we can take any class, loop over all functions in that class and modify
them as needed.
Depending on the real code, the problem in the question might be better tackled
using decorators or meta-classes, depending on the dependencies of the wrapper
(what values does it need access to). I will not go into meta-classes as most needs for meta-classes can also be implemented using class-decorators, which are less error-prone.
As you mention in one of your comments that you may have several different classes that need to be wrapped the class-decorator solution might be a good candidate. This way you won't lose the inheritance tree of the wrapped class.
Here is an example not using either, but doing exactly as asked ;)
Using __new__
from functools import update_wrapper
class A:
def __init__(self):
self.a = 1
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
def main(self, x):
return self.forward(x) + 100
class Wrapper:
# Using __new__ instead of __init__ gives us complete control *how* the
# "Wrapper" instance is created. We use it to "pull in" methods from *A*
# and dynamically attach them to the `Wrapper` instance using `setattr`.
#
# Using __new__ is error-prone however, and using either meta-classes or
# even easier, decorators would be more maintainable.
def __new__(cls, A):
# instance will be our instance of thie `Wrapper` class. We start off
# with no defined functions, we will add those soon...
instance = super().__new__(cls)
instance.module = A
# We now walk over every "name" in the wrapped class
for funcname in dir(A):
# We skip anything starting with two underscores. They are most
# likely magic methods that we don't want to wrap with the
# additional code. The conditions what exactly we want to wrap, can
# be adapted as needed.
if funcname.startswith("__"):
continue
# We now need to get a reference to that attribute and check if
# it's callable. If not it is a member variable or something else
# and we can/should skip it.
func = getattr(A, funcname)
if not callable(func):
continue
# Now we "wrap" the function with our additional code. This is done
# in a separate function to keep __new__ somewhat clean
wrapped = Wrapper._wrap(func)
# After wrapping the function we can attach that new function ont
# our `Wrapper` instance
setattr(instance, funcname, wrapped)
return instance
#staticmethod
def _wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
class Demo2:
def foo(self):
print("yoinks")
classA = A()
otherInstance = Demo2()
wrapperA = Wrapper(classA)
wrapperB = Wrapper(otherInstance)
print(wrapperA.forward(10))
print(wrapperB.foo())
print("docstring is maintained: %r" % wrapperA.forward.__doc__)
Using a class decorator
With a class decorator, there is no need to override __new__ which can lead to hard to debug issues if not 100% properly implemented.
However, it has a key difference: It modifies the existing class "in-place", so the original class is lost in a way. Although you could keep a reference to it in the unlikely case that you need to.
Modifying this in-place does however also mean that you don't need to replace all your usages in your application with the new "wrapper" class, making it a lot easier to implement in an existing code-base and eliminating the risk that you forget to apply the wrapper on new instances.
from functools import update_wrapper
def _wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
def wrapper(cls):
for funcname in dir(cls):
# We skip anything starting with two underscores. They are most
# likely magic methods that we don't want to wrap with the
# additional code. The conditions what exactly we want to wrap, can
# be adapted as needed.
if funcname.startswith("__"):
continue
# We now need to get a reference to that attribute and check if
# it's callable. If not it is a member variable or something else
# and we can/should skip it.
func = getattr(cls, funcname)
if not callable(func):
continue
# Now we "wrap" the function with our additional code. This is done
# in a separate function to keep __new__ somewhat clean
wrapped = _wrap(func)
# After wrapping the function we can attach that new function ont
# our `Wrapper` instance
setattr(cls, funcname, wrapped)
return cls
#wrapper
class A:
def __init__(self):
self.a = 1
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
def main(self, x):
return self.forward(x) + 100
#wrapper
class Demo2:
def foo(self):
print("yoinks")
classA = A()
otherInstance = Demo2()
print(classA.forward(10))
print(otherInstance.foo())
print("docstring is maintained: %r" % classA.forward.__doc__)
Using function decorators
Another alternative, which diverges largely from the original question but may still prove insightful is using individual functions wrappers.
The code still used the same wrapper function, but here functions/methods are annotated individually.
This might give more flexibility by offering the possibility to leave some methods "unwrapped", but could easily lead to the wrapping code being executed more often than anticipated as demonstrated in the main() method.
from functools import update_wrapper
def wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
class A:
def __init__(self):
self.a = 1
#wrap
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
#wrap # careful: will be wrapped twice!
def main(self, x):
return self.forward(x) + 100
def foo(self):
print("yoinks")
classA = A()
print(">>> forward")
print(classA.forward(10))
print("<<< forward")
print(">>> main")
print(classA.main(100))
print("<<< main")
print(">>> foo")
print(classA.foo())
print("<<< foo")
You could inherit Wrapper from A, and use super to access the parent class.
class A:
def __init__(self, child):
self.a = 1
self.child = child
def forward(self, x):
return self.a + x
def main(self, x):
return self.child.forward(x) + 100
class Wrapper(A):
def __init__(self):
super(Wrapper, self).__init__(self, self)
def forward(x):
return "whatever"
wrapperA = Wrapper()
But if you wish to use class A, just inherit A from Wrapper. Otherwise, I can't figure out whats wrong. Please don't use functions indiscriminate. Make a class you wish to use, and another one act as a parent and don't mix roles.
#...
class A(Wrapper):
def __init__(self):
super(A, self).__init__(self)
#...
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
I would like to create a python context manager, which would allow the following (with reverse_decorator applying the decorated function with first argument reversed if it is string):
print('hi')
with MyFunctionDecorator('print', reverse_decorator):
print('hello')
print('bye')
to result in:
hi
olleh
bye
The point is not the print function itself, but writing this kind of context manager, that could decorate any function - local, global, builtin, from whatever module. Is this even possible in python? How should I do it?
EDIT: To clarify a bit, the point was not to have to change the code inside the with context.
This is my approach:
from contextlib import contextmanager
from importlib import import_module
#contextmanager
def MyFunctionDecorator(func, decorator):
if hasattr(func, '__self__'):
owner = func.__self__
elif hasattr(func, '__objclass__'):
owner = func.__objclass__
else:
owner = import_module(func.__module__)
qname = func.__qualname__
while '.' in qname:
parent, qname = qname.split('.', 1)
owner = getattr(owner, parent)
setattr(owner, func.__name__, decorator(func))
yield
setattr(owner, func.__name__, func)
# Example decorator, reverse all str arguments
def reverse_decorator(f):
def wrapper(*args, **kwargs):
newargs = []
for arg in args:
newargs.append(arg[::-1] if isinstance(arg, str) else arg)
newkwargs = {}
for karg, varg in kwargs.values():
newkwargs[karg] = varg[::-1] if isinstance(varg, str) else varg
return f(*newargs, **newkwargs)
return wrapper
# Free functions
print('hi')
with MyFunctionDecorator(print, reverse_decorator):
print('hello')
print('bye')
# Class for testing methods (does not work with builtins)
class MyClass(object):
def __init__(self, objId):
self.objId = objId
def print(self, arg):
print('Printing from object', self.objId, arg)
# Class level (only affects instances created within managed context)
# Note for decorator: first argument of decorated function is self here
with MyFunctionDecorator(MyClass.print, reverse_decorator):
myObj = MyClass(1)
myObj.print('hello')
# Instance level (only affects one instance)
myObj = MyClass(2)
myObj.print('hi')
with MyFunctionDecorator(myObj.print, reverse_decorator):
myObj.print('hello')
myObj.print('bye')
Output:
hi
olleh
bye
Printing from object 1 olleh
Printing from object 2 hi
Printing from object 2 olleh
Printing from object 2 bye
This should work across functions and other modules and so on, since it modifies the attributes of the module or class. Class methods are complicated, because once you create an instance of a class its attributes point to the functions defined in the class at the time the object was created, so you have to choose between modifying the behavior of a particular instance or modifying the behavior of new instances within the managed context, as in the example. Also, trying to decorate methods of builtin classes like list or dict does not work.
It is possible if you modify it add a bit:
print('hi')
with MyFunctionDecorator(print, reverse_decorator) as print:
print('hello')
print('bye')
Here is a definition that works for this example*:
def reverse_decorator(func):
def wrapper(*args, **kwargs):
if len(args) == 1 and not kwargs and isinstance(args[0], str):
return func(args[0][::-1])
return func(*args, **kwargs)
return wrapper
class MyFunctionDecorator:
def __init__(self, func, decorator):
self.func = func
self.decorator = decorator
def __enter__(self):
"""Return the decorated function"""
return self.decorator(self.func)
def __exit__(self, *args):
"""Reset the function in the global namespace"""
globals()[self.func.__name__] = self.func
But its probably easier to just do it explicitly, following the Python Zen:
print('hi')
print('hello'[::-1])
print('bye')
*This code does not work under many circumstances, as #AranFey noted in the comments:
Inside functions
If the function you want to decorate is imported with import x from y as z
If you care that afterwards you have a print function defined in the globals(), instead of directly being a built-in
Since this is more a proof-of-concept, that yes, one can write a decorator that works in this example, I will not try to fix these shortcomings. Just use the way I gave above, or use only the decorator:
print('hi')
reverse_decorator(print)('hello')
print('bye')
Recently I'm learning Python decorator and the use of functools.wraps.
def a():
def b():
def c():
print('hello')
return c
return b
print a.__name__
#output:a
I understand why the output is a.But I don't know how __name__ change in the following code.
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
#log('...')
def simple():
print('*' * 20)
print simple.__name__
#output:wrapper
Why the output is 'wrapper' rather than 'decorator' or 'log'?
Some basics:
#decorator
def f():
pass
is equivalent to:
def f():
pass
f = decorator(f)
Decorator with args:
#decorator(*args, **kwargs)
def f():
pass
is equivalent to:
def f():
pass
decorator_instance = decorator(*args, **kwargs)
f = decorator_instance(f)
With knowing so, we may rewrite your example to:
def simple():
print('*' * 20)
log_instance = log('...')
simple = log_instance(simple)
Let's analyze what happens in last two lines:
log_instance is a decorator function, and text variable within it is equal to '...'
Since decorator (regardless of text value) returns function named wrapper, simple is replaced with function named wrapper
The point of decorators is to replace a function or class with what is returned by the decorator, when called with that function/class as argument. Decorators with arguments are a bit more convoluted, as you first call the outer method (log) to obtain a "parameterized" decorator (decorator), then you call that one and obtain the final function (wrapper), which will replace the decorated function (simple).
So, to give it some structure,
call log with '...' as argument and obtain decorator
call decorator with simple as argument and obtain wrapper
replace simple with wrapper
#log('...')
def simple(...
is equivalent to
def simple(...
simple = log('...')(simple)
so log is actually called, returning decorator, which is called with simple as argument which is then replaced by decorator's return value, which is the function wrapper, thus its __name__ is wrapper.
I have a decorator like below.
def myDecorator(test_func):
return callSomeWrapper(test_func)
def callSomeWrapper(test_func):
return test_func
#myDecorator
def someFunc():
print 'hello'
I want to enhance this decorator to accept another argument like below
def myDecorator(test_func,logIt):
if logIt:
print "Calling Function: " + test_func.__name__
return callSomeWrapper(test_func)
#myDecorator(False)
def someFunc():
print 'Hello'
But this code gives the error,
TypeError: myDecorator() takes exactly 2 arguments (1 given)
Why is the function not automatically passed? How do I explicitly pass the function to the decorator function?
Since you are calling the decorator like a function, it needs to return another function which is the actual decorator:
def my_decorator(param):
def actual_decorator(func):
print("Decorating function {}, with parameter {}".format(func.__name__, param))
return function_wrapper(func) # assume we defined a wrapper somewhere
return actual_decorator
The outer function will be given any arguments you pass explicitly, and should return the inner function. The inner function will be passed the function to decorate, and return the modified function.
Usually you want the decorator to change the function behavior by wrapping it in a wrapper function. Here's an example that optionally adds logging when the function is called:
def log_decorator(log_enabled):
def actual_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
if log_enabled:
print("Calling Function: " + func.__name__)
return func(*args, **kwargs)
return wrapper
return actual_decorator
The functools.wraps call copies things like the name and docstring to the wrapper function, to make it more similar to the original function.
Example usage:
>>> #log_decorator(True)
... def f(x):
... return x+1
...
>>> f(4)
Calling Function: f
5
Just to provide a different viewpoint: the syntax
#expr
def func(...): #stuff
is equivalent to
def func(...): #stuff
func = expr(func)
In particular, expr can be anything you like, as long as it evaluates to a callable. In particular particular, expr can be a decorator factory: you give it some parameters and it gives you a decorator. So maybe a better way to understand your situation is as
dec = decorator_factory(*args)
#dec
def func(...):
which can then be shortened to
#decorator_factory(*args)
def func(...):
Of course, since it looks like decorator_factory is a decorator, people tend to name it to reflect that. Which can be confusing when you try to follow the levels of indirection.
Just want to add some usefull trick that will allow to make decorator arguments optional. It will also alows to reuse decorator and decrease nesting
import functools
def myDecorator(test_func=None,logIt=None):
if test_func is None:
return functools.partial(myDecorator, logIt=logIt)
#functools.wraps(test_func)
def f(*args, **kwargs):
if logIt==1:
print 'Logging level 1 for {}'.format(test_func.__name__)
if logIt==2:
print 'Logging level 2 for {}'.format(test_func.__name__)
return test_func(*args, **kwargs)
return f
#new decorator
myDecorator_2 = myDecorator(logIt=2)
#myDecorator(logIt=2)
def pow2(i):
return i**2
#myDecorator
def pow3(i):
return i**3
#myDecorator_2
def pow4(i):
return i**4
print pow2(2)
print pow3(2)
print pow4(2)
Just another way of doing decorators.
I find this way the easiest to wrap my head around.
class NiceDecorator:
def __init__(self, param_foo='a', param_bar='b'):
self.param_foo = param_foo
self.param_bar = param_bar
def __call__(self, func):
def my_logic(*args, **kwargs):
# whatever logic your decorator is supposed to implement goes in here
print('pre action baz')
print(self.param_bar)
# including the call to the decorated function (if you want to do that)
result = func(*args, **kwargs)
print('post action beep')
return result
return my_logic
# usage example from here on
#NiceDecorator(param_bar='baaar')
def example():
print('example yay')
example()
Now if you want to call a function function1 with a decorator decorator_with_arg and in this case both the function and the decorator take arguments,
def function1(a, b):
print (a, b)
decorator_with_arg(10)(function1)(1, 2)