Inheritance and function overriding in Python - python

In python, if class C inherits from two other classes C(A,B), and A and B have methods with identical names but different return values, which value will that method return on C?

"Inherits two methods" isn't quite accurate. What happens is that C has a method resolution order (MRO), which is the list [C, A, B, object]. If you attempt to access a method that C does not define or override, the MRO determines which class will be checked next. If the desired method is defined in A, it shadows a method with the same name in B.
>>> class A:
... def foo(self):
... print("In A.foo")
...
>>> class B:
... def foo(self):
... print("In B.foo")
...
>>> class C(A, B):
... pass
...
>>> C.mro()
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
>>> C().foo()
In A.foo

MRO order will be followed now, if you inherit A and B in C, then preference order goes from left to right, so, A will be prefered and method of A will be called instead of B

Related

How does type() function infers type

Im trying to compare types of two objects by using isinstance(obj1, type(obj2)).
However, im not really sure how type infers the typing of an object - or whether theres a chance that the type returned is of an ancestor class.
Given three classes A, B, C. Class B is subclass of A and class C is subclass of B.
class A:
pass
class B(A):
pass
class C(B):
pass
Using type on an object is the same thing as calling obj.__class__. This returns the class to which an instance belongs.
isinstance however also checks for subclasses. So your call isinstance(obj1, type(obj2)) depends on if the two objects are related.
>>> a = A()
>>> b = B()
>>> c = C()
>>> type(c) == type(b)
False
>>> isinstance(c, type(b))
True
Instance c is of type <class '__main__.C'> and b is of type <class '__main__.B'>. So the comparison using type evaluates to False.
A more elaborate example of your approach and using the class directly:
>>> isinstance(c, type(b)) # as C is subclass of B
True
>>> isinstance(c, B) # using the class directly
True
>>> isinstance(b, type(c)) # as b is of parent class B
False
>>> isinstance(b, C) # C is subclass of B, thus b is not an instance of C
False
>>> isinstance(c, type(a)) # C is subclass of B which is subclass of A
True

How to create multiple empty classes in a single declaration?

As we can create multiple variables in a single line, such as:
x, y, z = 1, 2, 3
print(x)
and the output would be 1,
I wonder if there is something similar to create multiple empty classes, so something like this, which I know is wrong, but just to let you have an idea of what I mean:
class X, Y, Z:
pass
Thank you very much!
No, there is no special syntax to use a class definition this way. You could use the type constructor, something like:
>>> A, B, C = type('A', (object,), {}), type('B', (object,), {}), type('C', (object,), {})
>>> A, B, C
(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>)
>>>
>>> A()
<__main__.A object at 0x10354d780>
>>> B()
<__main__.B object at 0x1038b1ef0>
>>> C()
<__main__.C object at 0x10354d780>
But I think that's hardly elegant, and it's unlikely you'll be able to keep the single line to a sane length. Just stick to the full class definitions.

Is MRO order depth-first or breadth-first?

From Python in a Nutshell:
The lookup of an attribute name in a class essentially occurs by visiting ancestor
classes in left-to-right, depth-first order
However,
>>> class A(object): x = 'a'
...
>>> class B(A): pass
...
>>> class C(A): x = 'c'
...
>>> class D(B, C): pass
...
>>> D.x
'c'
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,
<class '__main__.A'>, <type 'object'>)
D.__mro__ lists the classes not in depth-first order, but breadth-first order. So do I misunderstand something?
Ignoring classic classes, Python resolves method and attribute lookups using the C3 linearisation of the class and its parents. The C3 linearisation is neither depth-first nor breadth-first in complex multiple inheritance hierarchies. In some sense, it is:
depth-first until classes are encountered that will share a parent,
and then breadth-first over those
although that is a very loose characterisation.
In particular however, in simple multiple inheritance hierarchies that do not share a parent, it is depth-first (conveniently ignoring object of course, which is always shared)
Simple Example – Depth First
>>> class a_0(object): pass
>>> class a_1(object): pass
>>> class b_0(a_0): pass
>>> class b_1(a_1): pass
>>> class c(b_0, b_1): pass
Then
>>> [x.__name__ for x in c.__mro__]
['c', 'b_0', 'a_0', 'b_1', 'a_1', 'object']
Shared Base Example – Depth then Breadth First
Note that in your example, you have a shared parent (A) which causes B and C to be traversed in a breadth first fashion. If you instead have an evern more complex hierarchy:
>>> class A(object): pass
>>> class B(A): pass
>>> class C(A): pass
>>> class D_0(B, C): pass
>>> class D_1(B, C): pass
>>> class E_0(D_0): pass
>>> class E_1(D_1): pass
>>> class F(E_0, E_1): pass
Then
>>> [x.__name__ for x in F.__mro__]
['F', 'E_0', 'D_0', 'E_1', 'D_1', 'B', 'C', 'A', 'object']
And you will observe that the search is depth first F, E_0, D_0 until it strikes the point where shared base classes are encountered (B and C that are also bases of D_1, at which point the depth first goes sideways to E_1 and depth first from there again.

usage of issubclass

>>> import sys
>>> sys.version_info
(2, 4, 4, 'final', 0)
>>> class C:
... pass
...
>>> issubclass(C, C)
True
>>> issubclass(C, object)
False
>>> class T(object):
... pass
...
>>> issubclass(T, T)
True
>>> issubclass(T, object)
True
>>>
Question 1> Why C is a subclass of C?
Question 2> what is the base class of C?
Thank you
// Update for Chris Morgan (At least for me, the following manual doesn't help at all)
>>> help(issubclass)
Help on built-in function issubclass in module __builtin__:
issubclass(...)
issubclass(C, B) -> bool
Return whether class C is a subclass (i.e., a derived class) of class B.
When using a tuple as the second argument issubclass(X, (A, B, ...)),
is a shortcut for issubclass(X, A) or issubclass(X, B) or ... (etc.).
Take a look at issubclass(class, classinfo) documentation
Return true if class is a subclass (direct, indirect or virtual) of
classinfo. A class is considered a subclass of itself. classinfo may
be a tuple of class objects, in which case every entry in classinfo
will be checked. In any other case, a TypeError exception is raised.
and to check base class of C use inspect.getmro(cls) function.
Return a tuple of class cls’s base classes, including cls, in method
resolution order.
>>> class C(object):
... pass
...
>>> inspect.getmro(C)
(<class '__main__.C'>, <type 'object'>)
>>>
http://docs.python.org/library/functions.html#issubclass From that link, "A class is considered a subclass of itself."
To answer your second question, C is an "old style" class so it isn't a subclass of object. Include object as the superclass if you want a new style class. See http://www.python.org/doc/newstyle/ for more info.
http://docs.python.org/library/functions.html#issubclass
A class is considered a subclass of itself.
C has no base class
print C.__bases__
()

Get base class type in Python

class a:
pass
class b(a):
pass
c = b()
type(c) == a #returns False
Is there an alternative to type() that can check if an object inherits from a class?
Yes, isinstance: isinstance(obj, Klass)
isinstance and issubclass are applicable if you know what to check against. If you don't know, and if you actually want to list the base types, there exist the special attributes __bases__ and __mro__ to list them:
To list the immediate base classes, consider class.__bases__. For example:
>>> from pathlib import Path
>>> Path.__bases__
(<class 'pathlib.PurePath'>,)
To effectively recursively list all base classes, consider class.__mro__ or class.mro():
>>> from pathlib import Path
>>> Path.__mro__
(<class 'pathlib.Path'>, <class 'pathlib.PurePath'>, <class 'object'>)
>>> Path.mro()
[<class 'pathlib.Path'>, <class 'pathlib.PurePath'>, <class 'object'>]
>>> class a:
... pass
...
>>> class b(a):
... pass
...
>>> c = b()
>>> d = a()
>>> type(c) == type(d)
True
type() returns a type object. a is the actual class, not the type

Categories