Overwrite class method from parent without affecting downstream classes - python

I would like to overwrite an inherited method in a class (see below example for __init__ method) while letting its children still use the Parents version.
I know that I could achieve the desired behaviour redefining the __init__ method in the GrandChild class or using multiple inheritance. However my question aims at a way to achieve the same with changes only to the Child class and its __init__ implementation.
(The actual use case is significantly more complex 'legacy code' with several classes on each level. The motivation of this question is therefore to achieve the desired behaviour in the respective class without having to touch the implementation of the other classes or the inheritance structure)
If this is impossible I would also appreciate an explanation to that effect.
class Parent:
def __init__(self, a,b):
self.a = a
self.b = b
def __str__(self):
return f"{self.a}, {self.b}"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a):
super().__init__(a, None)
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
# This throws a Type error right now since it calls the init method of class Child
grandchild = GrandChild("d", "e")
EDIT:
As mentioned above I am aware that I can achieve the desired behaviour in different ways such as changing the class structure (as below). However the question is really more about wether python allows doing it with changes only to the Child class. If this is actually impossible (not merely undesirable) in python, an explanation why would do more to answer my question than providing alternative implementations that change anything beyond the implementation of the Child class.
class ChildCommonFunctionality(Parent):
# Use this class for all common functionality originally provided by Child Class
pass
class Child(ChildCommonFunctionality):
# Use this class to override the init method
def __init__(self, a):
super().__init__(a, None)
class GrandChild(ChildCommonFunctionality):
# This Class should use the __init__ method of class Parent
pass

I have found a way using _init_subclass to make sure that all subclasses of Child use the constructor of Parent instead of the one defined in Child inspired by this post:
class Parent:
def __init__(self, a,b):
self.a = a
self.b = b
def __str__(self):
return f"{self.a}, {self.b}"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a):
super().__init__(a, None)
def __init_subclass__(cls):
cls.__init__ = super().__init__
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
Even though this is a bit hacky it provides the desired functionality of actually bypassing Childs init method

You could do :
class Parent:
def __init__(self, a, b = None):
self.a = a
self.b = b
def __str__(self):
return f"{self.a}, {self.b}"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a, b = None):
super().__init__(a, b) # Or None instead of b... but that's not good when called by GrandChild
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
grandchild = GrandChild("d", "e")
EDIT : you could also replace the optional parameter by a mandatory one in GrandChild :
class GrandChild(Child):
def __init__(self, a, b):
super().__init__(a, b)

This code might do the trick, adding a few lines to the suggestion of #dspr:
class Parent:
def __init__(self, a, b = None):
self.a = a
self.b = b
def __str__(self):
return f"{self.a}, {self.b}"
class Child(Parent):
# I would like to overwrite this method only for the Child Class and none of its children / downstream inhertiances
def __init__(self, a, b = None):
if type(self) == Child:
if b is not None:
raise ValueError(
"Second argument is not allowed for direct use in Child class")
super().__init__(a, None) #Or (a, b) if you trust b to be None as it is here
else:
super().__init__(a, b)
class GrandChild(Child):
# This Class should use the __init__ method of class Parent
pass
parent = Parent("a","b")
child = Child("c")
print(child.b) # None
grandchild = GrandChild("d", "e")
print(grandchild.b) # e
child = Child("f", "g")
print(child.b) # ValueError

Related

Calling super() without a base class

I am working through the O Reilly Python Cookbook and have been struggling with the below code. It is to with calling a method on a parent class using super():
class Proxy:
def __init__(self, obj):
self._obj = obj
# Delegate attribute lookup to internal obj
def __getattr__(self, name):
return getattr(self._obj, name)
# Delegate attribute assignment
def __setattr__(self, name, value):
if name.startswith('_'):
super().__setattr__(name, value) # Call original __setattr__
else:
setattr(self._obj, name, value)
if __name__ == '__main__':
class A:
def __init__(self, x):
self.x = x
def spam(self):
print('A.spam')
a = A(42)
p = Proxy(a)
print(p.x)
print(p.spam())
p.x = 37
print('Should be 37:', p.x)
print('Should be 37:', a.x)
The book states:
In this code the implementation of __setatrr__() includes a name
check. If the name starts with an underscore it invokes the original
implementation of __setattr__() using super(). Otherwise, it delegates
to the internally held object self._obj.
I am confused. How does super() work then if there is no explicit base class listed?
What exactly then is super() referring to?
There is always a base class; with none explicitly mentioned, Proxy inherits directly from object.
Each class defines a method-resolution order, determined recursively by its base class(es) and its ancestors. When super() gets called, it resolves to a "proxy" of the next class in the MRO of self, whether or not that class appears in the MRO of the class you are currently defining.
Consider the following classes:
class A:
def foo(self):
print("A.foo")
class B(A):
def foo(self):
super().foo()
print("B.foo")
class C(A):
def foo(self):
super().foo()
print("C.foo")
class D(C):
def foo(self):
super().foo()
print("D.foo")
class E(B,D):
def foo(self):
super().foo()
print("E.foo")
e = E()
The MRO of E is [E, B, D, C, A, object]. When you call e.foo(), you start a chain of calls in MRO order. In particular, the call to super in B.foo does not invoke A.foo, but D.foo, a method in a class B knows nothing about, as D is not an ancestor of B. But both B and D are ancestors of E, which is what matters.

The init values did not inherited to another class with super()

I built 2 classes. first class with its own init value and another class will inherit the init values from the first class. I am wondering whether I understand it correctly of the usage of super().
class testing(testing_2):
def __init__(self, name, c):
super().__init__(name, c)
def check(self):
print(super().name)
class testing_2:
def __init__(self, name, c):
self.name = name
self.c = c
tt = testing("tester", "check")
tt.check()
I thought my code was supposedly printed the "tester" because I initialize the testing class with name and c. since testing class inherit from testing_2 so we can just print the name. Am I confusing something?
My expectation is:
testing_2 will take the value from testing and we can print the values of testing_2 in testing.
Simplify it:
class A:
def __init__(self, name, c):
self.name = name
self.c = c
class B(A):
def check(self):
print(self.name)
tt = B("tester", "check")
tt.check()
The B object will have all the same things as an A object, since it inherits them. No need to implement __init__ on B if it doesn't do anything useful. You can access self.name directly, just as you would within A. The object that has that property is self. It's set on self and you can access it with self.
Remember, self is the object instance, not the class. When doing B(...), the self in A.__init__(self, ...) is actually an instance of B.
You only need to explicitly use super if you are overriding parent methods, like in:
def __init__(self, name, c):
super().__init__(name, c)
Here __init__ is overridden, and in order to execute the parent's __init__ you need to access it through super. Just self.__init__(name, c) would access the child's __init__ method and you'd call it in an endless recursive loop.

Only declare variable if doesn't exist yet in the initialization of a class

I'm working for the first time with OOP in a python project. I have 3 classes: PRA, GDB and XLS.
PRA = Main Class
GDB = Responsible for control databases
XLS = Responsible for control xls(x)
UserInput = Responsible for validate user input
How i'm doing:
PRA:
class PRA(GDB, XLS, UserInput):
__init__():
self.gdb_file, self.xls_file = self.ask_questions() # <--- ask_questions is inside the UserInput class.
self.do_something()
XLS:
class XLS:
do_something():
print(self.xls_file)
__init__(self, xls_file):
self.xls_file = xls_file
GDB:
class GDB:
__init__(self, gdb_file):
self.gdb_file = gdb_file
I would like to know if is a bad practice to initialize the gdb_file and xls_file inside the PRA.init, and if not, how can i run the initialization of self.xls_file and self.xls_gdb only in the PRA?
This is not really an answer, but more of an example of code reuse. I'm not sure if this is super Pythonic, though. Anyhow, take a look at this example:
import inspect
class A:
def __init__(self):
self.a = 1
class B:
def __init__(self):
self.b = 2
class C(A, B):
def __init__(self):
for super_class in inspect.getmro(type(self))[1:]:
super_class.__init__(self)
c = C()
print(c.a, c.b)
A bit OT but you may want to think twice about your class names... They are really terrible.
Now wrt/ your question... Technically, the way to handle proper initialisation of a parent class is to call it's initialiser from the child class, ie:
class Parent(object):
def __init__(self, name):
self.name = name
class Child(Parent):
def __init__(self, name, age):
super(Child, self).__init__(name)
self.age = age
You can also explicitly name the parent class, ie:
class Child(Parent):
def __init__(self, name, age):
Parent.__init__(self, name)
self.age = age
but it has a couple drawbacks, like harcoding the parent class in the call (so you have two or more places to edit if you change the parent class), and, more important, not taking care of proper resolution of the "diamond" problem in multiple inheritance (ie D child of B & C, B child of A, C child of A).
Actually, the only reason to not use super() would be when parent classes constructor are not compatible, but this is a huge design smell - if you have two or more parent classes that are not compatible, then you're certainly not using inheritance the right way. And that's actually the case in your example: your "main" class "is" not a "Database controller", it uses a Database controller instead, so you want to use composition, not inheritance
class ServiceA(object):
# code here
class ServiceB(object):
# code here
class Main(object):
def __init__(self, arg1, arg2, arg3):
self.arg1 = arg1
self.service_a = ServiceA(arg2)
self.service_b = ServiceB(arg3)
def run(self):
something = self.service_a.ask(question)
self.service_b.do_something_with(something)
Also you may want to avoid user interactions and costly resource acquisitions in constructors... Better use an entry point method for the first case (like app = MyApp(params); app.run()`) and lazy initialization for the second case (wait until you need the resource to acquire it, use a private attribute as cache an a public property that will take care of acquisition on first access).
[edit]
Someone suggested a dirty hack based on introspection, ie:
import inspect
class A:
def __init__(self):
self.a = 1
class B:
def __init__(self):
self.b = 2
class C(A, B):
def __init__(self):
for super_class in inspect.getmro(type(self))[1:]:
super_class.__init__(self)
c = C()
print(c.a, c.b)
This is definitly not something to do.
If all your parent classes have compatible constructors and properly use super calls, then all you have to do is using super instead:
class A(object):
def __init__(self):
super(A, self).__init__()
self.a = "a"
class B(object):
def __init__(self):
super(B, self).__init__()
self.b = "b"
class C(A, B):
def __init__(self):
super(C, self).__init__()
self.c = "c"
c = C()
print c.a, c.b, c.c
This also takes care of the "diamond inheritance" problem by calling parent classes in the right order:
class D(C, A):
def __init__(self):
super(D, self).__init__()
self.d = "d"
d = D()
print "d : ", d.a, d.b, d.c, d.d
Note that unless only one of the parent classes is a "proper" class (with state) and all other are mixin classes (stateless classes that only add functionalities), multiple inheritance is more often than not a design smell (as well as a maintainance hell). Since Python doesn't use static typing, inheritance is mostly used for implementation inheritance, and for a lot of cases composition/delegation (or just plain composition as in the OP case) is a better solution than implementation inheritance.

Pass a parent class as an argument?

Is it possible to leave a parent class unspecified until an instance is created?
e.g. something like this:
class SomeParentClass:
# something
class Child(unspecifiedParentClass):
# something
instance = Child(SomeParentClass)
This obviously does not work. But is it possible to do this somehow?
You can change the class of an instance in the class' __init__() method:
class Child(object):
def __init__(self, baseclass):
self.__class__ = type(self.__class__.__name__,
(baseclass, object),
dict(self.__class__.__dict__))
super(self.__class__, self).__init__()
print 'initializing Child instance'
# continue with Child class' initialization...
class SomeParentClass(object):
def __init__(self):
print 'initializing SomeParentClass instance'
def hello(self):
print 'in SomeParentClass.hello()'
c = Child(SomeParentClass)
c.hello()
Output:
initializing SomeParentClass instance
initializing Child instance
in SomeParentClass.hello()
Have you tried something like this?
class SomeParentClass(object):
# ...
pass
def Child(parent):
class Child(parent):
# ...
pass
return Child()
instance = Child(SomeParentClass)
In Python 2.x, also be sure to include object as the parent class's superclass, to use new-style classes.
You can dynamically change base classes at runtime. Such as:
class SomeParentClass:
# something
class Child():
# something
def change_base_clase(base_class):
return type('Child', (base_class, object), dict(Child.__dict__))()
instance = change_base_clase(SomeParentClass)
For example:
class Base_1:
def hello(self):
print('hello_1')
class Base_2:
def hello(self):
print('hello_2')
class Child:pass
def add_base(base):
return type('Child', (base, object), dict(Child.__dict__))()
# if you want change the Child class, just:
def change_base(base):
global Child
Child = type('Child', (base, object), dict(Child.__dict__))
def main():
c1 = add_base(Base_1)
c2 = add_base(Base_2)
c1.hello()
c2.hello()
main()
Result:
hello_1
hello_2
Works well in both python 2 and 3.
For more information, see the related question How to dynamically change base class of instances at runtime?

Python: How to register all child classes with the father class upon creation

I have python class trees, each made up of an abstract base class and many deriving concrete classes. I want all concrete classes to be accessible through a base-class method, and I do not want to specify anything during child-class creation.
This is what my imagined solution looks like:
class BaseClassA(object):
# <some magic code around here>
#classmethod
def getConcreteClasses(cls):
# <some magic related code here>
class ConcreteClassA1(BaseClassA):
# no magic-related code here
class ConcreteClassA2(BaseClassA):
# no magic-related code here
As much as possible, I'd prefer to write the "magic" once as a sort of design pattern. I want to be able to apply it to different class trees in different scenarios (i.e. add a similar tree with "BaseClassB" and its concrete classes).
Thanks Internet!
you can use meta classes for that:
class AutoRegister(type):
def __new__(mcs, name, bases, classdict):
new_cls = type.__new__(mcs, name, bases, classdict)
#print mcs, name, bases, classdict
for b in bases:
if hasattr(b, 'register_subclass'):
b.register_subclass(new_cls)
return new_cls
class AbstractClassA(object):
__metaclass__ = AutoRegister
_subclasses = []
#classmethod
def register_subclass(klass, cls):
klass._subclasses.append(cls)
#classmethod
def get_concrete_classes(klass):
return klass._subclasses
class ConcreteClassA1(AbstractClassA):
pass
class ConcreteClassA2(AbstractClassA):
pass
class ConcreteClassA3(ConcreteClassA2):
pass
print AbstractClassA.get_concrete_classes()
I'm personnaly very wary of this kind of magic. Don't put too much of this in your code.
Here is a simple solution using modern python's (3.6+) __init__subclass__ defined in PEP 487. It allows you to avoid using a meta-class.
class BaseClassA(object):
_subclasses = []
#classmethod
def get_concrete_classes(cls):
return list(cls._subclasses)
def __init_subclass__(cls):
BaseClassA._subclasses.append(cls)
class ConcreteClassA1(BaseClassA):
pass # no magic-related code here
class ConcreteClassA2(BaseClassA):
pass # no magic-related code here
print(BaseClassA.get_concrete_classes())
You should know that part of the answer you're looking for is built-in. New-style classes automatically keep a weak reference to all of their child classes which can be accessed with the __subclasses__ method:
#classmethod
def getConcreteClasses(cls):
return cls.__subclasses__()
This won't return sub-sub-classes. If you need those, you can create a recursive generator to get them all:
#classmethod
def getConcreteClasses(cls):
for c in cls.__subclasses__():
yield c
for c2 in c.getConcreteClasses():
yield c2
Another way to do this, with a decorator, if your subclasses are either not defining __init__ or are calling their parent's __init__:
def lister(cls):
cls.classes = list()
cls._init = cls.__init__
def init(self, *args, **kwargs):
cls = self.__class__
if cls not in cls.classes:
cls.classes.append(cls)
cls._init(self, *args, **kwargs)
cls.__init__ = init
#classmethod
def getclasses(cls):
return cls.classes
cls.getclasses = getclasses
return cls
#lister
class A(object): pass
class B(A): pass
class C(A):
def __init__(self):
super(C, self).__init__()
b = B()
c = C()
c2 = C()
print 'Classes:', c.getclasses()
It will work whether or not the base class defines __init__.

Categories