Better inheritance when calling grandparent method - python

In a python program, I implemented some class A, and a subclass B which extends some of A's methods and implements new ones. So it's something that looks like this.
class A:
def method(self):
print("This is A's method.")
class B(A):
def method(self):
super().method()
print("This is B's method.")
def another_method(self):
pass
Later, I wanted to use an object which would have access to all of B's methods, except for a small change in method: this object would have to first call A.method, and then do other things, different from what I added in B.method. I thought it was natural to introduce a class C which would inherit from B but modify method, so I defined C like this.
class C(B):
def method(self):
super(B, self).method()
print("This is C's method.")
Everything seems to work as I expect. However, I stumbled across this question and this one, which both address similar problems as what I described here. In both posts, someone quickly added a comment to say that calling a grandparent method in a child when the parent has overridden this method is a sign that there is something wrong with the inheritance design.
How should I have coded this? What would be a better design? Of course this is a toy example and I guess the answer might depend on the actual classes I defined. To give a more complete picture, let's say the method method from the example represents a single method in my actual program, but the method another_method represents many different methods.

Related

Trying to understand the fundamental difference on something simple here with python inheritance vs i don't even know what to call this

I am trying to understand multiple inheritance vs this other thing i am doing.
I don't even know what to call it. First let me explain this other thing. What is this even called? It seems to work really well without giving me a headache unlike inheritance.
class my_pets():
def __init__():
pass
self.mycat = cats()
class cats():
def __init__():
self.cats_name = bruno
So i dont know what that is called but it works well. I have been calling it a sub_objected incorrectly in my head. I know that is not inheritance.
I am utterly mystified with inheritance. Mainly the when to use it verse when to do what i did above. If i wrote the above with inheritance i would of done.
class my_pets(cats):
def __init__():
super(cats,self).__init__(cats_name)
class cats():
def __init__(cats_name):
self.cats_name = cats_name
So i don't understand the difference and when to do one or the other.
Can someone even tell me what this is called?
I appreciate the suggested thread and i had seen that previously. So that post is specific to UML drawings. Is composition what it is called then? That seems very specific to UML
Also, the answer still baffles me
If you google any search on inheritance all the examples lead to something that can be easily done with this other approach. The typical examples.
Mom, dad , child
pet, cat , dog etc
Its called super function which is used to give access to the methods of a parent class .Returns a temporary object of a parent class when used .its is mostly used where we overriding (a class drive from another class) like in your code catsis a parent class of a my catstake a look in my example:
class pa_rent:
classvar1 = "I am a class variable in class A"
def __init__(self):
self.var1 = "I am inside class A's constructor"
self.classvar1 = "Instance var in class A"
self.special = "Special"
class Child(pa_rent):
classvar1 = "I am in class B"
def __init__(self):
self.var1 = "I am inside class B's constructor"
self.classvar1 = "Instance var in class B"
super().__init__()
print(super().classvar1)
a = A()
b = B()
print(b.special, b.var1, b.classvar1)
so when we call classvar1, interpreter first checks in instance child'schild`` variable if its not found then its checks in parent classpa_rent`` instance variable .but in this case we have override and when we call callssvar1 its gives Instance var in class B so, in taht case if we want parent class variable classvar1 super function super().__init__ will be used,
I hope its enough for your understanding

Python: Call child method inside parent method from child method

I want to be able to recycle a method from a parent class that uses a second method in that parent class. However the second method is overridden in the child class. I want the recycled parent method to use the new overridden second method when it is called from the child class. An example of how I want it to work will hopefully make this clearer:
class Parent:
def method1(self, num):
return num**2
def method2(self, list_size):
return [self.method1(i) for i in range(list_size)] #List of squares
class Child(Parent):
def method1(self, num): #Overrides corresponding parent method
return num**3
def method2(self, list_size):
return super().method2(list_size) #Returns a list of cubes using child's method 1.
Is this possible in python3? Or will calling the parent's method 2 also use the parent's method 1? I'm hoping to reuse large parts of a parent class as the child class differs in only a few ways. The methods nesting like that in the parent class make it a lot more general.
Thanks!
EDIT: I forgot to test it with simple code! It does work like I wanted if anyone was wondering!
Short answer: yes.
Just tried a slightly modified version of your code with prints.
class Parent:
def method1(self):
print("Parent method1")
def method2(self):
print("Parent method2")
self.method1()
class Child(Parent):
def method1(self):
print("Child method1")
def method2(self):
print("Child method2")
super().method2()
c = Child()
c.method2()
This is the output:
Child method2
Parent method2
Child method1
As you can see, the method1 used is the child one.
Yes, this works the way you want it to.
You can easily test this yourself. Unless you pass in nothing but 0s and 1s, it should be pretty obvious whether they're getting squared or cubed.
And, in cases where it's less obvious, just add a debugger breakpoint to Child.method1 and Parent.method1 and see which one gets hit. Or add print(f'Child1.method({self}, {num})') to the method and see if it gets printed out.
If you're coming from another language with C++ OO semantics instead of Smalltalk OO semantics, it may help to think of it this way: Every method is always virtual.
Are __init__ calls virtual? Yes.
What if you call a method during __init__? Yes.
What if you call a method inside a super call? Yes.
What about a #classmethod? Yes.
What if…? Yes.
The only exceptions are when you go out of your way to explicitly tell Python not to make a virtual function call:
Calls on super() use the implementation from the next class in the MRO chain, because that's the whole point of super.
If you grab a parent's bound method and call that, like Parent.method1(self, num), you obviously get Parent.method1, because that's the whole point of bound methods.
If you go digging into the class dicts and run the descriptor protocol manually, you obviously get whatever you do manually.
If you're not trying to understand Python in terms of Java, and just want a deeper understanding of Python on its own terms, what you need to understand is what happens when you call self.method1(i).
First, self.method1 doesn't know or care that you're going to call it. It's an attribute lookup, just like, say, self.name would be.
The way Python resolves this is described in the Descriptor HOWTO, but an oversimplified version looks like this:
Does self.__dict__ have anything named method1? No.
Does type(self).__dict__ have anything named method1? Yes.
Return type(self).__dict__['method1'].__get__(self).
If that second lookup failed, Python would loop over type(self).mro() and do the same test for each one. But here, that doesn't come up. type(self) is always going to be Child for an instance of Child, and Child.__dict__['method1'] exists, so it binds Child.method to self, and the result is what self.method1 means.

Python multiple inheritance, calling second base class method, if both base classes holding same method

class A:
def amethod(self): print("Base1")
class B():
def amethod(self): print("Base3")
class Derived(A,B):
pass
instance = Derived()
instance.amethod()
#Now i want to call B method amethod().. please let me know the way.**
Python multiple inheritance, calling second base class method, if both base classes holding same method
try to use composition
+Avoid multiple inheritance at all costs, as it's too complex to be reliable. If you're stuck with it, then be prepared to know the class hierarchy and spend time finding where everything is coming from.
+Use composition to package code into modules that are used in many different unrelated places and situations.
+Use inheritance only when there are clearly related reusable pieces of code that fit under a single common concept or if you have to because of something you're using.
class A:
def amethod(self): print("Base1")
class B:
def amethod(self): print("Base3")
class Derived2:
def __init__(self):
self.a = A()
self.b = B()
def amthodBase1(self):
self.a.amethod()
def amthodBase3(self):
self.b.amethod()
instance2 = Derived2()
instance2.amthodBase1()
instance2.amthodBase3()
galaxyan's answer suggesting composition is probably the best one. Multiple inheritance is often complicated to design and debug, and unless you know what you're doing, it can be difficult to get right. But if you really do want it, here's an answer explaining how you can make it work:
For multiple inheritance to work properly, the base classes will often need to cooperate with their children. Python's super function makes this not too difficult to set up. You often will need a common base for the classes involved in the inheritance (to stop the inheritance chain):
class CommonBase:
def amethod(self):
print("CommonBase")
# don't call `super` here, we're the end of the inheritance chain
class Base1(CommonBase):
def amethod(self):
print("Base1")
super().amethod()
class Base2(CommonBase):
def amethod(self):
print("Base2")
super().amethod()
class Derived(Base1, Base2):
def amethod(self):
print("Derived")
super().amethod()
Now calling Derived().amethod() will print Derived, Base1, Base2, and finally CommonBase. The trick is that super passes each call on to the the next class in the MRO of self, even if that's not the in the current class's inheritance hierarchy. So Base1.amethod ends up calling Base2.amethod via super since they're being run on an instance of Derived.
If you don't need any behavior in the common base class, its method body just be pass. And of course, the Derived class can just inherit the method without writing its own version and calling super to get the rest.

Why inheritance in python require the parent class to inherit object explicitly?

Below is two versions of my code:
Non-working one
class A:
def __init__(self):
print "I am A "
class B:
def __init__(self):
print "I am B "
class C(A, B):
def __init__(self):
super(C, self).__init__()
c = C()
This raises exception:
super(C, self).__init__()
TypeError: must be type, not classobj
Working version
class A(object):
def __init__(self):
print "I am A "
class B:
def __init__(self):
print "I am B "
class C(A, B):
def __init__(self):
super(C, self).__init__()
c = C()
If one of the parent class has inherited object explicitly , there is no exception and things works as desired. Any explanation, why?
With the above working one , it prints "I am A" but not "I am B" which means it initializes only Class A and not Class B. HOw to initialize multiple parent classes in children class?
That's because super only works with new-style classes (that inherit from object). In Python 2, classes where object is nowhere in the inheritance hierarchy (like your first example) are called old-style classes, and should never be used anywhere.
It's an old historical artifact from when Python first got OO added to it, and the developers got it wrong. Among other things, new-style classes allow for the use of super, the descriptor protocol (properties), and makes several fixes to the way multiple inheritance is handled. super is one of them, but the most important is the way method resolution order is computed for diamond inheritance cases (A inherits from B and C which both inherit from D). Read more about it here: http://python-history.blogspot.fr/2010/06/method-resolution-order.html
Note that Python 3 ditched old-style classes entirely, and thus in Python 3 all classes are new-style classes and inherit from object, regardless of whether or not they do it explicitly.
super() only works for new-style classes(Any class which inherits from object).So You couldn't pass class object to super.
There are two typical use cases for super. In a class hierarchy with single inheritance, super can be used to refer to parent classes without naming them explicitly, thus making the code more maintainable. This use closely parallels the use of super in other programming languages.
The second use case is to support cooperative multiple inheritance in a dynamic execution environment. This use case is unique to Python and is not found in statically compiled languages or languages that only support single inheritance. This makes it possible to implement “diamond diagrams” where multiple base classes implement the same method. Good design dictates that this method have the same calling signature in every case (because the order of calls is determined at runtime, because that order adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime).
a typical superclass call looks like this:
class C(B):
def method(self, arg):
super(C, self).method(arg)

Mutate an object into an instance of one its subclasses

Is it possible to mutate an object into an instance of a derived class of the initial's object class?
Something like:
class Base():
def __init__(self):
self.a = 1
def mutate(self):
self = Derived()
class Derived(Base):
def __init__(self):
self.b = 2
But that doesn't work.
>>> obj = Base()
>>> obj.mutate()
>>> obj.a
1
>>> obj.b
AttributeError...
If this isn't possible, how should I do otherwise?
My problem is the following:
My Base class is like a "summary", and the Derived class is the "whole thing". Of course getting the "whole thing" is a bit expensive so working on summaries as long as it is possible is the point of having these two classes. But you should be able to get it if you want, and then there's no point in having the summary anymore, so every reference to the summary should now be (or contain, at least) the whole thing. I guess I would have to create a class that can hold both, right?
class Thing():
def __init__(self):
self.summary = Summary()
self.whole = None
def get_whole_thing(self):
self.whole = Whole()
Responding to the original question as posed, changing the mutate method to:
def mutate(self):
self.__class__ = Derived
will do exactly what was requested -- change self's class to be Derived instead of Base. This does not automatically execute Derived.__init__, but if that's desired it can be explicitly called (e.g. as self.__init__() as the second statement in the method).
Whether this is a good approach for the OP's actual problem is a completely different question than the original question, which was
Is it possible to mutate an object
into an instance of a derived class of
the initial's object class?
The answer to this is "yes, it's possible" (and it's done the way I just showed). "Is it the best approach for my specific application problem" is a different question than "is it possible";-)
A general OOP approach would be to make the summary object be a Façade that Delegates the expensive operations to a (dynamically constructed) back-end object. You could even make it totally transparent so that callers of the object don't see that there is anything going on (well, not unless they start timing things of course).
I forgot to say that I also wanted to be able to create a "whole thing" from the start and not a summary if it wasn't needed.
I've finally done it like that:
class Thing():
def __init__(self, summary=False):
if summary:
self.summary = "summary"
self._whole = None
else:
self._whole = "wholething"
#property
def whole(self):
if self._whole: return self._whole
else:
self.__init__()
return self._whole
Works like a charm :)
You cannot assign to self to do what you want, but you can change the class of an object by assigning to self.__class__ in your mutate method.
However this is really bad practice - for your situation delegation is better than inheritance.

Categories

Resources