Inheriting Built In Exceptions
Some built-in exceptions (notably OSError subclasses) use __new__ to select a subclass based on errno, and override __init__/__new__ to set attributes like errno, strerror, filename. If you subclass these and add your own attributes without careful handling, they can be silently dropped or overwritten.
Pythonclass MyError(OSError): def __init__(self, message, foo): super().__init__(message) self.foo = foo def __str__(self): return f"{super().__str__()} (foo={self.foo!r})" try: raise MyError("bad thing", foo=42) except MyError as e: print(e.foo) # 42 print(str(e)) # bad thing (foo=42)
Progress:
- Step 1: Identify if the base exception has custom
__new__(e.g.,OSErrorpicks subclasses likeFileNotFoundErrorbased onerrno). - Step 2: Check whether base
__init__/__new__accepts and stores extra positional args — subclass args beyond what the base expects are stored inargsbut not as separate attributes. - Step 3: Always call
super().__init__(...)explicitly with the args the base class expects, then set additional custom attributes afterward in your own__init__. - Step 4: If overriding
__new__(rare, only needed for immutable-like customization), pass through*args, **kwargstosuper().__new__and avoid overwriting base attributes. - Step 5: Override
__str__/__repr__if you want custom attributes reflected in the printed message — the base__str__won't know about them. - Step 6: Test pickling/copying if relevant — exceptions with custom
__init__signatures may fail to unpickle unless__reduce__is implemented, since default pickling replaysargsthrough__init__or__new__.
Example 1: Adding an attribute to OSError subclass Input:
Pythonclass ValidationError(OSError): def __init__(self, message, field): super().__init__(message) self.field = field
Output: ValidationError("bad", field="x").field == "x"; str(exc) == "bad"; pickling works if args == (message,) matches what __init__ expects — but field is lost on unpickle unless __reduce__ is added:
Pythondef __reduce__(self): return (self.__class__, (self.args[0], self.field))
Example 2: OSError subclass hijacked by errno-based dispatch Input:
Pythonclass MyOSError(OSError): pass err = MyOSError(2, "No such file") type(err)
Output: FileNotFoundError — because OSError.__new__ inspects the errno argument (2 == ENOENT) and returns an instance of the more specific built-in subclass instead of MyOSError, unless MyOSError itself overrides __new__ to prevent this dispatch.
Example 3: StopIteration with custom value Input:
Pythonclass MyStop(StopIteration): def __init__(self, value, extra): super().__init__(value) self.extra = extra e = MyStop("done", extra="meta") e.value, e.extra
Output: ("done", "meta") — StopIteration.__init__ already sets self.value from the first arg, so no need to set it manually.
- Always call
super().__init__()with exactly the arguments the built-in expects; don't assume it forwards**kwargs. - Keep custom attributes as plain instance attributes set after
super().__init__(), not passed through it. - Override
__str__/__repr__when custom attributes should appear in tracebacks/logs. - If subclassing
OSError, be aware of implicit subclass dispatch via__new__; override__new__in your subclass if you need to force your exact type to be preserved. - Implement
__reduce__for exceptions with non-standard__init__signatures to keep pickling/multiprocessing working.
- Forgetting that
OSError(errno, strerror[, filename])auto-selects a more specific subclass (FileNotFoundError,PermissionError, etc.), silently changing your exception's type. - Overriding
__init__without callingsuper().__init__(), leavingargs,strerror,errno, etc. unset. - Assuming default pickling works — it calls
cls(*exc.args), which breaks if__init__has a different signature (extra required args likefieldabove). - Not overriding
__str__, resulting in custom attributes being invisible in printed tracebacks. - Adding attributes via
__new__instead of__init__when__new__doesn't need customizing — unnecessary complexity for most cases.