python/cpython · error · RuntimeError

_markupbase.ParserBase must be subclassed

Error message

_markupbase.ParserBase must be subclassed

What it means

RuntimeError raised by _markupbase.ParserBase.__init__ (Lib/_markupbase.py) when ParserBase itself is instantiated. The class only supplies shared position/declaration-scanning machinery for SGML/HTML/XHTML parsers (html.parser.HTMLParser, html.parser.HTMLParserBase use cases); it has no document handlers, so direct instantiation is blocked.

Source

Thrown at Lib/_markupbase.py:29

_declstringlit_match = re.compile(r'(\'[^\']*\'|"[^"]*")\s*').match
_commentclose = re.compile(r'--\s*>')
_markedsectionclose = re.compile(r']\s*]\s*>')

# An analysis of the MS-Word extensions is available at
# http://web.archive.org/web/20060321153828/http://www.planetpublish.com/xmlarena/xap/Thursday/WordtoXML.pdf

_msmarkedsectionclose = re.compile(r']\s*>')

del re


class ParserBase:
    """Parser base class which provides some common support methods used
    by the SGML/HTML and XHTML parsers."""

    def __init__(self):
        if self.__class__ is ParserBase:
            raise RuntimeError(
                "_markupbase.ParserBase must be subclassed")

    def reset(self):
        self.lineno = 1
        self.offset = 0

    def getpos(self):
        """Return current line number and offset."""
        return self.lineno, self.offset

    # Internal -- update line number and offset.  This should be
    # called for each piece of data exactly once, in order -- in other
    # words the concatenation of all the input strings to this
    # function should be exactly the entire input.
    def updatepos(self, i, j):
        if i >= j:
            return j
        rawdata = self.rawdata

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Subclass ParserBase (adding at least goahead/handlers, or better: subclass html.parser.HTMLParser which composes it) — instantiation of the subclass is allowed.
  2. Use html.parser.HTMLParser for real HTML parsing needs; _markupbase is an implementation detail.
  3. If you only need position tracking (lineno/offset), copy the small getpos/updatepos pattern instead of instantiating the base.
  4. For copy/pickle paths, ensure reconstruction targets the concrete subclass, not ParserBase.

Example fix

# before
import _markupbase
p = _markupbase.ParserBase()  # RuntimeError: must be subclassed

# after
from html.parser import HTMLParser

class MyParser(HTMLParser):
    pass

p = MyParser()  # fine
Defensive patterns

Strategy: type-guard

Validate before calling

import _markupbase

def instantiable(cls) -> bool:
    return cls is not _markupbase.ParserBase

Type guard

import _markupbase

def is_concrete_parser_class(cls) -> bool:
    """True when cls can be instantiated (not the abstract ParserBase itself)."""
    return isinstance(cls, type) and issubclass(cls, _markupbase.ParserBase) and cls is not _markupbase.ParserBase

Try / catch

try:
    p = _markupbase.ParserBase()
except RuntimeError as e:
    if 'must be subclassed' in str(e):
        class MiniParser(_markupbase.ParserBase):
            pass
        p = MiniParser()
    else:
        raise

Prevention

When it happens

Trigger: Calling _markupbase.ParserBase() directly. Reached when someone imports the private module to reuse its regex scanning helpers, or a metaclass/copy path (e.g. copy.copy then __init__ re-run on the base) instantiates the base class.

Common situations: Developers reaching for _markupbase to parse fragments; inheriting from the wrong class in a custom parser; copy/deepcopy or pickle round-trips that reconstruct objects through the base __init__.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/f2d4f0a91f42450e. Report an issue: GitHub.