I am trying to add a decorator to required class methods and I have come up with the following code for it. I need this to work with all the similar classes.
import allure
def class_method_dec(cls):
"""
Decorator function to decorate required class methods.
"""
if cls.method1:
cls.method1= allure.step(cls.method1)
if cls.method2:
cls.method2= allure.step(cls.method2)
if cls.method3:
cls.method3= allure.step(cls.method3)
return cls
#class_method_dec
class TestClass:
def __init__(self, a, b):
self.a = a
self.b = b
def method1(self):
"""
method docstring
"""
pass
def method2(self):
"""
method docstring
"""
pass
def method3(self):
"""
method docstring
"""
pass
Is this the right way to do it? I am looking for the best way to do this.
Also, I understand that we can use functools.wraps to preserve the docstring when decorating functions. Is there a need of something like it when we are decorating classes?
From Satwik Kansal’s brilliant Metaprogramming in Python IBM tutorial , I discovered this gem:
Satwik first defined a decorator:
from functools import wraps
import random
import time
def wait_random(min_wait=1, max_wait=30):
def inner_function(func):
#wraps(func)
def wrapper(args, **kwargs):
time.sleep(random.randint(min_wait, max_wait))
return func(args, **kwargs)
return wrapper
return inner_function
And then he created a class wrapper that will apply this decorator to a class:
def classwrapper(cls):
for name, val in vars(cls).items():
#callable return True if the argument is callable
#i.e. implements the __call
if callable(val):
#instead of val, wrap it with our decorator.
setattr(cls, name, wait_random()(val))
return cls
Application:
# decorate a function
#wait_random(10, 15)
def function_to_scrape():
#some scraping stuff
# decorate a class
#classwrapper
class Scraper:
# some scraping stuff
To make use of it in your case, substitute wait_random decorator with your own. Turn your function to a decorator.
E.g
from functools import wraps
import allure
def apply_allure():
def inner_function(func):
#wraps(func)
def wrapper(args, **kwargs):
func = allure.step(func)
return func(args, **kwargs)
return wrapper
return inner_function
In the classwrapper replace wait_random with apply_allure:
Do read the tutorial for more information and explanations
Related
I have a class that I cannot change (it comes from a library), which may look like
class Test(object):
def __init__(self):
pass
def bar(self, x):
return x
And I want to add a decorator to the bar method in Test, like the following, for instance:
from functools import wraps
def some_decorator(fn):
#wraps(fn)
def wrapped(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
return wrapped
By "adding" the decorator I mean to have a way to generate objects that have the bar method wrapped in some_decorator.
However, I cannot change the code in Test, which makes my problem harder. Is there an easy way to add a decorator in a method from a class that I cannot change in python?
As you probably know the decorator is just a function which takes a function as an argument and returns a new function, thus you can do following nasty-hacky monkeypatching:
import your_library
your_library.Test.your_function = your_decorator(your_library.Test.your_function)
If you dont want to modify the original class definition then you can achieve this with with simple inheritance itself instead of using decorators..
class Test(object):
def __init__(self):
pass
def bar(self, x):
print("In base bar..")
return x
class DerivedTest(Test):
def __init__(self):
super().__init__()
def bar(self,x):
print("In derive's bar..")
super().bar(x)
Now say when you execute:
dt=DerivedTest()
dt.bar(10)
The output will be
In derive's bar..
In base bar..
You can put whatever wrapper code you were intending to before and after the super() call..
I’m trying to create a decorator that is called within a class, which would pull attributes from that class, and use those class attributes to edit the function’s docstring.
My problem is that I have found examples of decorators that edit the docstring of the function (setting the function's __doc__ attribute equal to a new string), and I have also found examples of decorators that pull attributes from the parent class (by passing self into the decorator), but I haven’t been able to find an example of a decorator that is able to do both.
I have tried to combine these two examples, but it isn't working:
def my_decorator(func):
def wrapper(self, *args, **kwargs):
name = func.__name__ # pull function name
cls = self.__class__.__name__ # pull class name
func.__doc__ = "{} is new for the function {} in class {}".format(
str(func.__doc__), name, cls) # set them to docstring
return func(self, *args, **kwargs)
return wrapper
class Test():
#my_decorator
def example(self, examplearg=1):
"""Docstring"""
pass
With this, I would hope that the following would return "Docstring is now new for the function: example":
Test().example.__doc__
Instead it returns None.
Edit: Note that I am not interested in how to access the name of the class specifically, so much as how to access the class attributes in general (where here self.__class__.__name__ is used as an example).
example is replaced with wrapper; the decoration is equivalent to
def example(self, examplearg=1):
"""Docstring"""
pass
example = my_decorator(example)
so you need to set wrapper.__doc__, not func.__doc__.
def my_decorator(func):
def wrapper(self, *args, **kwargs):
return func(self, *args, **kwargs)
wrapper.__doc__ = "{} is new for the function {}".format(
str(func.__doc__),
func.__name__)
return wrapper
Note that at the time you call my_decorator, you don't have any information about what class the decorated function/method belongs to. You would have to pass its name explicitly:
def my_decorator(cls_name):
def _decorator(func):
def wrapper(self, *args, **kwargs):
return func(self, *args, **kwargs)
wrapper.__doc__ = "{} is new for function {} in class {}".format(
func.__doc__,
func.__name__,
cls_name)
return wrapper
return _decorator
class Test():
#my_decorator("Test")
def example(self, examplearg=1):
"""Docstring"""
# or
# def example(self, examplearg=1):
# """Docstring"""
#
# example = my_decorator("Test")(example)
You can simply modify the __doc__ attribute when the decorator is called instead, and use the first token of the dot-delimited __qualname__ attribute of the function to obtain the class name:
def my_decorator(func):
func.__doc__ = "{} is new for the function {} in class {}".format(
str(func.__doc__), func.__name__, func.__qualname__.split('.')[0])
return func
so that:
class Test():
#my_decorator
def example(self, examplearg=1):
"""Docstring"""
pass
print(Test().example.__doc__)
would output:
Docstring is new for the function example in class Test
Turns out that accessing class attributes from within a class is impossible, as the class has yet to be executed when the decorator is called. So the original goal - using a decorator within a class to access class attributes - does not seem to be possible.
However, thanks to jdehesa for pointing me to a workaround that allows access to the class attributes using a class decorator, here: Can a Python decorator of an instance method access the class?.
I was able to use the class decorator to alter the specific method's docstring using class attributes like so:
def class_decorator(cls):
for name, method in cls.__dict__.items():
if name == 'example':
# do something with the method
method.__doc__ = "{} is new for function {} in class {}".format(method.__doc__, name, cls.__name__)
# Note that other class attributes such as cls.__base__
# can also be accessed in this way
return cls
#class_decorator
class Test():
def example(self, examplearg=1):
"""Docstring"""
print(Test().example.__doc__)
# Returns "Docstring is new for function example in class Test"
I am trying to write a decorator for an instance method, as follows:
from functools import wraps
def plus_decorator(f):
#wraps(f)
def wrapper(*args, **kwargs):
return 1 + f(*args, **kwargs)
return wrapper
#plus_decorator
def return_i(i):
return i
class A(object):
#plus_decorator
def return_i(self, i):
return i
#plus_decorator
#classmethod
def return_i_class(cls, i):
return i
#plus_decorator
#staticmethod
def return_i_static(i):
return i
if __name__ == '__main__':
print return_i(1)
a = A()
print a.return_i(1)
print A.return_i_class(1)
print A.return_i_static(1)
However, it pops up the error:
AttributeError: 'classmethod' object has no attribute '__module__'
I am wondering why the decorator does not work on classmethod and staticmethod. I think the decorator mostly just passes all the parameters it receives to the wrapper, and only modifies the result. How can I modify the decorator to make it work for classmethod and staticmethod?
Just flip the order. Put the #classmethod or #staticmethod on the outside, and your decorator (which uses #wraps, and therefore needs a bare function) on the inside.
#classmethod
#plus_decorator
def return_i_class(cls, i):
return i
Try reversing.
It will work when #staticmethod and #classmethod are top-most decorators as explained here
Because your decorator expects a function but both the other decorators return descriptor objects.
Suppose I have defined:
def to_class(cls):
""" returns a decorator
aimed to force the result to be of class cls. """
def decorating_func(func):
def wrapper(*args, **kwargs):
return cls(func(*args, **kwargs))
return wrapper
return decorator(decorating_func)
I wish to use it to create decorators which turn function results to objects of a given class. However, this will not work:
class TestClass(object):
def __init__(self, value):
self._value = (value, value)
def __str__(self):
return str(self._value)
#staticmethod
#to_test_class
def test_func(value):
return value
to_test_class = to_class(TestClass)
as test_func will look for to_test_class and will not find it. On the other hand, putting the assignment to to_test_class before the class definition will fail as well, as TestClass will not be defined yet.
Trying to put #to_class(TestClass) above the definition of test_func will also fail, as the method is constructed before the class (if I am not wrong).
The only workaround I have found is to define to_test_class manually as a decorator, and not as one returned from the general "to_class" def.
It might be important to mention that this is only a basic example, but I wish to use to_class for many applications, such as modifying the returned value before 'plugging' it into the class' constructor; and I wish to use it as a decorator for other class' methods as well.
I am sure some think a "to_class" decorator is pointless; manipulations can be done within the decorated method, instead. Though, I find it convenient, and it helps me with readability.
Finally I wish to add that this interests me 20% for practical reasons and 80% for studying reasons, as I find this is something I do not fully understand about decorators in Python in general.
Indeed, at class construction time, the class object itself has not yet been constructed, thus you cannot use it as the basis of a decorator.
One work-around I can think of, is to not use the staticmethod decorator. Instead, internally in your own decorator, re-use the classmethod decorator. That way you ensure that Python at the very least passes in the associated class for you:
def to_class(func):
""" returns a decorator
aimed to force the result to be of class cls. """
def wrapper(cls, *args, **kwargs):
return cls(func(*args, **kwargs))
return classmethod(wrapper)
Then use it like this:
class TestClass(object):
def __init__(self, value):
self._value = (value, value)
def __str__(self):
return str(self._value)
#to_class
def test_func(value):
return value
Demonstration:
>>> def to_class(func):
... """ returns a decorator
... aimed to force the result to be of class cls. """
... def wrapper(cls, *args, **kwargs):
... return cls(func(*args, **kwargs))
... return classmethod(wrapper)
...
>>> class TestClass(object):
... def __init__(self, value):
... self._value = (value, value)
... def __str__(self):
... return str(self._value)
... #to_class
... def test_func(value):
... return value
...
>>> TestClass.test_func('foo')
<__main__.TestClass object at 0x102a77210>
>>> print TestClass.test_func('foo')
('foo', 'foo')
A generic version of your decorator is not easy; the only other workaround to your conundrum is to use a metaclass hack; see another answer of mine where I describe the method in more detail.
You basically need to reach into the class-under-construction namespace, set a temporary metaclass, and then rely on there being at least one instance of the class before your decorator will work; the temporary metaclass approach hooks into the class creation mechanisms to retrieve the constructed class at a later time.
Seeing as you are using this decorator as an alternative class factory however, that is probably not going to be ideal; if someone used your decorated functions to create class instances exclusively the metaclass would be called too late.
Well, you forgot that class is the first parameter passed to method decorated with classmethod, so you can write it like this:
def to_this_class(func):
def wrapped(cls, value):
res = func(cls, value)
return cls(res)
return wrapped
class TestClass(object):
def __init__(self, value):
self._value = (value, value)
def __str__(self):
return str(self._value)
#classmethod
#to_this_class
def test_func(cls, value):
return value
x = TestClass('a')
print x.test_func('b')
The problem is that a decorator gets evaluated upon defining the thing it decorates, so when defining the method test_func(), the decorator to_test_class gets called, and even if it already exists, the thing it shall work on (the class TestClass) does not exist yet (as this is created after all methods are created).
Maybe you can use a placeholder at the point where the class is used and later (after the class is created) fill in that value (the class) at the point of the placeholder.
Example:
lazyClasses = {}
def to_lazy_class(className):
""" returns a decorator
aimed to force the result to be of class cls. """
def decorating_func(func):
def wrapper(*args, **kwargs):
return lazyClasses[className](func(*args, **kwargs))
return wrapper
return decorating_func
class TestClass(object):
def __init__(self, value):
self._value = (value, value)
def __str__(self):
return str(self._value)
#staticmethod
#to_lazy_class('TestClass')
def test_func(value):
return value
lazyClasses['TestClass'] = TestClass
>>> TestClass.test_func('hallo')
<__main__.TestClass object at 0x7f76d8cba190>
I would like to provide a decorator that allows for an optional configuration when applied to a function.
A simple implementation follows:
import functools
class Deco(object):
config = {'message': 'hello'}
def __init__(self, func):
self.func = func
functools.wraps(func)(self)
def __call__(self, *args, **kwargs):
print self.config['message']
return self.func(*args, **kwargs)
#classmethod
def customize(cls, **kwargs):
"""Return a customized instance of this class. """
return type(cls.__name__, (Deco, ), {'config': kwargs})
#Deco
def add(a, b):
return a + b
#Deco.customize(message='bye')
def sub(a, b):
return a - b
>>> add(1, 2)
'hello'
>>> sub(2, 1)
'bye'
I would like to use it to provide user-friendly decorators for Django views.
This approach works without errors, but is there something bad about allowing a class to have a static factory method instantiating customized instances of it self, as a decorator?
You could work without creating an extra sub-class for each time the decorator is used there, but your code is fine. The way without extra subclass could be something along:
class Deco(object):
config = {'message': 'hello'}
def __init__(self, func=None, **kwargs):
if kwargs:
self.config = kwargs
if func is not None:
self._decorate(func)
def _decorate(self, func):
self.func = func
functools.wraps(func)(self)
def __call__(self, *args, **kwargs):
if not hasattr(self, "func"):
self._decorate(func)
return self
print self.config['message']
return self.func(*args, **kwargs)
So, while performance wise there would be no difference to your code (unless you would be decorating at least hundreds of thousands of functions - your code create an extra object - a class - for each time the decorator is used, besides the instance of that class) - there is an impact on people would review your code (either to use your modules, or to maintain it after you are done). I mean "a decorator that dynamically generates subclasses of itself" may sound too advanced and scare people away. Although it is as simple as my suggestion above once one understands the mechanisms of class generation in Python as you had.