GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — pickle.dumps() creates and discards an AttributeError on every call since 3.13.1 · Issue #158126 · python/cpython

pickle.dumps() creates and discards an AttributeError on every call since 3.13.1 #158126

New issue
Copy link
New issue
Copy link
Closed
#158148
Closed
pickle.dumps() creates and discards an AttributeError on every call since 3.13.1#158126
#158148
Copy link
Labels
3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes3.16new features, bugs and security fixesnew features, bugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@ashm-dev
ashm-dev
opened on Sep 24, 2026
Issue body actions

Bug report

Bug description:

Since gh-125631 (GH-125752, backported to 3.13 in GH-126528), _pickle.Pickler has a custom tp_getattro (Pickler_getattr) so that persistent_id can be set on instances. A side
effect is that PyObject_GetOptionalAttr() no longer takes its fast path for Pickler: that path is only used when tp_getattro is PyObject_GenericGetAttr, and it reports a missing
attribute without creating an exception object.

dump() looks up the optional reducer_override method on every call:

https://github.com/python/cpython/blob/a5a4659548e/Modules/_pickle.c#L4862-L4867

A plain Pickler has no such attribute, so every pickle.dumps() call goes through Pickler_getattr() → PyObject_GenericGetAttr(), which creates an AttributeError with a formatted
message and sets its name and obj, and then PyObject_GetOptionalAttr() clears it. In 3.12 and 3.13.0 Pickler used the generic tp_getattro, and this lookup did not create an
exception.

The cost is visible with plain getattr():

import io
import pickle
import timeit

p = pickle.Pickler(io.BytesIO())


class C:
    pass


c = C()
cases = [
    ('getattr(pickler, "reducer_override", None)', lambda: getattr(p, 'reducer_override', None)),
cases = [
    ('getattr(pickler, "reducer_override", None)', lambda: getattr(p, 'reducer_override', None)),
    ('getattr(pickler, "fast", None)', lambda: getattr(p, 'fast', None)),
    ('getattr(c, "reducer_override", None)', lambda: getattr(c, 'reducer_override', None)),
    ('pickle.dumps(None)', lambda: pickle.dumps(None)),
]
for label, f in cases:
    t = timeit.Timer(f)
    n, _ = t.autorange()
    print(f'{min(t.repeat(7, n)) / n * 1e9:8.1f} ns  {label}')

On main (a5a4659, ./configure --enable-experimental-jit=yes, clang 21, no PGO/LTO, Linux x86-64):

   333.2 ns  getattr(pickler, "reducer_override", None)
    46.3 ns  getattr(pickler, "fast", None)
    39.3 ns  getattr(c, "reducer_override", None)
   524.8 ns  pickle.dumps(None)

A missing attribute costs about 40 ns on a regular object and about 330 ns on a Pickler, which is most of the cost of pickle.dumps(None).

Pickler_getattr() only special-cases persistent_id, so dump() could use _PyObject_GenericGetAttrWithDict(self, name, NULL, 1) when Py_TYPE(self)->tp_getattro == Pickler_getattr:

+static PyObject *Pickler_getattr(PyObject *self, PyObject *name);
+
 static int
 dump(PickleState *state, PicklerObject *self, PyObject *obj)
 {
@@
     /* Cache the reducer_override method, if it exists. */
-    if (PyObject_GetOptionalAttr((PyObject *)self, &_Py_ID(reducer_override),
-                             &tmp) < 0) {
+    if (Py_TYPE(self)->tp_getattro == Pickler_getattr) {
+        tmp = _PyObject_GenericGetAttrWithDict((PyObject *)self,
+                                               &_Py_ID(reducer_override),
+                                               NULL, 1);
+        if (tmp == NULL && PyErr_Occurred()) {
+            goto error;
+        }
+    }
+    else if (PyObject_GetOptionalAttr((PyObject *)self,
+                                      &_Py_ID(reducer_override), &tmp) < 0) {
         goto error;
     }

With this change (only the _pickle extension rebuilt from main, same interpreter, best of 3 runs of timeit with 7 repeats, pinned to one core):

before after
pickle.dumps(None) 517 ns 202 ns −61%
pickle.dumps(1) 526 ns 212 ns −60%
pickle.dumps(d, 1), d = LogRecord.__dict__-like dict with 22 keys 1941 ns 1500 ns −23%
pickle.dumps(d) 1770 ns 1404 ns −21%
multiprocessing.reduction.ForkingPickler.dumps(d) 3644 ns 3295 ns −10%
pickle.dumps(list(range(1000))) 10507 ns 10432 ns within noise

test_pickle, test_pickletools and test_copyreg pass with the change. Behaviour of subclasses is unchanged: a reducer_override method on a subclass, an instance attribute, a
property that raises AttributeError (suppressed) or another exception (propagated), and subclasses that define __getattr__ (they keep the old path).

The same pattern exists in Pickler.__init__() for the optional dispatch_table lookup:
https://github.com/python/cpython/blob/a5a4659548e/Modules/_pickle.c#L5143-L5148

getattr(pickler, "dispatch_table", None) takes about 390 ns, and pickle.Pickler(buf) about 550 ns. This affects code that creates Pickler objects directly, for example
multiprocessing.reduction.ForkingPickler.dumps().

This is similar to gh-157840, where hasattr() stopped creating an AttributeError for member descriptors without a value.

I found this while profiling logging.handlers.DatagramHandler, which calls pickle.dumps() for every record: about 26% of the time spent in pickle.dumps() was this lookup.

CPython versions tested on:

CPython main branch, 3.16, 3.15, 3.14, 3.13

Operating systems tested on:

Linux

Linked PRs

  • gh-158126: Avoid creating AttributeError for optional Pickler attribute lookups #158148
  • [3.15] gh-158126: Avoid creating AttributeError for optional Pickler attribute lookups (GH-158148) #160251
  • [3.14] gh-158126: Avoid creating AttributeError for optional Pickler attribute lookups (GH-158148) #160252

Activity

  1. ashm-dev
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Sep 24, 2026
  2. aisk
    added
    extension-modulesC modules in the Modules dirC modules in the Modules dir
    on Sep 24, 2026
  3. aisk
    added this to Pickle and copy issues 🥒on Sep 24, 2026
  4. aisk
    added
    3.13only security fixesonly security fixes
    3.14bugs and security fixesbugs and security fixes
    3.15bugs and security fixesbugs and security fixes
    3.16new features, bugs and security fixesnew features, bugs and security fixes
    on Sep 24, 2026
  5. 94mbi7 commented on Sep 25, 2026

    @94mbi7
    94mbi7
    on Sep 25, 2026
    More actions

    is this open ?

  6. ashm-dev commented on Sep 25, 2026

    @ashm-dev
    ashm-dev
    on Sep 25, 2026
    ContributorAuthor
    More actions

    is this open ?

    The status literally says Open, but PR #158148 has already beat you to it.

  7. kevinprimeraquecreo-stack
    added this to Project_VKon Oct 18, 2026
  8. github-project-automation
    moved this to Todo in Project_VKon Oct 18, 2026
  9. github-project-automation
    moved this from Todo to In progress in Project_VKon Oct 18, 2026
  10. kevinprimeraquecreo-stack
    added this to Project_VKon Oct 18, 2026
  11. serhiy-storchaka
    removed
    3.13only security fixesonly security fixes
    on Oct 22, 2026
  12. serhiy-storchaka
    closed this as completedin #158148on Oct 22, 2026
  13. serhiy-storchaka
    added a commit that references this issue on Oct 22, 2026

    gh-158126: Avoid creating AttributeError for optional Pickler attribu…

    e60a9da
  14. github-project-automation
    moved this to Done in Pickle and copy issues 🥒on Oct 22, 2026
  15. serhiy-storchaka commented on Oct 22, 2026

    @serhiy-storchaka
    serhiy-storchaka
    on Oct 22, 2026
    Member
    More actions

    Thank you @ashm-dev for the detailed analysis and benchmarks, and @lazerg for the PR.

    @lazerg, the PR was created only 8 hours after the issue was opened. The issue author may still have been working on their own PR. In the future, please wait a few days before submitting PRs for fresh issues when there is a chance that the reporter may work on it.

    Reacted by Shamil
  16. serhiy-storchaka
    added 2 commits that reference this issue on Oct 22, 2026

    [3.14] gh-158126: Avoid creating AttributeError for optional Pickler …

    be9b776

    [3.15] gh-158126: Avoid creating AttributeError for optional Pickler …

    73f9de3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes3.16new features, bugs and security fixesnew features, bugs and security fixesextension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    • Pickle and copy issues 🥒
      Status
      Done
      Show more project fields

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402520 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.