Class inheritance via super with two arguments - python

In the below code, i replaced args with numbers to demonstrate what classes are inherited.
class Animal:
def __init__(self, animalName):
print(animalName, 'is an animal.');
class Mammal(Animal):
def __init__(self, mammalName):
print(mammalName, 'is a mammal.')
super().__init__(mammalName)
class CannotFly(Mammal):
def __init__(self, mammalThatCantFly):
print('2', "cannot fly.")
super().__init__('2')
class CannotSwim(Mammal):
def __init__(self, mammalThatCantSwim):
print('1', "cannot swim.")
super().__init__('1')
# Cat inherits CannotSwim and CannotFly
class Cat(CannotSwim, CannotFly):
def __init__(self):
print('I am a cat.');
super().__init__('Cat')
cat = Cat()
returns
I am a cat.
1 cannot swim.
2 cannot fly.
2 is a mammal.
2 is an animal.
Why is it not the below?
I am a cat.
1 cannot swim.
1 is a mammal.
1 is an animal.
2 cannot fly.
2 is a mammal.
2 is an animal.
There are effectively two call streams, no?

You can see the method resolution order (MRO) for Cat:
>>> Cat.mro()
[<class '__main__.Cat'>, <class '__main__.CannotSwim'>, <class '__main__.CannotFly'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>]
Each class appears once in the MRO, due to the C3 linearization algorithm. Very briefly, this constructs the MRO from the inheritance graph using a few simple rules:
Each class in the graph appears once.
Each class precedes any of its parent classes.
When a class has two parents, the left-to-right order of the parents is preserved.
("Linearization", because it produces a linear ordering of the nodes in the inheritance graph.)
super() is misnamed; a better name would have been something lie nextclass, because it does not use the current class's list of parents, but the MRO of the self argument. When you call Cat, you are seeing the following calls.
Cat.__init__
Cat.__init__ uses super to call CannotSwim.__init__
CannotSwim.__init__ uses super to call CannotFly.__init__
CannotFly.__init__ uses super to call Mammal.__init__
Mammal.__init__ uses super to call Animal.__init__
Animal.__init__ uses super to call object.__init__
object.__init__ does not use super (it "owns" __init__), so the chain ends there.
In particular, note #3: CannotSwim causes its "sibling" in the inheritance graph to be used, not its own parent.

Take a look at this post What does 'super' do in Python? - difference between super().__init__() and explicit superclass __init__()
Now what it says is that __init__ is called for every class in the instance's mro.
you can print that by doing print(Cat.__mro__) this will print out
(<class '__main__.Cat'>, <class '__main__.CannotSwim'>, <class '__main__.CannotFly'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>)
As you can see there is the order of the calls. Now, why '2' is used and not '1', see the answer in the comments by hussic

Related

super() printable representation

sup = super(B, self) and sup2 = super(B, B) have indistinguishable representations, they both look like this:
<super: <class 'B'>, <B object>>
while super(B, self).spam gives a bound method, super(B, B) only works with class methods, so super(B, B).cls_spam(), like demonstrated below. You can't use it for regular methods, you get a normal function:
class A:
#classmethod
def cls_spam(cls):
print('A.cls_spam: cls =', cls)
def spam(self):
print('A.spam: self =', self)
class B(A):
def call_spam(self):
sup = super(B, self)
print('B.call_spam: sup =', sup)
print('B.call_spam: sup.spam =', sup.spam)
print('B.call_spam: sup.cls_spam =', sup.cls_spam)
sup.spam()
sup.cls_spam()
sup2 = super(B, B)
print('B.call_spam: sup2 =', sup2)
print('B.call_spam: sup2.css_spam =', sup2.cls_spam)
# can't call sup2.spam(), not without giving it self explicitly
sup2.cls_spam()
The following interactive session illustrates:
>>> b = B()
>>> b.call_spam3()
B.call_spam: sup = <super: <class 'B'>, <B object>>
B.call_spam: sup.spam = <bound method A.spam of <__main__.B object at 0x108830b50>>
B.call_spam: sup.cls_spam = <bound method A.cls_spam of <class '__main__.B'>>
A.spam: self = <__main__.B object at 0x108830b50>
A.cls_spam: cls = <class '__main__.B'>
B.call_spam: sup2 = <super: <class 'B'>, <B object>>
B.call_spam: sup2.css_spam = <bound method A.cls_spam of <class '__main__.B'>>
A.cls_spam: cls = <class '__main__.B'>
super() is a complicated subject, if the demonstrated above behavior is documented understanding it would help me a lot.
Using Python 3.5.3,Debian GNU/Linux 9.11 (stretch)
super() is meant to be useful both in class methods and in regular methods. In a classmethod there is no instance, you only have access to the class, so the second argument to super() accepts either an instance or a class. That at least is covered in the documentation for super():
If the second argument is an object, isinstance(obj, type) must be true. If the second argument is a type, issubclass(type2, type) must be true (this is useful for classmethods).
Bold emphasis mine.
Now, the primary task super() has to perform is to search for attributes along the Method Resolution Order (MRO) list of classes, given a starting point. The starting point is the first argument, but the MRO has to be taken from the second argument; if you use super() in class Foo, you can't know at that time if Foo might have been subclassed, which can alter the MRO of the second argument. So, for this purpose, super() tracks two pieces of information:
The class you want to use as a starting point for the search (the first argument)
The class from which the MRO is taken (the type of the second argument if it is an instance, or just the second argument, if it is a class).
There is also a 3rd piece of information, the instance or class to which attributes are bound, when you do an attribute lookup. That's simply the second argument itself, so either an instance or class. The repr() output reflects just the first two values, in large part because those are 'hidden' in that super(), without arguments, gets its arguments from the context and so you can't, as easily see what the starting point is, or what the MRO source is, but you can much more easily see the first argument to the method (so self or cls).
If you want to distinguish between your two super() instances, you can instead look at the __self__ attribute, which represents that 3rd piece of information:
>>> sup = super(B, b)
>>> sup.__self__
<__main__.B object at 0x108b4c520>
>>> sup2 = super(B, B)
>>> sup2.__self__
<class '__main__.B'>
The other two pieces of information you do see in the repr() output are the __thisclass__ and __self_class__ attributes, respectively:
>>> sup.__thisclass__, sup.__self_class__
(<class '__main__.B'>, <class '__main__.B'>)
This is easier to spot when you use a different class as the first argument:
>>> sup_a = super(A, b)
>>> sup_a
<super: <class 'A'>, <B object>>
>>> sup_a.__thisclass__, sup_a.__self_class__
(<class '__main__.A'>, <class '__main__.B'>)

Grandchild inheriting from Parent class - Python

I am learning all about Python classes and I have a lot of ground to cover.
I came across an example that got me a bit confused.
These are the parent classes
Class X
Class Y
Class Z
Child classes are:
Class A (X,Y)
Class B (Y,Z)
Grandchild class is:
Class M (A,B,Z)
Doesn't Class M inherit Class Z through inheriting from Class B or what would the reason be for this type of structure? Class M would just ignore the second time Class Z is inherited wouldn't it be, or am I missing something?
Class M would just inherit the Class Z attributes twice (redundant) wouldn't it be, or am I missing something?
No, there are no "duplicated" attributes, Python performs a linearization they can the Method Resolution Order (MRO) as is, for instance, explained here. You are however correct that here adding Z to the list does not change anything.
They first construct MRO's for the parents, so:
MRO(X) = (X,object)
MRO(Y) = (Y,object)
MRO(Z) = (Z,object)
MRO(A) = (A,X,Y,object)
MRO(B) = (B,Y,Z,object)
and then they construct an MRO for M by merging:
MRO(M) = (M,)+merge((A,X,Y,object),(B,Y,Z,object),(Z,object))
= (M,A,X,B,Y,Z,object)
Now each time you call a method, Python will first check if the attribute is in the internal dictionary self.__dict__ of that object). If not, Python will walk throught the MRO and attempt to find an attribute with that name. From the moment it finds one, it will stop searching.
Finally super() is a proxy-object that does the same resolution, but starts in the MRO at the stage of the class. So in this case if you have:
class B:
def foo():
super().bar()
and you construct an object m = M() and call m.foo() then - given the foo() of B is called, super().bar will first attempt to find a bar in Y, if that fails, it will look for a bar in Z and finally in object.
Attributes are not inherited twice. If you add an attribute like:
self.qux = 1425
then it is simply added to the internal self.__dict__ dictionary of that object.
Stating Z explicitly however can be beneficial: if the designer of B is not sure whether Z is a real requirement. In that case you know for sure that Z will still be in the MRO if B is altered.
Apart from what #Willem has mentioned, I would like to add that, you are talking about multiple inheritance problem. For python, object instantiation is a bit different compared other languages like Java. Here, object instatiation is divided into two parts :- Object creation(using __new__ method) and object initialization(using __init__ method). Moreover, it's not necessary that child class will always have parent class's attributes. Child class get parent class's attribute, only if parent class constructor is invoked from child class(explicitly).
>>> class A(object):
def __init__(self):
self.a = 23
>>> class B(A):
def __init__(self):
self.b = 33
>>> class C(A):
def __init__(self):
self.c = 44
super(C, self).__init__()
>>> a = A()
>>> b = B()
>>> c = C()
>>> print (a.a) 23
>>> print (b.a) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'B' object has no attribute 'a'
>>> print (c.a) 23
In the above code snipped, B is not invoking A's __init__ method and so, it doesn't have a as member variable, despite the fact that it's inheriting from A class. Same thing is not the case for language like Java, where there's a fixed template of attributes, that a class will have. This's how python is different from other languages.
Attributes that an object have, are stored in __dict__ member of object and it's __getattribute__ magic method in object class, which implements attribute lookup according to mro specified by willem. You can use vars() and dir() method for introspection of instance.

Order of base classes and super() usage in multiple inheritance

Could you please help me to understand the difference between these two cases?
class B1:
def f(self):
super().temp()
class B2:
def temp(self):
print("B2")
class A(B1, B2):
pass
A().f()
It prints "B2".
If we switch B1 and B2:
class A(B2, B1):
pass
A().f()
I get AttributeError: 'super' object has no attribute 'temp'
Python uses something called C3 linearization to decide what order the base classes are in: the "method resolution order". This has basically two parts when stated informally:
The path must hierarchy must never go down from a class to its superclass, even indirectly. As such, no cycles are allowed and issubclass(X, Y) and issubclass(Y, Z) implies issubclass(X, Z).
The order when not forced by the rule above is ordered by number of steps to the super-most class (lower number of steps means earlier in the chain) and then the order of the classes in the class lists (earlier in the list means earlier in the chain).
The hierarchy here is:
A
/ \
/ \
B1 B2 # Possibly switched
\ /
\ /
object
In the first case the order after C3 linearization is
super super super
A → B1 → B2 → object
which we can find out with:
A.mro()
#>>> [<class 'A'>, <class 'B1'>, <class 'B2'>, <class 'object'>]
So the super() calls would resolve as:
class A(B1, B2):
pass
class B1:
def f(self):
# super() proxies the next link in the chain,
# which is B2. It implicitly passes self along.
B2.temp(self)
class B2:
def temp(self):
print("B2")
so calling A().f() tries:
Is f on the instance? No, so
Is f on the first class, A? No, so
Is f on the next class, B1? Yes!
Then B1.f is called and this calls B2.temp(self), which checks:
Is f on the class, B2? Yes!
And it is called, printing B2
In the second case we have
super super super
A → B2 → B1 → object
So the resolves
So the super() calls would resolve as:
class A(B2, B2):
pass
class B2:
def temp(self):
print("B2")
class B1:
def f(self):
# super() proxies the next link in the chain,
# which is B2. It implicitly passes self along.
object.temp(self)
Is f on the instance? No, so
Is f on the first class, A? No, so
Is f on the next class, B2? No, so
Is f on the next class, B1? Yes!
So B1.f is called and this calls object.temp(self), which checks:
Is f on the class, object? No,
There are no superclasses, so we have failed to find the attribute.
Raise AttributeError("{!r} object has no attribute {!r}".format(instance, attribute_name)).
The difference is simply the order of classes in MRO of class A in both cases:
class A1(B1, B2):
pass
class A2(B2, B1):
pass
print(A1.mro())
print(A2.mro())
Which returns:
[<class '__main__.A1'>, <class '__main__.B1'>, <class '__main__.B2'>, <class 'object'>]
and
[<class '__main__.A2'>, <class '__main__.B2'>, <class '__main__.B1'>, <class 'object'>]
Now when you call A1.f() or A2.F() the attribute is found in B1, and there you call super().temp(), which means call temp() on the next class found(or move on to class next to it not until temp is not found and so on..) in MRO.
As the next and only class in case of A2 is object which has no temp() method an error is raised.
In case of A1 next class after B1 is B2 which has a temp() method, hence no error is raised.

What does inheritance actually do in Python?

I'm struggling with how I should interpret class inheritance. What does it actually do? As far as I know, it allows your class to:
Use the inherited class function/methods
Use the inherited class local variables (self variables)
Does this go both ways? If something is inherited, will it be able to read his inheriter as well?
Please give your answer in as much layman's terms as possible. I'm not an native Englisch speaker and I've never had proper programming education. Give me an explenation, not a definiton :]
Let's say you have a class called Animal. In this class you have a method called walk, that prints "Animal is walking"
Now you have 2 other classes:
1. Class Bird that is inherited from Animal. You can now add an additional method to it: fly. This will print that a bird can fly.
2. Class Monkey that is inherited from Animal as well. You can now add an additional method to it: climb. This will print that a monkey can climb trees.
Both Monkey and Bird derive from Animal, so they can walk, they have a same functionality/feature. But both have a distinct feature as well: birds can fly and monkeys can climb trees. So it takes sense, right?
The reverse is false, because not every Animal can fly or can climb trees.
EDIT: I exaplined it to you in terms of methods. But this can apply to variables as well. The Animal class can have a weight field. Which is accessible from both inherited classes: Monkey and Bird.
But Monkey can also have a field called max_power and Bird can have a class variable called max_fly_altitude. This fields are unique for these certain types of animal and NOT for the generic Animal. Which can also be a crocodile. And a crocodile can't fly to have a max_fly_altitude attribute.
Inheritance does not work both ways. Here is what I mean:
If you have a class called Father and another class called Son which inherits from father as class Son(Father), then Son can use all the methods from the Father class but the Father cannot use the methods from the Son class.
Examples
class A:
def get_val(self):
return 5
class B(A):
def num(self):
return 3
>>> a = A()
>>> b = B()
>>> a.get_val()
5
>>> b.num()
3
>>> b.get_val()
5
>>> a.num()
Attribute Error: A instance has no attribute 'num'
Analogy
class Musician:
def get_type(self):
return 'musician'
class Pianist(Musician):
def get_prof(self):
return 'pianist'
>>> m = Musician()
>>> p = Pianist()
>>> m.get_type()
'musician'
>>> p.get_type()
'musician'
>>> p.get_prof()
'pianist'
>>> m.get_prof()
Attribute Error: Musician instance has no attribute 'get_prof'

Can I instantiate a subclass object from the superclass

I have the following example code:
class A(object):
def __init__(self, id):
self.myid = id
def foo(self, x):
print 'foo', self.myid*x
class B(A):
def __init__(self, id):
self.myid = id
self.mybid = id*2
def bar(self, x):
print 'bar', self.myid, self.mybid, x
When used, the following could be generated:
>>> a = A(2)
>>> a.foo(10)
foo 20
>>>
>>> b = B(3)
>>> b.foo(10)
foo 30
>>> b.bar(12)
bar 3 6 12
Now lets say I have some more subclasses class C(A): and class D(A):. I also know that the id will always fit in either B, C or D, but never in 2 of them at the same time.
Now I would like to call A(23) and get an object of the correct subclass. Something like this:
>>> type(A(2))
<class '__main__.B'>
>>> type(A(22))
<class '__main__.D'>
>>> type(A(31))
<class '__main__.C'>
>>> type(A(12))
<class '__main__.B'>
Is this impossible or is it possible but just bad design? How should problems like this be solved?
You should rather implement Abstract Factory pattern, and your factory would then build any object you like, depending on provided parameters. That way your code will remain clean and extensible.
Any hack you could use to make it directly can be removed when you upgrade your interpreter version, since no one expects backwards compatibility to preserve such things.
EDIT: After a while I'm not sure if you should use Abstract Factory, or Factory Method pattern. It depends on the details of your code, so suit your needs.
Generally it's not such a good idea when a superclass has any knowledge of the subclasses.
Think about what you want to do from an OO point of view.
The superclass is providing common behaviour for all objects of that type, e.g. Animal. Then the subclass provides the specialisation of the behaviour, e.g. Dog.
Think of it in terms of an "isa" relationship, i.e. a Dog is an Animal.
An Animal is a Dog doesn't really make sense.
HTH
cheers,
Rob
I don't think you can change the type of the object, but you can create another class that will work like a factory for the subclasses. Something like this:
class LetterFactory(object):
#staticmethod
def getLetterObject(n):
if n == 1:
return A(n)
elif n == 2:
return B(n)
else:
return C(n)
a = LetterFactory.getLetterObject(1)
b = LetterFactory.getLetterObject(2)
...

Categories

Resources