8

We know that this creates a class:

class X:
    a = 1

And, in Python 3, due to new style classes, X automatically inherits from object, but in Python 2, it doesn't:

>>> X.__bases__ # Python 2
()
>>> X.__bases__ # Python 3
(<class 'object'>,)

And we also know that we can dynamically create such a class using type factory:

X = type("X",(object,), {"a":1})
      name^   ^bases     ^ "class" body

However, if we omit the object in the bases tuple just as we did with class syntax, we inherit from object in python3 and, unexpectedly, in python2 as well:

X = type("X", ( ), {"a":1})
               ^ empty bases tuple
>>> X.__bases__ # Python 2
(<type 'object'>,)
>>> X.__bases__ # Python 3
(<class 'object'>,)

I expected X.__bases__ to be empty in python 2.
And it's not such a documented feature, I hardly find something about this on internet.

In fact, python's official documentation contradictory states that:

the bases tuple itemizes the base classes and becomes the __bases__ attribute

But, as shown above, () still leads to (<type 'object'>,) in Python 2, so it's not really true that it becomes the __bases__ attribute.

Can anyone explain this behavior?

Adelin
  • 7,809
  • 5
  • 37
  • 65
  • Interesting question. I would start Python with `gdb`, make a coffee and step debug through this. Maybe you'll also find the sword of [Martin the warrior](https://xkcd.com/1722/)? :) – hek2mgl Jun 07 '19 at 06:36

1 Answers1

5

Let's look into this Python 2 shell.

>>> class X1:
...     a = 1
... 
... X2 = type('X2', (), {'a': 1})
>>> type(X1)
0: <type 'classobj'>
>>> type(X2)
1: <type 'type'>
>>> import types
>>> assert types.ClassType is type(X1)

types.ClassType is described as:

The type of user-defined old-style classes.

Basically type in the new-style classes' default metaclass. If you want metaprogramming in the old style, you can use types.ClassType in the same way.

>>> X3 = types.ClassType('X3', (), {'a': 1})
>>> X3.__bases__
2: () 

For the reference, excerpt from Python 2's New-style and classic classes:

Classes and instances come in two flavors: old-style (or classic) and new-style.

Up to Python 2.1 the concept of class was unrelated to the concept of type, and old-style classes were the only flavor available. For an old-style class, the statement x.__class__ provides the class of x, but type(x) is always <type 'instance'>. This reflects the fact that all old-style instances, independent of their class, are implemented with a single built-in type, called instance.

New-style classes were introduced in Python 2.2 to unify the concepts of class and type. A new-style class is simply a user-defined type, no more, no less. If x is an instance of a new-style class, then type(x) is typically the same as x.__class__ (although this is not guaranteed -- a new-style class instance is permitted to override the value returned for x.__class__).

saaj
  • 23,253
  • 3
  • 104
  • 105