Skip to content

Generate __reduce__ for auto_exc classes so kw_only exceptions pickle#1595

Open
Jayashanker-Padishala wants to merge 2 commits into
python-attrs:mainfrom
Jayashanker-Padishala:fix-734-exception-pickling
Open

Generate __reduce__ for auto_exc classes so kw_only exceptions pickle#1595
Jayashanker-Padishala wants to merge 2 commits into
python-attrs:mainfrom
Jayashanker-Padishala:fix-734-exception-pickling

Conversation

@Jayashanker-Padishala

Copy link
Copy Markdown

Fixes #734

Summary

BaseException.__reduce__ re-creates an exception by calling cls(*self.args) — that fails when __init__ can't accept the args positionally (kw_only fields) and silently loses any state __init__ doesn't reproduce.

Following the direction in the issue (a dedicated __reduce__ when we detect an exception), but addressing @meshy's counterexample — (cls, (), state) breaks for subclasses with required fields because cls() still calls __init__:

  • Re-create the instance with BaseException.__new__(cls, *args) via a module-level reconstructor: args is set without __init__ ever running, so signatures don't matter and validators/converters don't re-run on unpickle.
  • Slots classes restore state through their attrs-generated __setstate__ (frozen-safe via object.__setattr__); the generated __reduce__ tolerates attributes that were never set (init=False, no default).
  • Dict classes apply __dict__ state inside the reconstructor, because the default pickle state application can go through setattr, which frozen classes reject; this also preserves non-attrs instance attributes, matching classic exception pickling.
  • A user-defined __reduce__ on the class is respected (not overwritten).

Tests

New tests in tests/test_functional.py: kw_only round-trip across dict/slots × plain/frozen and all pickle protocols ≥2; subclass-with-required-positional round-trip; unset-attribute (init=False) pickling + deepcopy; user __reduce__ respected. Full suite: 1412 passed.

Changelog fragment included (named 1567.change.md — happy to rename to the actual PR number).

BaseException.__reduce__ re-creates an exception by calling
cls(*self.args), which fails when __init__ cannot accept the args
positionally (e.g. kw_only fields) and loses state __init__ does not
reproduce.

Generate a dedicated __reduce__ for exceptions that re-creates the
instance with BaseException.__new__ (setting args without calling
__init__) and restores attribute state: slots classes go through their
generated frozen-safe __setstate__, dict classes apply state inside the
reconstructor because default state application can go through setattr,
which frozen classes reject.

Fixes python-attrs#734
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pickling of Exceptions and kw_only

2 participants