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 — Introduction of Thread._handle: _thread._ThreadHandle in Python3.13 has broken an unknown number of packages · Issue #132578 · python/cpython

Introduction of Thread._handle: _thread._ThreadHandle in Python3.13 has broken an unknown number of packages #132578

New issue
Copy link
New issue
Copy link
Open
Open
Introduction of Thread._handle: _thread._ThreadHandle in Python3.13 has broken an unknown number of packages#132578
Copy link
Labels
3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixeseasystdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@initialed85
initialed85
opened on Apr 16, 2025
Issue body actions

Bug report

Bug description:

To open with- Python is great, probably the language I have written the most code in; super excited for the progressing into no-GIL, at my day job we've built a massive Python system and we're hopeful that no-GIL (at least for parts of it) will help with a bunch of perf issues that we're presently either enduring or have built complexity to work around.

Onto the issue; in the code of a number now-broken (since Python 3.13) packages (if you Google around for TypeError: '_thread._ThreadHandle' object is not callable you'll get a feel for it), we'll likely see something roughly like this (I believe this is a bit of a common pattern):

class SomeThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.stopped = threading.Event()    

    def _handle(self):
        some_logic_here()

    def run(self):
        while not self.stopped.is_set():
            self._handle()

However, since Python 3.13, attempts to use this pattern result in failures like like this:

Traceback (most recent call last):
  File "/Users/edwardbeech/.venv/ims-mono-py3.13/lib/python3.13/site-packages/ims_utils/utils/thread_utils.py", line 208, in _do_tasks_and_return_duration
    self.tasks()
    ~~~~~~~~~~^^
  File "/Users/edwardbeech/Projects/FTP/ims-mono/wireless-manager-back-end/other/tcp/tcp_socket.py", line 111, in tasks
    self._handle()
    ~~~~~~~~~~~~^^
TypeError: '_thread._ThreadHandle' object is not callable

I believe this is because of the introduction of this code in threading.py; truncated excerpt:

class Thread:
    ....
    def __init__(self, group=None, target=None, name=None,
        ...
        self._handle = _ThreadHandle()

Now, for sure- arguably this pattern is problematic in general because threading.Thread has a bunch of sunder-private properties that this pattern risks clobbering and probably the right thing to do is to use threading.Thread.__init__(target=your_run_func)- but if we consider Hyrum's Law (relevant xkcd) we could re-frame this such that prior to Python 3.13, everyone's usage of threading.Thread was in-keeping with the "interface" (if you can call it that) supported by threading.Thread (despite the dubious and accidental "swiss cheese symbiosis" of property naming) and from Python 3.13 onwards, we've broken that interface for people.

I don't know enough about internal Python development to know if there's a strategy with these sorts of things e.g. perhaps we're happy to break userspace and folks must simply revise their libraries (and if so, then that's fine, I'm happy with that answer).

If there is no such strategy though and we're more focused on interoperation across versions, I think an easy fix here would be to just change to be a dunder property instead of a sunder property and call it a day- I would probably warn against changing all the other existing sunder properties to dunder properties though because again, who knows who is relying on them in some odd way as an interface lol.

Here's some Docker-based testing I was doing; probably not really needed to paint the picture:

docker run --rm -it --entrypoint python3 python:3.12 -c 'from threading import Thread; t = Thread(); print(repr(t._handle)) if hasattr(t, "_handle") else print("property does not exist")'
property does not exist

docker run --rm -it --entrypoint python3 python:3.13 -c 'from threading import Thread; t = Thread(); print(repr(t._handle)) if hasattr(t, "_handle") else print("property does not exist")'
<_thread._ThreadHandle object: ident=0>

docker run --rm -it --entrypoint python3 python:3.14.0a7 -c 'from threading import Thread; t = Thread(); print(repr(t._handle)) if hasattr(t, "_handle") else print("property does not exist")'
<_thread._ThreadHandle object: ident=0>

CPython versions tested on:

3.13

Operating systems tested on:

macOS

Linked PRs

  • gh-132578: Rename the threading.Thread._handle field #132696
  • [3.13] gh-132578: Rename the threading.Thread._handle field (GH-132696) #132789
  • gh-132578: Add regression test ensuring Thread subclasses can safely define _handle #150322

Activity

  1. initialed85
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    on Apr 16, 2025
  2. initialed85
    changed the title [-]Introduction of Thread._handle: _thread._ThreadHandle in Python3.13 has broken an unknown number of pages[/-] [+]Introduction of Thread._handle: _thread._ThreadHandle in Python3.13 has broken an unknown number of packages[/+] on Apr 16, 2025
  3. ZeroIntensity
    added
    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
    on Apr 16, 2025
  4. ZeroIntensity commented on Apr 16, 2025

    @ZeroIntensity
    ZeroIntensity
    on Apr 16, 2025
    Member
    More actions

    Hmm. In hindsight, we probably shouldn't have named it that, but is there anything we can really do at this point? All of those packages are already broken for 3.13.

  5. initialed85 commented on Apr 16, 2025

    @initialed85
    initialed85
    on Apr 16, 2025
    Author
    More actions

    Yeah, I mean I guess the only thing we could do is rename it to e.g. Thread.__handle for 3.14 but 3.13 has been out for a few months now, arguably the package maintainers are already putting effort into fixing this on their side.

    /shrug

  6. colesbury commented on Apr 18, 2025

    @colesbury
    colesbury
    on Apr 18, 2025
    Contributor
    More actions

    If I understand correctly, the problem is that it's fairly common to have a def _handle() function in a Thread subclass and that shadows the new _handle field. I think renaming the _handle field makes sense.

    cc @mpage @Yhg1s

    Reacted by Edward Beech
  7. ZeroIntensity
    added
    easy
    on Apr 18, 2025
  8. ZeroIntensity commented on Apr 18, 2025

    @ZeroIntensity
    ZeroIntensity
    on Apr 18, 2025
    Member
    More actions

    I've applied easy to give new contributors a shot at this. How do we prevent this sort of thing from happening in the future?

  9. picnixz commented on Apr 18, 2025

    @picnixz
    picnixz
    on Apr 18, 2025
    Member
    More actions

    How do we prevent this sort of thing from happening in the future

    Any easy but ugly way to do it is to prefix everything with _cpython or _cpy for short :') or use more explicit naming?

  10. picnixz
    added
    3.13only security fixesonly security fixes
    3.14bugs and security fixesbugs and security fixes
    on Apr 18, 2025
  11. ZeroIntensity commented on Apr 18, 2025

    @ZeroIntensity
    ZeroIntensity
    on Apr 18, 2025
    Member
    More actions

    Yeah, or use __ name mangling. This should definitely go to DPO.

  12. initialed85 commented on Apr 19, 2025

    @initialed85
    initialed85
    on Apr 19, 2025
    Author
    More actions

    Dang a PR up with the fix already- that's like a 3-day turnaround.

    Thx team, epic work!

    Reacted by Rakesh Asapanna
  13. mpage
    added 2 commits that reference this issue on Apr 21, 2025

    Merge branch 'main' into pythongh-132578-rename-_handle

    65582df

    gh-132578: Rename the `threading.Thread._handle` field (#132696)

    3cfab44
  14. miss-islington
    added a commit that references this issue on Apr 21, 2025

    pythongh-132578: Rename the `threading.Thread._handle` field (pythonG…

    d59d1d5
  15. kumaraditya303 commented on Oct 8, 2025

    @kumaraditya303
    kumaraditya303
    on Oct 8, 2025
    Contributor
    More actions

    There have been several releases of 3.13 with this so most packages have adapted for it by now, @Yhg1s should this be abandoned now?

  16. lowaa commented on Jan 30, 2026

    @lowaa
    lowaa
    on Jan 30, 2026
    More actions

    There have been several releases of 3.13 with this so most packages have adapted for it by now, @Yhg1s should this be abandoned now?

    We are still waiting patiently to upgrade to 3.13 and beyond :) 🙏

    EDIT: sorry, i see the PR is merged, pls disregard 👍

  17. ZeroIntensity commented on Jan 30, 2026

    @ZeroIntensity
    ZeroIntensity
    on Jan 30, 2026
    Member
    More actions

    EDIT: sorry, i see the PR is merged, pls disregard 👍

    We didn't backport the fix to 3.13. What packages are still broken by this?

  18. dioput12 commented on Apr 23, 2026

    @dioput12
    dioput12
    on Apr 23, 2026
    More actions

    Hello All, I just tried this minimal_repro.py and here's the result on Termux with 3.13.13 version:

    import sys
    
    # Check whether two identical string literals are actually shared (interned)
    a = "hello"
    b = "hello"
    print(a is b)          # True — CPython automatically intern short strings
    
    # Interestingly: strings from runtime operations are not always interned
    c = "hel" + "lo"
    print(a is c)          # True in CPython due to constant folding at compile time
    
    # But this is not:
    x = "hello " * 1
    print(a is x.strip())  # False — not yet interned
    
    # sys.intern for manual verification
    d = sys.intern("hello")
    print(a is d)          # True
    ~ $ python3
    Python 3.13.13 (main, Apr 10 2026, 16:12:19) [Clang 21.0.0 (https://android.googlesource.com/toolchain/llvm-project 5e96669f0 on android
    Type "help", "copyright", "credits" or "license" for more information.
    >>>
    ~ $ python3 minimal_repro.py
    True
    True
    False
    True
    ~ $
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.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixeseasystdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — 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.