Keywords: Python | super() | TypeError | old-style classes | new-style classes | object-oriented programming
Abstract: This article provides an in-depth exploration of the root cause behind the TypeError: must be type, not classobj error when using Python's super() function in inheritance scenarios. By analyzing the fundamental differences between old-style and new-style classes, particularly the relationship between classes and types, and the distinction between issubclass() and isinstance() tests, it explains why HTMLParser as an old-style class causes super() to fail. The article presents correct methods for testing class inheritance, compares direct parent method calls with super() usage, and helps developers gain a deeper understanding of Python's object-oriented mechanisms.
Problem Background and Phenomenon
In Python programming, using the super() function to call parent class methods is a common inheritance pattern. However, when inheriting from certain classes, developers may encounter the TypeError: must be type, not classobj error. Consider the following code example:
>>> from HTMLParser import HTMLParser
>>> class TextParser(HTMLParser):
... def __init__(self):
... super(TextParser, self).__init__()
... self.all_data = []
...
>>> TextParser()
TypeError: must be type, not classobj
This error appears to be related to old-style versus new-style classes, but the test isinstance(HTMLParser(), object) returns True, which can misleadingly suggest that HTMLParser is a new-style class. This represents a common misunderstanding of Python's class system.
The Fundamental Difference Between Old-style and New-style Classes
Python 2.x maintains a distinction between old-style and new-style classes, a division that has been unified in Python 3 (where all classes are new-style). To understand the super() error, one must deeply comprehend the essential differences between these class types.
Class Type versus Instance Type
For old-style classes, the class itself is not a type but rather a classobj:
>>> class OldStyle:
... pass
>>> type(OldStyle)
<type 'classobj'>
>>> isinstance(OldStyle, type)
False
In contrast, for new-style classes, the class itself is a type:
>>> class NewStyle(object):
... pass
>>> type(NewStyle)
<type 'type'>
>>> isinstance(NewStyle, type)
True
This difference directly impacts the implementation of the super() function. super() requires its parameters to be types, but old-style classes are classobj instances, thus triggering the TypeError.
Correct Testing Methods
A common misconception is using isinstance(instance, object) to test whether an instance is new-style. In reality, all Python objects are instances of object, making this test ineffective for distinguishing between old-style and new-style classes.
The correct testing approaches are:
- Testing whether a class is new-style: Use
issubclass(cls, object) - Testing whether an instance comes from a new-style class: Use
issubclass(instance.__class__, object)
>>> issubclass(OldStyle, object)
False
>>> issubclass(int, object) # int is a new-style class
True
>>> instance = OldStyle()
>>> issubclass(instance.__class__, object)
False
The Reality of HTMLParser
Returning to the original problem, HTMLParser in Python 2.x is an old-style class. Although isinstance(HTMLParser(), object) returns True, this only indicates that its instances are objects, not that HTMLParser itself is a new-style class.
Verification method:
>>> from HTMLParser import HTMLParser
>>> issubclass(HTMLParser, object)
False
>>> type(HTMLParser)
<type 'classobj'>
Therefore, when TextParser inherits from HTMLParser and attempts to use super(TextParser, self).__init__(), the super() function receives a classobj instead of a type, resulting in the error.
Solutions and Alternative Approaches
Solution 1: Direct Parent Method Call
For old-style classes, the most straightforward solution is to explicitly call the parent class's initialization method:
class TextParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self) # Direct parent method call
self.all_data = []
This approach avoids using super() and works with all class types, but loses the dynamic resolution advantages of super() in multiple inheritance scenarios.
Solution 2: Creating an Adapter Class
If super() functionality is required, one can create an intermediate class that inherits from object:
class NewStyleHTMLParser(HTMLParser, object):
"""Wraps an old-style class as new-style"""
pass
class TextParser(NewStyleHTMLParser):
def __init__(self):
super(TextParser, self).__init__() # Now works correctly
self.all_data = []
This method converts old-style classes to new-style through multiple inheritance, but potential method resolution order (MRO) issues should be considered.
Solution 3: Upgrading to Python 3
In Python 3, where all classes are new-style, this problem naturally disappears:
# Python 3 code
from html.parser import HTMLParser
class TextParser(HTMLParser):
def __init__(self):
super().__init__() # Works correctly
self.all_data = []
Deep Understanding: Evolution of the Type System
Python's type system has evolved from old-style to new-style classes, a change that profoundly impacted the language's object-oriented capabilities:
Limitations of Old-style Classes
Major issues with old-style classes include:
- Classes are not types and cannot be returned by
type() - No support for the descriptor protocol
- Simple method resolution without complex multiple inheritance support
- Inability to use advanced features like
super()andproperty
Advantages of New-style Classes
New-style classes introduced a unified type model:
- All classes are instances of
type - Full support for the descriptor protocol
- Introduction of the method resolution order (MRO) algorithm
- Support for advanced features like
super(), properties, and class methods
Practical Recommendations
- Explicitly inherit from
objectin Python 2: Always define classes usingclass MyClass(object):to ensure creation of new-style classes. - Understand class types in third-party libraries: When using third-party libraries, especially those written for Python 2.x, be aware that their classes may be old-style.
- Choose appropriate calling methods: Use direct parent method calls for known old-style classes; use
super()for new-style classes or when multiple inheritance support is needed. - Gradually migrate to Python 3: Python 3's unified type system eliminates these historical issues and represents the long-term solution.
Conclusion
The TypeError: must be type, not classobj error raised by super() originates from the fundamental differences between Python's old-style and new-style classes. Old-style classes (like HTMLParser) are classobj instances rather than types, while super() requires type parameters. Correctly distinguishing between old-style and new-style classes requires using issubclass(cls, object) rather than isinstance(instance, object). In practical development, choose between direct parent method calls or adapter patterns based on specific circumstances, and prioritize migration to Python 3 for a more consistent type system experience.