Add decorator to method from library class - python

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..

Related

Python : function decorator on class with decorator as a decorator parameter

I am unable to understand python code where function decorator is applied on a class which takes in decorator as a parameter.
I came across a code, Which I cannot share completely.
but below is skeleton of how the code looks like.
class Manager:
def first(decorator):
def inner(cls):
# Do something
# ie : setattr(cls, k, classmethod(decorator(f)))
return cls
return inner
def second(func):
def inside(*arg, **args):
# do something
return 'some_value'
return inside
Now we have a class on which decorator is applied
and another decorator is passed as a parameter.
#Manager.first(Manager.second)
class Main:
# this has __init__ and other methords
...
can someone help with what's going on in this code?

Using decorators with class

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

How to decorate inherited method - Python

I have to decorate a inherited method, but it decorates all inherited methods. Basically I have to create a decorator that will decorate just one method from the class.
The test looks like this
#my_decorator
class TestClass(Subclass):
pass
t = TestClass()
t.say_hi
Let's say my SubClass looks like this
class SubClass():
def __init__(self):
pass
def say_hi():
print("Hi")
def say_wow():
print("wow")
Now I have to make my_decorator, that has to decorate inherited function say_hi() to print("*****") before it prints "Hi"
I tried doing it like this, but than the decorator applies to all methods from SubClass
def my_decorator(cls)
def say_hi():
print("*********")
cls.say_hi()
return say_hi()
Naturally It applies to every function of the subclass, but how do I make it to apply to only a say_hi() function? -It also returns an TypeError "NoneType" object is not callable
First let us fix SubClass, because instance methods require an explicit instance parameter at definition time:
class SubClass():
def __init__(self):
pass
def say_hi(self):
print("Hi")
def say_wow(self):
print("wow")
Now you want the decorator to replace the say_hi method with a method that prints '****' before calling the original method. Le us write a decorator that just does that(*):
def my_decorator(cls):
orig = cls.say_hi # save the original method
def say_hi(self): # define a new one
print('****')
return orig(self) # ... calling the original method
cls.say_hi = say_hi # replace the method in the class
return cls
You can then use:
#my_decorator
class TestClass(SubClass):
pass
t = TestClass()
t.say_hi()
and get as expected:
****
Hi
(*) this is a very simple decorator that can only replace a say_hi(self) method: neither a different name, nor additional parameters, but decorators can be much smarter...
If you want to decorate a method, then decorate the method, not the class that contains it. If you want a new class, then the decorator applied to the class needs to return a class.
def print_banner(f):
def _(*args, **kwargs):
print("****")
f(*args, **kwargs)
return _
class SubClass():
def __init__(self):
pass
#print_banner
def say_hi(self, ):
print("Hi")
def say_wow(self):
print("wow")

Trying to program a "to_class" general decorator

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>

self in python decorators

I want a decorator that would add the decorated function to list, like this :
class My_Class(object):
def __init__(self):
self.list=[]
#decorator
def my_function(self)
print 'Hi'
I expect my_function to be added to self.list, but I just can't write this decorator. If I try to write it inside My_Class, then I would have to use #self.decorator, and self does not exist since we're outside any function. And if I try to write it out of My_Class, then I can't retrieve self from my_function.
I know quite similar questions exist, but they are overly complicated, and I'm just learning python and decorators.
You can't access self from the decorator, because the decorator is run at the time the function is defined, and at that time there are no instances of My_Class yet.
It's better to put the function list as a class attribute instead of an instance attribute. Then you can pass this list as a parameter to the decorator:
def addToList(funcList):
'''Decorator that adds the function to a given list'''
def actual_decorator(f):
funcList.append(f)
return f
return actual_decorator
class MyClass(object):
funcList = []
#addToList(funcList)
def some_function(self, name):
print 'Hello,', name
Now you can access MyClass.funcList to get the list of decorated functions.
There's nothing really special about writing decorators for bound functions (instance methods). For example, this simple example works fine:
def decorator(fn):
print "I'm decorating!"
return fn
class MyClass(object):
def __init__(self):
self.list = []
#decorator
def my_function(self):
print "Hi"
If you want to use self in your decorator, you'll treat your decorator the same as you'd treat any decorator that uses the function's args:
def decorator(fn):
def _decorator(self):
print "I'm decorating, and here's my list: %s!" % self.list
return fn(self)
return _decorator
Your list attribute should be a class attribute (and it should be renamed, because list is a builtin type). Then you can do something like this:
my_methods = []
def my_method(method):
my_methods.append(method)
return method
class MyClass(object):
my_methods = my_methods
#my_method
def my_function(self):
print 'Hi'

Categories

Resources