Pythonic way to decorate parent method in subclass - python

Let's say I have the following classes:
class A:
def query(self, id):
...
class B(A):
...
And this decorator:
def my_decorator(func):
#functools.wraps(func)
def wrapper(self, *args, **kwargs):
...
return self
return wrapper
I want to decorate B.query to modify its behavior. I've tried the following:
This seems to work.
class B(A):
#my_decorator
def query(self, id):
A.query(self, id)
I couldn't get this to work:
class B(A):
def __init__(self, **kwargs):
self.query = my_decorator(self.query)(self)
What's the most pythonic way to go about this?

It works, you just had a couple of bugs.
class A:
def query(self, id):
print(f"A.query {id}")
def my_decorator(func):
#functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"decorator {args=} {kwargs=}")
return func(*args, **kwargs) # Call the function in the wrapper
return wrapper
class B(A):
#my_decorator
def query(self, id):
print(f"B.query {id}")
super().query(id) # use super() to get the instance of A
a = A()
a.query(1)
b = B()
b.query(2)

Related

how do I create a meta-class such that instances of the class are wrapped in `functools.wraps`?

I want to wrap a function name funky_the_function with an object which has a __call__ method defined.
Without using functools.wraps the name of the wrapped function will be lost and so will the docstring.
How do I create a meta-class such that instances of the class are wrapped in functools.wraps?
import functools
class MetaDecorator(type):
def __call__(self, *args):
super().__call__(*args)
# SOMEWHERE INSIDE OF `__call__` WE HAVE:
# obj = functools.wraps(obj)
class Decorator(metaclass=MetaDecorator):
def __init__(f):
assert(callable(f))
self._f = f
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)
#Decorator
def funky_the_function(*args, **kwargs):
"""Documentation string"""
print('Called example function')
print(funky_the_function.__name__)
print(funky_the_function.__doc__)
1. without metaclass, without wraps:
If you're looking for a way to fix the name and docstring, it can be easily fix by dynamically adding the __name__ and __doc__ to the instance. There is no need for meta-classes.
class Decorator:
def __init__(self, f):
assert callable(f)
self.__name__ = f.__name__
self.__doc__ = f.__doc__
self._f = f
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)
2. with metaclass, without wraps:
Of course you can do this in metaclass as well:
class MetaDecorator(type):
def __call__(self, f):
assert callable(f)
instance = super().__call__(f)
instance.__name__ = f.__name__
instance.__doc__ = f.__doc__
return instance
class Decorator(metaclass=MetaDecorator):
def __init__(self, f):
self._f = f
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)
3. with metaclass, with wraps:
from functools import wraps
class MetaDecorator(type):
def __call__(self, f):
assert callable(f)
instance = super().__call__(f)
instance = wraps(f)(instance)
return instance
class Decorator(metaclass=MetaDecorator):
def __init__(self, f):
self._f = f
def __call__(self, *args, **kwargs):
return self._f(*args, **kwargs)

How can I get a class parameter from a function of class via decorator class?

The problem:
I want to get an attribute of class via decorator which is a class but I can not.
The question is how can?
class DecoratorClass:
def __call__(self, fn, *args, **kwargs) -> Callable:
try:
# do something with the TestClass value
return fn
finally:
pass
class TestClass:
def __init__(self):
self.value = 1
#DecoratorClass()
def bar(self):
return 1
How can I reach the the TestClass's value attr via DecoratorClass?
I got the solution :)
class Decoratorclass:
def __call__(self, fn, *args, **kwargs) -> Callable:
def decorated(instance):
try:
# do something with the TestClass value
print(instance.value)
return fn(instance)
finally:
pass
return decorated
class TestClass:
def __init__(self):
self.value = 1
#Decoratorclass()
def bar(self):
return 1

How to use a class method to decorate other class methods?

When designing a class, I find that in the class methods, there are repeated steps are called each time when invoke the class method. For example:
class Queue(object):
def __init__(self):
self.connection = Connection()
def create(self, name):
self.probe = self.connection.plug()
self.probe.create(name)
self.probe.unplug()
def delete(self, name):
self.probe = self.connection.plug()
self.probe.delete(name)
self.probe.unplug()
And there are many methods require the similar steps to 'plug' and 'unplug' the 'probe'. In this design we need to 'plug' and 'unplug' the 'probe' each time we perform the actions.
Thus I am thinking about the wrap those functions by decorator to make the code looking less repeated.
class Queue(object):
def __init__(self):
self.connection = Connection()
def _with_plug(self, fn):
def wrapper(*args, **kwargs):
self.probe = self.connection.plug()
fn(*args, **kwargs)
self.probe.unplug()
#_with_plug
def create(self, name):
self.probe.create(name)
#_with_plug
def delete(self, name):
self.probe.delete(name)
But this strategy is not working. How could I use a method in the class to decorate other methods to perform such actions before and after calling a method?
Seems like a bit of muddled arguments to me:
file deco.py, say
def _with_plug(fn): # decorator takes exactly one argument, the function to wrap
print("wrapping", fn.__name__)
def wrapper(self, *args, **kwds):
print("wrapper called")
self.probe = [self.connection, ".plug()"]
fn(self, *args, **kwds)
self.probe.append(".unplug()")
return wrapper # decorator must return the wrapped function
class Queue(object):
def __init__(self):
self.connection = "Connection()"
#_with_plug
def create(self, name):
self.probe.append("create(name)")
#_with_plug
def delete(self, name):
self.probe.append("delete(name)")
Check:
>>> import deco
wrapping create
wrapping delete
>>> q = deco.Queue()
>>> q.create("name")
wrapper called
>>> q.probe
['Connection()', '.plug()', 'create(name)', '.unplug()']
Observe that the decorator function is called at definition time of the to-be-wrapped function, i.e. before the class definition is completed and long before the first instance is created. Therefore you can't reference self in the way you tried.
You should define your decorator function outside of the class body and your decorator function should return the wrapped function in order for it to work. Something like:
def _with_plug(fn):
def wrapper(self, *args, **kwargs):
self.probe = self.connection.plug()
fn(self, *args, **kwargs)
self.probe.unplug()
return wrapper
class Queue(object):
def __init__(self):
self.connection = Connection()
#_with_plug
def create(self, name):
self.probe.create(name)
#_with_plug
def delete(self, name):
self.probe.delete(name)

How to pass self into a decorator?

How do I pass self.key below into the decorator?
class CacheMix(object):
def __init__(self, *args, **kwargs):
super(CacheMix, self).__init__(*args, **kwargs)
key_func = Constructor(
memoize_for_request=True,
params={'updated_at': self.key}
)
#cache_response(key_func=key_func)
def list(self, *args, **kwargs):
pass
class ListView(CacheMix, generics.ListCreateAPIView):
key = 'test_key'
I get the error:
'self' is not defined
Here's an example of doing it with a class decorator as I tried to describe to you in the comments. I filled-in a few undefined references in your question and used a super-simplified version of your cache_response function decorator, but hopefully this will convey the idea concretely enough for you to be able adapt it to your real code.
import inspect
import types
class Constructor(object):
def __init__(self, memoize_for_request=True, params=None):
self.memoize_for_request = memoize_for_request
self.params = params
def __call__(self):
def key_func():
print('key_func called with params:')
for k, v in self.params.items():
print(' {}: {!r}'.format(k, v))
key_func()
def cache_response(key_func):
def decorator(fn):
def decorated(*args, **kwargs):
key_func()
fn(*args, **kwargs)
return decorated
return decorator
def example_class_decorator(cls):
key_func = Constructor( # define key_func here using cls.key
memoize_for_request=True,
params={'updated_at': cls.key} # use decorated class's attribute
)
# create and apply cache_response decorator to marked methods
# (in Python 3 use types.FunctionType instead of types.UnboundMethodType)
decorator = cache_response(key_func)
for name, fn in inspect.getmembers(cls):
if isinstance(fn, types.UnboundMethodType) and hasattr(fn, 'marked'):
setattr(cls, name, decorator(fn))
return cls
def decorate_me(fn):
setattr(fn, 'marked', 1)
return fn
class CacheMix(object):
def __init__(self, *args, **kwargs):
super(CacheMix, self).__init__(*args, **kwargs)
#decorate_me
def list(self, *args, **kwargs):
classname = self.__class__.__name__
print('list() method of {} object called'.format(classname))
#example_class_decorator
class ListView(CacheMix):
key = 'test_key'
listview = ListView()
listview.list()
Output:
key_func called with params:
updated_at: 'test_key'
list() method of ListView object called
I just found out that if you write the decorator function like so:
def decorator(the_func):
#wraps(the_func)
def wrapper(*args, **kwargs):
the_func(*args, **kwargs)
return wrapper
and decorate any method which takes self as an argument, self will appear in args. Therefore you can do this:
from functools import wraps
class myClass:
def __init__(self):
self.myValue = "Hello"
def decorator(the_func):
#wraps(the_func)
def wrapper(*args, **kwargs):
print(args[0].myValue)
the_func(*args, **kwargs)
return wrapper
#decorator
def myFunction(self):
print("World")
Call it like you normally would
foo = myClass()
foo.myFunction()
and you should get
Hello
World

Python __getattribute__ and wrapper of method

I want to call a wrapper method of function with argument just before I call specific method.
So I guess I have to ovewrite __getattribute__ method.
Here is an example of code:
def wrapper(func):
return func * 2
class A(object):
def test(self, arg):
return arg + 1
def __getattribute__(self, name):
if name in ['test']:
return wrapper(super(A, self).__getattribute__(name))
return super(A, self).__getattribute__(name)
The matter is that getattribute is called when test return the value. What I want is to be able to catch test with the arguments and define the wrapper method like this:
def wrapper(func, *args, **kwargs):
print "do some stuff"
return func(*args, **kwargs)
Use a factory function to return your wrapper:
def decorate(f):
def wrapper(*args, **kw):
return f(*args, **kw) * 2
return wrapper
Here f is closed over by wrapper(), so it can access the name when called.
Then return this in the __getattribute__ hook:
def __getattribute__(self, name):
result = super(A, self).__getattribute__(name)
if name in ('test',):
return decorate(result)
return result
Of course, you could also just apply decorate as a decorator on test then:
class A(object):
#decorate
def test(self, arg):
return arg + 1
If I understand you correctly, you can use a decorator.
def wrapper(func):
def _wrapper(*args, **kwargs):
return func(*args, **kwargs) * 2
return _wrapper
class A(object):
#wrapper
def test(self, arg):
return arg + 1

Categories