I would like to extend a library's decorator. I know that I can just call both decorators:
#my_decorator
#lib_decorator
def func():
pass
But I would like to avoid having to pass #lib_decorator to each function each time. I would like my decorator to automatically decorate func() with lib_decorator. How can I do this? Can they be nested?
You can incorporate the lib's decorator within yours. For simple, argument-less decorators, it's rather straight-forward:
def my_decorator():
#lib_decorator # <--- Just include the lib's decorator here
def inner:
func(*args, **kwargs)
return inner
It's a bit trickier for decorators that have arguments. Just remember that your decorator is replacing the decorated function with the inner-most function. So that's the one you need to decorate. So if you call your decorator with args, e.g.
#my_decorator(arg)
def func():
pass
Then decorate the inner function with the lib decorator:
def my_decorator(arg):
def wrapper(func):
#lib_decorator # <--- Just include the lib's decorator here
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
return wrapper
Or, using the class form of the decorator function:
class my_decorator():
def __init__(self, arg):
pass
def __call__(self, func):
#lib_decorator # <--- Just include the lib's decorator here
def inner(*args, **kwargs):
func(*args, **kwargs)
return inner
You can easily transform a decoration like yours:
#my_decorator
#lib_decorator
def func():
pass
To this simpler decoration, using function composition:
my_composed_decorator = lambda func: my_decorator(lib_decorator(func))
#my_composed_decorator
def func():
pass
Related
I have a decorator responsible for logging. E.g. something like this:
import logging
import functools
from typing import Callable
def log_container(func: Callable, logger=None):
if logger is None:
logger = logging.getLogger("default")
#functools.wraps(func)
def wrapper(*args, **kwargs):
logger.info(f"{func.__qualname__} started.")
result = func(*args, **kwargs)
logger.info(f"{func.__qualname__} ended.")
return result
return wrapper
I have a class that has a distinct logger as an attribute, and I want to give that logger as a decorator argument. Is this possible? and if so, how?
class Dummy:
logger = logging.getLogger("Dummy")
#log_container(logger=Dummy.logger)
def do_something(self):
pass
Your decorator, as its currently defined, wouldn't work. You can't pass in the decorated function together with an argument since the decorator itself only accepts one argument: the func itself. If you want your decorator to accept an argument, you'll need to nest the decorator one level deeper. Which now becomes a function that returns a decorator:
def log_container(logger=None):
if logger is None:
logger = logging.getLogger("default")
def inner(func: Callable):
#functools.wraps(func)
def wrapper(*args, **kwargs):
logger.info(f"{func.__qualname__} started.")
result = func(*args, **kwargs)
logger.info(f"{func.__qualname__} ended.")
return result
return wrapper
return inner
Then when decorating the method for the Dummy class, don't use Dummy.logger since Dummy won't be defined until the end of the class definition, instead, you can access the class attribute logger directly.
class Dummy:
logger = logging.getLogger("Dummy")
#log_container(logger=logger)
def do_something(self):
pass
# Use default logger
#log_container()
def do_something_else(self):
pass
So, im writing a library for appium tests.
I have a main class that look like this:
class APP():
def __init__(self):
self.variable1 = 1
self.current_view = "main_screen"
def do_operation_A(self):
self.open_side_menu()
do_something
self.current_view = "side_menu"
def do_operation_B(self):
self.open_side_menu()
do_something_else
self.current_view = "side_menu"
def set_landscape(self):
self.open_settings_menu()
configure_landscape
self.current_view = "settings_menu"
The class has a lot of operations so i can do things like app.do_operation_A() or app.set_landscape() without having to first go to each menu manually (resolved inside the class)
To reduce this i want to implement a decorator to do something like this if possible:
class APP():
def __init__(self):
self.variable1 = 1
self.current_view = "main_screen"
#DEFINE_DECORATOR_HERE
#side_menu
def do_operation_A(self):
do_something
#side_menu
def do_operation_B(self):
do_something_else
#settings_menu
def set_landscape(self):
configure_landscape
So i want to implement this decorators to navigate to the corresponding view and also change that variable that i use to check some things in other functions. I have seen some examples with functools.wraps but is not clear to me of how to implement the decorator inside the class to be able to modify this self variables.
Any help?
Using a decorator means that you "wrap" your other function, i.e. you call the decorator and then call the function from inside the decorator.
E.g.:
import functools
def outer(func):
#functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
Upon defining the function, the decorator will be called, returning the inner function.
Whenever you call func, you will in reality call inner, which runs it's own code, including calling the original func function.
So for your use case, you should be able to create decorators similar to:
def settings_menu(func):
#functools.wraps(func)
def inner(self, *args, **kwargs):
self.open_settings_menu()
self.current_view = "settings_menu"
return func(self, *args, **kwargs)
return inner
So a decorator is basically a function that returns another function, right?
def side_menu(func):
def wrapper():
return func()
return wrapper
The wrapper, returned by side_menu, will be called whenever App().do_operationA is called. And whenever that method is called, self is always the first argument. Or rather, the first argument is the instance of App, but whatever. So we could do:
def side_menu(func):
def wrapper(self, *args, **kwargs):
self.open_menu()
func(self, *args, **kwargs)
return wrapper
Now, you don't want the method to present itself as wrapper - you like the name do_operationA. That's where #functools.wraps comes in, it makes things look and work right when decorating.
def side_menu(func):
#functools.wraps
def wrapper(self, *args, **kwargs):
self.open_menu()
func(self, *args, **kwargs)
return wrapper
I have a scenario like:
#decorator_one(1)
#foo
#bar
def my_decorated_func():
pass
I am trying to condense this into something like:
#my_custom_decorator(1)
def my_decorated_func():
pass
This is straightforward if I have decorators that do not have (1) option:
def my_custom_decorator(f):
#decorator_one
#foo
#bar
def wrapped():
pass
return wrapped
However I am uncertain how to properly propagate the argument to the first wrapper.
In this case, I am comfortable assuming that if I pass 1 to my_custom_decorator that it will always and only be the arg for decorator_one.
#decorator_one(1) means that there is a callable that returns a decorator; call it a decorator factory. decorator_one(1) returns the decorator that is then applied to the function.
Just pass on the arguments from your own decorator factory:
def my_custom_decorator(*args, **kwargs): # the factory
def decorator(f): # the decorator
#decorator_one(*args, **kwargs)
#foo
#bar
def wrapped():
pass
return wrapped
return decorator
I used *args, **kwargs to pass on arbitrary arguments to the wrapped decorator factory decorator_one(), but you could also use explicitly named paramaters.
Yes, you need a decorator factory.
I found the solution using a class more attractive because it separates the factory and the decorator:
class my_custom_decorator(object):
__init__(self, *args, **kwargs): # the factory
self.args = args
self.kwargs = kwargs
__call__(self, f): # the decorator
#decorator_one(*self.args, **self.kwargs)
#foo
#bar
def wrapped():
pass
return wrapped
In setUp() method of unittest I've setup some self variables, which are later referenced in actual tests. I've also created a decorator to do some logging. Is there a way in which I can access those self variables from decorator?
For the sake of simplicity, I'm posting this code:
def decorator(func):
def _decorator(*args, **kwargs):
# access a from TestSample
func(*args, **kwargs)
return _decorator
class TestSample(unittest.TestCase):
def setUp(self):
self.a = 10
def tearDown(self):
# tear down code
#decorator
def test_a(self):
# testing code goes here
What would be the best way of accessing a (set in setUp()) from decorator?
Since you're decorating a method, and self is a method argument, your decorator has access to self at runtime. Obviously not at parsetime, because there are no objects yet, just a class.
So you change your decorator to:
def decorator(func):
def _decorator(self, *args, **kwargs):
# access a from TestSample
print 'self is %s' % self
return func(self, *args, **kwargs)
return _decorator
I guess that's how they are called, but I will give examples just in case.
Decorator class:
class decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print 'something'
self.func(*args, **kwargs)
Decorator function:
def decorator(func):
def wrapper(*args, **kwargs):
print 'something'
return func(*args, **kwargs)
return wrapper
Is using one or the other just a matter of taste? Is there any practical difference?
If you can write a function to implement your decorator you should prefer it. But not all decorators can easily be written as a function - for example when you want to store some internal state.
class counted(object):
""" counts how often a function is called """
def __init__(self, func):
self.func = func
self.counter = 0
def __call__(self, *args, **kwargs):
self.counter += 1
return self.func(*args, **kwargs)
#counted
def something():
pass
something()
print something.counter
I've seen people (including myself) go through ridiculous efforts to write decorators only with functions. I still have no idea why, the overhead of a class is usually totally negligible.
It is generally just a matter of taste. Most Python programs use duck typing and don't really care whether the thing they're calling is a function or an instance of some other type, so long as it is callable. And anything with a __call__() method is callable.
There are a few advantages to using function-style decorators:
Much cleaner when your decorator doesn't return a wrapper function (i.e., it returns the original function after doing something to it, such as setting an attribute).
No need to explicitly save the reference to the original function, as this is done by the closure.
Most of the tools that help you make decorators, such as functools.wraps() or Michele Simionato's signature-preserving decorator module, work with function-style decorators.
There may be some programs out there somewhere which don't use duck typing, but actually expect a function type, so returning a function to replace a function is theoretically "safer."
For these reasons, I use function-style decorators most of the time. As a counterexample, however, here is a recent instance in which the class-style decorator was more natural for me.
The proposed class decorator implementation has a slight difference with the function implementation : it will fail on methods
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('something')
self.func(*args, **kwargs)
class A:
#Decorator
def mymethod(self):
print("method")
A().mymethod()
will raise TypeError: mymethod() missing 1 required positional argument: 'self'
To add support of methods, you need to implement the __get__
import types
class Decorator2(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('something')
self.func(*args, **kwargs)
def __get__(self, instance, owner):
if instance is None:
return self
return types.MethodType(self, instance)
class B:
#Decorator2
def mymethod(self):
print("method")
B().mymethod()
will output
class B:...
something
method
The reason it works is that when you access B().mymethod, the __get__ is called first and supplies the bound method. Then __call__ is called
To conclude, provided you define the __get__, class and function implementation can be used the same way. See python cookbook recipe 9.9 for more information.