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
Related
I have a scheme of cooperative classes based on collection.abc. When I subclass them, I want to be able to define just a couple of class attributes that then become the default values at instantiation, like so:
class MyFancyClass:
# Defines various attributes, as class attributes and/or in the
# __init__ method
def __init__(self, a=1, b=1):
self.a = a
self.b = b
class A(myFancyClass):
# Instances of A should have these values, even if they override
# a value set in MyFancyClass's __init__ method:
a = 2
b = 2
c = SomeHelperClass
Currently, in the __init__ of FancyClass, I do:
def __init__(self, *args, **kwargs):
for k, v in vars(type(self)).items():
if k.startswith("_"):
continue
if k not in kwargs:
kwargs[k] = v
super().__init__(*args, **kwargs)
That works fine, but if I make a class B that is a subclass of A, I lose those values defined for A, and I want to keep them.
So playing around, I got stuck here...
class InitExtras:
def __init__(self, *args, **kwargs):
for cls in type(self).__mro__:
if cls == InitExtras:
break
for k, v in vars(cls).items():
if k.startswith("_") or callable(v):
continue
if k not in kwargs:
print(f"adding\n{k=}\n{v=}\n")
kwargs[k] = v
super().__init__(*args, **kwargs)
class Base:
def __init__(self, *args, **kwargs):
print(f"{args = }")
print(f"{kwargs = }")
class A(Base):
def fun1(self):
pass
class B(A):
def fun2(self):
pass
#property
def b(self):
return self._b
#b.setter
def b(self, value):
self._b = value
def __init__(self, *args, b=23, b2=32, **kwargs):
super().__init__(*args, **kwargs)
self.b = b
self.b2 = b2
class C(InitExtras, B):
b = 42
class D(C):
b2 = 420
class T:
pass
class E(C):
b2 = T
def fun3(self):
pass
This seem to do most of what I want, except that E().b2 is 32, not T. And if I remove the callable() filter, other stuff can get mixed in too, like extra functionalities one might define later to personalize classes even further if needed (fun3 in the example). I don't want to need to do a new __init__ each time.
So my question is, how to accomplish that?
I could solve it, I did by making a metaclass, and to distinguish between different class attributes I limit it to just properties
abc_recipes.py
from abc import ABCMeta, ABC, abstractmethod
class PropertyConfigMeta(ABCMeta):
def __new__(mcls, name, bases, namespace, /, **kwargs):
#list the properties that the new class would inherit
properties = {p for bcls in bases
for cls in bcls.__mro__
for p,v in vars(cls).items()
if isinstance(v,property)
}
#proceed to extract the attributes that would
#overwrite the properties inherited by non-property
new_default={}
new_namespace = {}
for k,v in namespace.items():
if k in properties:
if isinstance(v,property):
new_namespace[k] = v
else:
new_default[k] = v
else:
new_namespace[k] = v
cls = super().__new__(mcls, name, bases, new_namespace, **kwargs)
if hasattr(cls,"_new_default"):
cls._new_default = {**cls._new_default, **new_default}
else:
cls._new_default = new_default
return cls
class PropertyConfig(metaclass=PropertyConfigMeta):
"""cooperative class that transform
class A(SomeClass):
a = 1
b = 2
into
class A(SomeClass):
def __init__(self, *arg, a = 1, b = 2, **karg):
super().__init__(*arg, a = a, b = b, **karg)
so long as a and b are defined as properties in SomeClass
(or somewhere in the inheritance chain)
class SomeClass:
#property
def a(self):
...
#property
def b(self):
...
Use as
class A(PropertyConfig, SomeClass):
a = 1
b = 2
"""
def __init__(self,*arg,**kwargs):
for k,v in self._new_default.items():
if k not in kwargs:
kwargs[k]=v
super().__init__(*arg,**kwargs)
class ConfigClass(ABC):
"""Cooperative class that offer a default __repr__ method
based on the abstract property .config"""
#property
#abstractmethod
def config(self) -> dict:
"""configuration of this class"""
return {}
def __repr__(self):
return f"{type(self).__name__}({', '.join( f'{k}={v!r}' for k,v in self.config.items() )})"
sample use
import abc_recipes
class Base:
def __init__(self,*arg,**karg):
if arg:
print(f"{arg=}")
if karg:
print(f"{karg=}")
class A(Base):
pass
class B(abc_recipes.ConfigClass,A):
def __init__(self,*a, b=23, b2=32, **k):
super().__init__(*a,**k)
self.b = b
self.b2 = b2
#property
def b(self):
"b attribute"
#print("b getter")
return self._b
#b.setter
def b(self,v):
#print("b setter")
self._b=v
#property
def b2(self):
"b2 atrribute"
#print("b2 getter")
return self._b2
#b2.setter
def b2(self,v):
#print("b2 setter")
self._b2=v
#property
def config(self) -> dict:
"""configuration of this class"""
res = super().config
res.update(b=self.b, b2=self.b2)
return res
class C(abc_recipes.PropertyConfig,B):
b=42
pass
class D(C):
b2=420
pass
class T:
pass
class E(C):
b2 = T
pi = 3.14
class F(E):
#property
def b2(self):
#print("rewriten b2 getter")
return "rewriten b2"
#b2.setter
def b2(self, value):
#print("rewriten b2 setter")
pass
test
>>> F()
F(b=42, b2='rewriten b2')
>>> E()
E(b=42, b2=<class '__main__.T'>)
>>> D()
D(b=42, b2=420)
>>> C()
C(b=42, b2=32)
>>> B()
B(b=23, b2=32)
>>> e=E()
>>> e.pi
3.14
>>> f=F()
>>> f.pi
3.14
>>>
I have a class hierarchy - which may be multiple inheritance, for example:
class Base:
def __init__(self):
print("Base")
class A(Base):
def __init__(self):
super().__init__()
print("A")
class B(Base):
def __init__(self):
super().__init__()
print("B")
class C(A, B):
def __init__(self):
super().__init__()
print("C")
x = C()
will print
Base
B
A
C
as expected.
what I'd like to do is somehow print Done after this is all finished, by defining a method or similar in Base that will be called when C.__init__() has completed.
What I can't do:
Put code in Base.__init__ as that gets called too early.
Put code in C.__init__ as I want this to apply to all potential subclasses
I've tried various things with metaclasses and haven't managed to make anything work yet - any clever ideas?
The call to __init__ is handled by the call to type.__call__, once __new__ has returned. You could define a metaclass that overrides __call__ to handle your post-init code; I believe this is the only way to avoid having the code be called prematurely whenever you subclass, since __call__ won't be invoked for or by any of the ancestor classes.
class PostInitHook(type):
def __call__(cls, *args, **kwargs):
rv = super().__call__(*args, **kwargs)
print("Done")
return rv
class Base(metaclass=PostInitHook):
def __init__(self):
print("Base")
...
More generally, you could replace print("Done") a class specific hook, for example,
class PostInitHook(type):
def __new__(metacls, *args, **kwargs):
cls = super().__new__(metacls, *args, **kwargs)
try:
cls._post_init
except AttributeError:
raise TypeError("Failed to define _post_init")
return cls
def __call__(cls, *args, **kwargs):
rv = super().__call__(*args, **kwargs)
rv._post_init()
return rv
class Base(metaclass=PostInitHook):
def __init__(self):
print("Base")
def _post_init(self):
print("Done")
Then
>>> a = A()
Base
A
Done
>>> b = B()
Base
B
Done
>>> c = C()
Base
B
A
C
Done
>>> class D(C):
... def __init__(self):
... super().__init__()
... print("D")
...
>>> d = D()
Base
B
A
C
D
Done
>>>
Let's say class A has 10 methods. Some of the methods are private and it has private attributes as well. I want to create class B so I can change last method only without duplicating the code for the rest of the methods. My example is below. At the moment I am unable to achieve it with such inheritance as I get AttributeError: 'B' object has no attribute '_B__c'
class A:
def __init__(self, a=1, b=2):
self.a = a
self.b = b
self.__foo()
def __foo(self):
self.__c = self.a + self.b
def get_data(self):
return self.__c
class B(A):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.__c = self.__modify_data()
def __modify_data(self):
self.__c += 10000
def get_data(self):
return self.__c
b = B(a=5, b=10).get_data()
Question 2:
Can I achieve it with use of *args so I do not have to repeat all the arguments?
EDIT:
Please see my updated code above.
I believe private attributes causes the problem.
Can I solve it with still using private?
class A(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
self.__foo()
def __foo(self):
self._c = self.a + self.b
def get_data(self):
return self._c
class B(A):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.__modify_data()
def __modify_data(self):
self._c += 10000
b = B(a=5, b=10).get_data()
print(b)
Output:
10015
Changing _c to __c gives AttributeError: 'B' object has no attribute '_B__c'
Yes, the __ is causing the trouble by making variable c inaccessible in children, which is good because the private variable of parents should not be allowed to edit by the children class.
This question already has answers here:
Python: Correct way to initialize when superclasses accept different arguments?
(2 answers)
Closed 9 years ago.
What is the best way to deal with an inheritance structure like this:
class A(object):
def __init__(self):
print('A')
class B(A):
def __init__(self, foo):
super(B, self).__init__()
self.foo = foo
print('B')
class C(A):
def __init__(self, bar):
super(C, self).__init__()
self.bar = bar
print('C')
class D(B, C):
def __init__(self, foo, bar):
super(D, self).__init__(foo, bar)
Essentially, I want to be able to call:
>>> d = D('bar', 'ram ewe')
>>> d.foo
'bar'
>>> d.bar
'ram ewe'
Currently, the super(D, self).__init__(foo, bar) raises TypeError: __init__() takes exactly 2 arguments (3 given)
EDIT
Working answer, thanks to Daniel Roseman.
class A(object):
def __init__(self, *args, **kwargs):
print('A')
class B(A):
def __init__(self, foo, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.foo = foo
print('B')
class C(A):
def __init__(self, bar, *args, **kwargs):
super(C, self).__init__(*args, **kwargs)
self.bar = bar
print('C')
class D(B, C):
def __init__(self, foo, bar, *args, **kwargs):
super(D, self).__init__(foo, bar, *args, **kwargs)
The best way is to always ensure the methods are both defined and called using the *args, **kwargs syntax. That means they will get the parameters they need and ignore the rest.
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)