See all inherited classes in python - python

So I've done a bit of research online and it seems like the __subclasses__ method returns all the inherited classes for a python object (relevant stack overflow question)
On python3.8 I then tried the following:
class A:
a = 1
class B:
b = 2
class C(A, B):
c = 3
obj = C()
print('a: ', obj.a)
print('subclasses: ', C.__subclasses__())
and I get out
a: 1
subclasses: []
this shows that class C successfully inherits A and B, however they don't show up with the subclasses method? So is there something I'm missing in the __subclasses__ method, or has the method changed for python 3.8?

just combining the answerts from above :
my_class.__subclasses__ will return the classes, which subclass from my_class
C.__mro__ shows the inheritence hierarchy in your case :
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <type 'object'>)
object
/ \
A B
\ /
C
In short, __subclasses__ goes down the object hierarchy ladder and the __mro__ goes up. Good luck :)

Related

Class inheritance via super with two arguments

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

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'>)

method resolution order MRO

Why after searching B, it does not go deeper to search Y OR z but go to search A?
Y is the parent of A, if should search A first, but Y is the parent of B so it should search Y first, why this does not throw a MRO error?
Can someone explain how this lookup works?
class X(object):pass
class Y(object): pass
class Z(object): pass
class A(X,Y): pass
class B(Y,Z):pass
class M(B,A,Z):pass
print M.__mro__
gives
(<class '__main__.M'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.X'>, <class '__main__.Y'>, <class '__main__.Z'>, <type 'object'>)
In your specific example, after searching B, we can't consider Y immediately because it is a child of A. We can't consider Z immediately because M inherits from A before it inherits from Z.
Python uses C3 method resolution order details here .
C3 resolution order solves the diamond inheritance problem well
In the example below, we have a very generic class Object that's a superclass of B and C. We only want method implementations (say of __repr__ or something) in Object to be considered if neither B nor C have an implementation.
Object
/ \
B C
\ /
A
In other words, each possible parent in the transitive closure of the parent classes of A is considered, but the classes are ordered according to the "latest" path from the base class to the class in question.
There are two paths to object:
A -> B -> Object
A -> C -> Object
The "latest" path is A -> C -> Object because A -> B -> Object would be earlier in a left-biased depth-first search.
C3 linearization satisfies two key invariants:
if X inherits from Y, X is checked before Y.
if Z inherits from U and then V in that order, U is checked before V.
Indeed C3 linearization guarantees that both of those properties hold.
It's possible to construct hierarchies that can't be linearized, in which case you get an exception at class definition time.
running inherit.py
class E: pass
class F: pass
class A(E, F): pass
class B(F, E): pass
class Z(A, B): pass
produces the following error.
Traceback (most recent call last):
File "inherit.py", line 5, in <module>
class Z(A, B): pass
TypeError: Cannot create a consistent method resolution
order (MRO) for bases E, F

the searching order of inheritance in python [duplicate]

This question already has answers here:
How does Python's super() work with multiple inheritance?
(18 answers)
Closed 7 years ago.
when searching for attributes, Python looks in object g 1st. If I number each class named in the drawing below (2=2nd, 3=3rd, etc.) Could anyone show me the order in which all these classes are searched.
class A : pass
class B : pass
class C(A,object): pass
class D: pass
class E(D,B): pass
class F(B,C): pass
class G(E,F): pass
g = G()
This order:
>>> G.__mro__
(<class '__main__.G'>, <class __main__.E at 0x028412D0>, <class __main__.D at 0x
02841298>, <class '__main__.F'>, <class __main__.B at 0x02841260>, <class '__mai
n__.C'>, <class __main__.A at 0x02841228>, <type 'object'>)
>>> print ', '.join(klass.__name__ for klass in G.__mro__)
G, E, D, F, B, C, A, object
If you want to know how that order is computed, there isn't a good, short answer. The incomplete, short answer is that Python ensures that subclasses come before superclasses and classes earlier in a list of base classes come before classes later in a list of base classes. The long answer is the 40-page document about C3 linearization on python.org, although the actual algorithm isn't 40 pages.

What is the default `__reduce__` in Python?

import pickle
class A:
pass
pickle.dumps(B().__reduce__())
yields
(<function _reconstructor at 0x1010143b0>, (<class '__main__.B'>, <class 'object'>, None))
What is this function "_reconstructor". It's neither B, B.__init__, nor B.__new__ as I expected.
I have to make 2 changes to get that result:
Change the name of your class from A to B.
Remove the outer pickle.dumps() call.
In any case, pickle is free to do anything it likes to reconstruct the object ;-) In this case, you can find the _reconstructor() function in Lib/copyreg.py.

Categories

Resources