Python - class decorator - runtime inheritance - python

I am trying to create a decorator that will inject some functionality to wrapped class __init__ method. This is what works.
class Decorator:
def __init__(self, arg):
print(arg)
self.arg = arg
def __call__(self, cls):
print(cls)
class Wrapped(cls):
def __init__(self, first_arg, second_arg, **kwargs):
cls.__init__(self, first_arg, second_arg, **kwargs)
print('in wrapped init', self.variable)
return Wrapped
#Decorator('random_string')
class TestClass:
def __init__(self, first_arg, second_arg, **kwargs):
self.variable = 10
print('TestClass init')
test = TestClass(first_arg='one', second_arg='two')
and produces
random_string
<class '__main__.TestClass'>
TestClass init
in wrapped init 10
for some mysterious reasons code is no longer working after removing decorator param (random string in this case)
#Decorator
class TestClass:
def __init__(self, first_arg, second_arg, **kwargs):
self.variable = 10
print('TestClass init')
Output:
Traceback (most recent call last):
File "/home/python_examples/test_decorators.py", line 24, in <module>
test = TestClass(first_arg='one', second_arg='two')
<class '__main__.TestClass'>
TypeError: __call__() got an unexpected keyword argument 'first_arg'
Two questions:
is this a well-known and valid approach to decorate classes?
why is the never used 'random_string' param crucial?

It's because your decorator class takes arg as a constructor argument. So when you remove your decorator param, make sure that you have removed that parameter from the __init__ method too. Changing this
class Decorator:
def __init__(self, arg):
print(arg)
self.arg = arg
def __call__(self, cls):
print(cls)
class Wrapped(cls):
def __init__(self, first_arg, second_arg, **kwargs):
cls.__init__(self, first_arg, second_arg, **kwargs)
print('in wrapped init', self.variable)
return Wrapped
to this
class Decorator:
def __init__(self):
pass
def __call__(self, cls):
print(cls)
class Wrapped(cls):
def __init__(self, first_arg, second_arg, **kwargs):
cls.__init__(self, first_arg, second_arg, **kwargs)
print('in wrapped init', self.variable)
return Wrapped
will solve your problem.

Actually, this is how it works:
# Assume you have a decorator class and a class named A which is needed to decorate.
#Decorator
class A:
...
# is the same as
class A:
...
A = Decorator()(A)
That's why you need to define __cal__.
And here you can see that if your Decorator accepts some parameters to initialize, you need to use something like A = Decorator(xxx)(A). And the equivalent in decorator syntax is:
#Decorator(xxx)
class A:
...

Related

Pythonic way to decorate parent method in subclass

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)

Extending behavior of the property decorator

I would like to extend the behavior of the builtin #property decorator. The desired usage is shown in the code below:
class A:
def __init__(self):
self.xy = 42
#my_property(some_arg="some_value")
def x(self):
return self.xy
print(A().x) # should print 42
First of all, the decorator should retain the property behavior so that no () is needed after the x. Next, I would like to be able to access the arguments a programmer passes to my decorator.
I started off with this:
class my_property(property):
def __init__(self, fn):
super().__init__(fn)
TypeError: __init__() got an unexpected keyword argument 'some_arg'
After adding **kwargs:
class my_property(property):
def __init__(self, fn, **kwargs):
super().__init__(fn)
TypeError: __init__() missing 1 required positional argument: 'fn'
OK, let's do *args instead:
class my_property(property):
def __init__(self, *args, **kwargs):
super().__init__(*args)
TypeError: 'my_property' object is not callable
Let's make it callable:
class my_property(property):
def __init__(self, *args, **kwargs):
super().__init__(*args)
def __call__(self, *args, **kwargs):
pass
No errors, but prints None instead of 42
And now I am lost. I have not even yet managed to access `some_arg="some_value" and the property behavior seems to be already gone. What is wrong and how to fix it?
It's not clear how you intent to use some_arg, but to pass a parameter to a decorator you need to have "two layers" of decorators
#my_decorator(arg)
def foo():
return
under the hood this translates to my_decorator(arg)(foo) (i.e. my_decorator(arg) must return another decorator that is called with foo). The inner decorator in this case should be your custom implementation of property
def my_property(some_arg):
class inner(object):
def __init__(self, func):
print(some_arg) # do something with some_arg
self.func = func
def __get__(self, obj, type_=None):
return self.func(obj)
return inner
Now you can use it like this:
class MyClass:
def __init__(self, x):
self.x = x
#my_property('test!')
def foo(self):
return self.x
obj = MyClass(42) # > test!
obj.foo # > 42
Read more about descriptors here

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

Pre process arguments passed to all instance methods in Python

class SimpleClass(object):
def A(self, *args):
...
def B(self, *args):
...
def C(self, *args):
...
Is there anyway to catch the arguments passed to A, B, C, modify those arguments and pass the modified arguments onto A, B, C without having to modify the existing code ?
Edit:
Answer: use a class decorator, that decorates all methods of the class with another decorator.
The method decorator performs the pre-processing.
Try this:
from types import FunctionType
class SimpleClass(object):
def A(self, *args):
pass
def B(self, *args):
pass
def C(self, *args):
pass
def methods(cls):
"""List of all method in class."""
return [x for x, y in cls.__dict__.items() if type(y) == FunctionType]
def args_decorator(method):
"""Decorator for SimpleClass methods"""
def wrapper(self, *args):
new_args = list(args)
# change new_args here
return method(self, *new_args)
return wrapper
for method in methods(SimpleClass):
setattr(SimpleClass, method, args_decorator(SimpleClass.__dict__[method]))
Though if you can change your SimpleClass I'd suggest using decorator like so:
def args_decorator(method):
def wrapper(self, *args):
new_args = list(args)
# change new_args here
return method(self, *new_args)
return wrapper
class SimpleClass(object):
#args_decorator
def A(self, *args):
pass
#args_decorator
def B(self, *args):
pass
#args_decorator
def C(self, *args):
pass

Mixins, multi-inheritance, constructors, and data

I have a class:
class A(object):
def __init__(self, *args):
# impl
Also a "mixin", basically another class with some data and methods:
class Mixin(object):
def __init__(self):
self.data = []
def a_method(self):
# do something
Now I create a subclass of A with the mixin:
class AWithMixin(A, Mixin):
pass
My problem is that I want the constructors of A and Mixin both called. I considered giving AWithMixin a constructor of its own, in which the super was called, but the constructors of the super classes have different argument lists. What is the best resolution?
class A_1(object):
def __init__(self, *args, **kwargs):
print 'A_1 constructor'
super(A_1, self).__init__(*args, **kwargs)
class A_2(object):
def __init__(self, *args, **kwargs):
print 'A_2 constructor'
super(A_2, self).__init__(*args, **kwargs)
class B(A_1, A_2):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
print 'B constructor'
def main():
b = B()
return 0
if __name__ == '__main__':
main()
A_1 constructor
A_2 constructor
B constructor
I'm fairly new to OOP too, but what is the problem on this code:
class AWithMixin(A, Mixin):
def __init__(self, *args):
A.__init__(self, *args)
Mixin.__init__(self)

Categories