Is MRO order depth-first or breadth-first? - python

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.

Related

Inheritance and function overriding in 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

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.

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

Object vs. class variable

This is a completely theoretical question. Suppose the following code:
>>> class C:
... a = 10
... def f(self): self.a = 999
...
>>>
>>> C.a
10
>>> c = C()
>>> c.a
10
>>> c.f()
>>> c.a
999
At this point, is class variable C.a still accessible through the object c?
Yes, though c.__class__.a or type(c).a. The two differ slightly in that old-style classes (hopefully, those are all dead by now - but you never know...) have a type() of <type 'instance'> (and __class__ works as expected) while for new-style classes, type() is identical to __class__ except when the object overrides attribute access.
All class variables are accessible through objects instantiated from that class.
>>> class C:
... a = 10
... def f(self): self.a = 999
...
>>> C.a
10
>>> c = C()
>>> c.a
10
>>> c.f()
>>> c.a
999
>>> c.__class__.a
10
>>> c.a
999
>>> del(c.a)
>>> c.a
10
Attributes are first searched within the object namespace and then class.
Yes, you can access a from an object c, à la c.a. The value would initially be 10.
However, if you call c.f(), the value of c.a will now be 999, but C.a will still be 10. Likewise, if you now change C.a to, say, 1000, c.a will still be 999.
Basically, when you instantiate an instance of C, it will use the class variable as its own a value, until you change the value of that instance's a, in which case it will no longer "share" a with the class.
After you assign to it on the class instance, there is both a class attribute named a and an instance attribute named a. I illustrate:
>>> class Foo(object):
... a = 10
...
>>> c = Foo()
>>> c.a
10
>>> c.a = 100 # this doesn't have to be done in a method
>>> c.a # a is now an instance attribute
100
>>> Foo.a # that is shadowing the class attribute
10
>>> del c.a # get rid of the instance attribute
>>> c.a # and you can see the class attribute again
10
>>>
The difference is that one exists as an entry in Foo.__dict__ and the other exists as an entry in c.__dict__. When you access instance.attribute, instance.__dict__['attribute'] is returned if it exists and if not then type(instance).__dict__['attribute'] is checked. Then the superclasses of the class are checked but that gets slightly more complicated.
But at any rate, the main point is that it doesn't have to be one or the other. A class and an instance can both have distinct attributes with identical names because they are stored in two separate dicts.

Categories