I have the following code:
class Parent:
def __init__(self) -> None:
print(f"init Parent")
class Child(Parent):
def __init__(self) -> None:
super().__init__()
print(f"init Child")
def post_init(self):
print(f"post init")
Child()
Here I get the output:
init Parent
init Child
Is it possible to modify the code in the Parent-class to call the post_init method from the Child-class automatically after the ___init___() of the Parent and the Child-class. I would like to have the following output:
init Parent
init Child
post init
I don't want to modify the Child-class!!!
I have already tried to define a decorator in the Parent-class, but I failed.
EDIT:
The Parent-class is a pattern-class and is multiple used in multiple children. So I don't want to take care every time i use the Child-class. Is it possible on using the abstract-package?
Using __init_subclass__ to override the __init__:
class Parent:
def __init__(self):
print(f"init Parent")
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
def new_init(self, *args, init=cls.__init__, **kwargs):
init(self, *args, **kwargs)
self.post_init()
cls.__init__ = new_init
Supporting grandchildren
To only call after a grandchild class's __init__, we need an additional check to see if the __init__ being called is that of the grandchild's:
class Parent:
def __init__(self):
print("init Parent")
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
def new_init(self, *args, init=cls.__init__, **kwargs):
init(self, *args, **kwargs)
if cls is type(self):
self.post_init()
cls.__init__ = new_init
Example usage:
class Child(Parent):
def __init__(self):
super().__init__()
print("init Child")
def post_init(self):
print("post init")
class Grandchild(Child):
def __init__(self):
super().__init__()
print("init Grandchild")
_ = Grandchild()
Output:
init Parent
init Child
init Grandchild
post init
You can also write code like this:
class Meta(type):
def __call__(cls, *args, **kwargs):
instance = super().__call__(*args, **kwargs)
instance.post_init()
return instance
class Parent(metaclass=Meta):
def __init__(self) -> None:
print(f"init Parent")
class Child(Parent):
def __init__(self) -> None:
super().__init__()
print(f"init Child")
def post_init(self):
print(f"post init")
Child()
Related
How to make it sure in a parent class method that super is called in its children methods which override the parent method? I found this question in SO for other languages except for Python.
To the best of my knowledge, there is no way to enforce that a child class method must call the parent class method when it overrides it.
Nevertheless, a workaround could be to use a decorator calling the overriden method.
Based on this answer,
import inspect
def get_class_that_defined_method(meth):
if inspect.ismethod(meth):
for cls in inspect.getmro(meth.__self__.__class__):
if meth.__name__ in cls.__dict__:
return cls
if inspect.isfunction(meth):
return getattr(inspect.getmodule(meth),
meth.__qualname__.split('.<locals>', 1)[0].rsplit('.', 1)[0],
None)
return None
def override(func):
def wrapper(*args, **kwargs):
cls = get_class_that_defined_method(func)
if hasattr(cls.__base__, func.__name__):
getattr(cls.__base__, func.__name__)(*args, **kwargs)
return func(*args, **kwargs)
return wrapper
This can be used as follows.
class A:
def method(self):
print("call A.method")
class B(A):
#override
def method(self):
print("call B.method")
B().method()
# call A.method
# call B.method
This can be achieved by using decorator and metaclass
import inspect
def my_decorator(func):
def wrapper(self, *args, **kwargs):
has_super = False
for line in inspect.getsource(func).split('\n'):
if line.strip().startswith("super"):
has_super = True
break
if not has_super:
super_method = getattr(super(self.__class__, self), func.__name__, None)
if super_method:
super_method(*args, **kwargs)
func(self, *args, **kwargs)
return wrapper
class MyMeta(type):
def __new__(cls, name, bases, local):
for attr in local:
if callable(local[attr]):
local[attr] = my_decorator(local[attr])
return super().__new__(cls, name, bases, local)
class BaseClass:
def __init__(self):
print("BaseClass __init__")
def method1(self):
print("BaseClass method1")
def method2(self):
print("BaseClass method2")
class Child(BaseClass, metaclass=MyMeta):
def __init__(self):
print("Child __init___")
def method1(self):
print("Child method1")
def method2(self):
print("Child method2")
super().method2()
if __name__=="__main__":
a = Child()
a.method1()
a.method2()
Output
BaseClass __init__
Child __init___
BaseClass method1
Child method1
Child method2
BaseClass method2
While you can not force the call to the superclass, what you can do is force the call of one function when ANOTHER one is overridden:
class Parent:
def other_method(self):
# code that would be in overridden function should be placed here
print("Hello from parent!")
def method(self):
pass
def run_method(self):
self.other_method()
self.method()
class Child(Parent):
def method(self):
print("Hello from child!")
myChild= Child()
# calling method() here will not have the desired effect, one needs to call run_method()
myChild.run_method()
This will produce following output:
Hello from parent!
Hello from child!
The child method is overriding method() without calling super().
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:
...
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
For example I have something like this:
class A(object):
def __init__(self):
pass
def foo(self, a, b, c):
return a + b + c
class B(object):
def __init__(self):
self.b = A()
def wrapper_func(func):
def wrapper(self, *args, **kwargs):
return func(self, a=3, *args, **kwargs)
return wrapper
class C(B):
def __init__(self):
pass
#wrapper_func
def ???
Is it possible to some how overload and then wrap method foo of the field of parent B class in python without inherits from class A? I need the wrapper indeed because I have the different methods with same arguments, but in the same time I have to save original class B methods native (besides overloading).
Initialize C's parent class using super and then pass all the parameters to the foo method of the composed class instance A() via the inherited attribute b of the class C:
def wrapper_func(func):
def wrapper(self, *args, **kwargs):
kwargs['a'] = 3
return func(self, *args, **kwargs)
return wrapper
class C(B):
def __init__(self):
super(C, self).__init__()
#wrapper_func
def bar(self, *args, **kwargs):
return self.b.foo(*args, **kwargs) # access foo via attribute b
Trial:
c = C()
print(c.bar(a=1, b=2, c=3))
# 8 -> 3+2+3
To make the call to the decorated function via c.b.foo, patch the c.b.foo method with the new bar method:
class C(B):
def __init__(self):
super(C, self).__init__()
self._b_foo = self.b.foo
self.b.foo = self.bar
#wrapper_func
def bar(self, *args, **kwargs):
return self._b_foo(*args, **kwargs)
Trial:
c = C()
print(c.b.foo(a=1, b=2, c=3))
# 8 -> 3+2+3
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)