Tkinter + pyplot crashes AppKit on MacOS

I’m working on an app that has a Tkinter UI and uses Matplotlib’s pyplot to display a graph. The graph displays fine but Python crashes when the plot window is closed (only on macOS). I can run a Pyinstaller build in LLDB, which gives some detailed information.

When the plot window opens the following is logged:

2024-12-28 13:16:35.469192-0600 tt_repro[35230:1283710] [General] ERROR: Setting <View: 0x10af7f9a0> as the first responder for window <Window: 0x10af7ce80>, but it is in a different window ((null))! This would eventually crash when the view is freed. The first responder will be set to nil.
(
	0   AppKit                              0x00007ff8158c1b2e -[NSWindow _validateFirstResponder:] + 449
	1   AppKit                              0x00007ff8158c1929 -[NSWindow _setFirstResponder:] + 31
	2   AppKit                              0x00007ff8159d63f0 -[NSWindow _realMakeFirstResponder:] + 310
	3   _macosx.cpython-313-darwin.so       0x00000001076ff9dd FigureManager_init + 301
	4   Python                              0x0000000102fe914c wrap_init + 12
	5   Python                              0x0000000102f23017 wrapperdescr_call + 887
	6   Python                              0x00000001030ced98 _PyEval_EvalFrameDefault + 51160
	7   Python                              0x0000000102fe8893 slot_tp_init + 339
	8   Python                              0x0000000102fd8897 type_call + 135
	9   Python                              0x00000001030ced98 _PyEval_EvalFrameDefault + 51160
	10  Python                              0x0000000102f163d3 method_vectorcall + 371
	11  Python                              0x00000001030d45a8 _PyEval_EvalFrameDefault + 73704
	12  Python                              0x0000000102f16434 method_vectorcall + 468
	13  _tkinter.cpython-313-darwin.so      0x0000000107561135 PythonCmd + 213
	14  Tcl                                 0x00000001164cb9b2 Tcl_GetVersion + 4610
	15  Tcl                                 0x00000001164c691f TclNRRunCallbacks + 159
	16  Tcl                                 0x00000001164c8e10 TclEvalObjEx + 96
	17  Tcl                                 0x00000001164c603a Tcl_EvalObjEx + 42
	18  Tcl                                 0x0000000116640804 TclServiceIdle + 4004
	19  Tcl                                 0x0000000116640cf9 TclServiceIdle + 5273
	20  Tcl                                 0x000000011660c170 Tcl_ServiceEvent + 192
	21  Tcl                                 0x000000011660c659 Tcl_DoOneEvent + 521
	22  _tkinter.cpython-313-darwin.so      0x000000010756063e _tkinter_tkapp_mainloop_impl + 222
	23  Python                              0x0000000102f251db method_vectorcall_FASTCALL + 107
	24  Python                              0x00000001030ce8be _PyEval_EvalFrameDefault + 49918
	25  Python                              0x00000001030bfcdb PyEval_EvalCode + 139
	26  tt_repro                            0x00000001000035f4 tt_repro + 13812
	27  tt_repro                            0x0000000100004015 tt_repro + 16405
	28  dyld                                0x000000010085152e start + 462
)
2024-12-28 13:16:35.606430-0600 tt_repro[35230:1283710] [General] _createMenuRef called with existing principal MenuRef already associated with menu
2024-12-28 13:16:35.608113-0600 tt_repro[35230:1283710] [General] (
	0   CoreFoundation                      0x00007ff812eeb6e3 __exceptionPreprocess + 242
	1   libobjc.A.dylib                     0x00007ff812c4b8bb objc_exception_throw + 48
	2   CoreFoundation                      0x00007ff812eeb54a +[NSException exceptionWithName:reason:userInfo:] + 0
	3   AppKit                              0x00007ff8158a4d3a -[NSCarbonMenuImpl _createMenuRef] + 55
	4   AppKit                              0x00007ff8158a4705 -[NSCarbonMenuImpl _instantiateCarbonMenu] + 133
	5   AppKit                              0x00007ff81589dc1f -[NSApplication finishLaunching] + 680
	6   AppKit                              0x00007ff81589d6c8 -[NSApplication run] + 250
	7   _macosx.cpython-313-darwin.so       0x0000000107700aa2 show + 162
	8   Python                              0x0000000102f99680 cfunction_vectorcall_NOARGS + 96
	9   Python                              0x00000001030ce8be _PyEval_EvalFrameDefault + 49918
	10  Python                              0x0000000102f16434 method_vectorcall + 468
	11  Python                              0x00000001030d3f08 _PyEval_EvalFrameDefault + 72008
	12  Python                              0x0000000102f16434 method_vectorcall + 468
	13  _tkinter.cpython-313-darwin.so      0x0000000107561135 PythonCmd + 213
	14  Tcl                                 0x00000001164cb9b2 Tcl_GetVersion + 4610
	15  Tcl                                 0x00000001164c691f TclNRRunCallbacks + 159
	16  Tcl                                 0x00000001164c8e10 TclEvalObjEx + 96
	17  Tcl                                 0x00000001164c603a Tcl_EvalObjEx + 42
	18  Tcl                                 0x0000000116640804 TclServiceIdle + 4004
	19  Tcl                                 0x0000000116640cf9 TclServiceIdle + 5273
	20  Tcl                                 0x000000011660c170 Tcl_ServiceEvent + 192
	21  Tcl                                 0x000000011660c659 Tcl_DoOneEvent + 521
	22  _tkinter.cpython-313-darwin.so      0x000000010756063e _tkinter_tkapp_mainloop_impl + 222
	23  Python                              0x0000000102f251db method_vectorcall_FASTCALL + 107
	24  Python                              0x00000001030ce8be _PyEval_EvalFrameDefault + 49918
	25  Python                              0x00000001030bfcdb PyEval_EvalCode + 139
	26  tt_repro                            0x00000001000035f4 tt_repro + 13812
	27  tt_repro                            0x0000000100004015 tt_repro + 16405
	28  dyld                                0x000000010085152e start + 462
)

I suspect the first error and the crash it predicts are relevant. Then when the plot window is closed, the program crashes due to an illegal instruction error, which apparently can be due to an assertion failing.

(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
  * frame #0: 0x00007ff81587977c AppKit`-[NSResponder release] + 216
    frame #1: 0x00007ff8159c2a67 AppKit`-[NSTextView release] + 472
    frame #2: 0x00007ff812ada654 libsystem_blocks.dylib`_Block_release + 130
    frame #3: 0x00007ff812ada654 libsystem_blocks.dylib`_Block_release + 130
    frame #4: 0x00007ff812ada654 libsystem_blocks.dylib`_Block_release + 130
    frame #5: 0x00007ff812e6fb26 CoreFoundation`__CFRunLoopDoBlocks + 503
    frame #6: 0x00007ff812e6ee5d CoreFoundation`__CFRunLoopRun + 2609
    frame #7: 0x00007ff812e6dd6c CoreFoundation`CFRunLoopRunSpecific + 562
    frame #8: 0x000000011668d1f9 Tcl`Tcl_WaitForEvent + 441
    frame #9: 0x000000011660c5e7 Tcl`Tcl_DoOneEvent + 407
    frame #10: 0x000000010756063e _tkinter.cpython-313-darwin.so`_tkinter_tkapp_mainloop_impl + 222
    frame #11: 0x0000000102f251db Python`method_vectorcall_FASTCALL + 107
    frame #12: 0x00000001030ce8be Python`_PyEval_EvalFrameDefault + 49918
    frame #13: 0x00000001030bfcdb Python`PyEval_EvalCode + 139
    frame #14: 0x00000001000035f4 tt_repro`___lldb_unnamed_symbol138 + 500
    frame #15: 0x0000000100004015 tt_repro`___lldb_unnamed_symbol140 + 1797
    frame #16: 0x000000010085152e dyld`start + 462

The stack traces are from this reproducer:

from matplotlib import pyplot
import matplotlib
import tkinter


matplotlib.use("macosx")

def do_plot():
	axes = pyplot.axes()
	axes.bar(1, 1)
	pyplot.show()

root = tkinter.Tk()
root.after(500, do_plot)
root.mainloop()

The same error seems to happen regardless of whether I run this in the interpreter (python3 reproducer.py) or as an executable built by Pyinstaller. I’m not sure which library is at fault here, or whether it could be AppKit itself.

On what python version and MacOS version are you? How did you get your python?

macOS Monterey 12.7.6 and Python 3.13.0 downloaded from python.org

I guess this is a problem with the macosx backend specifically as it seems to be fixed by telling Matplotlib to use the Tk backend:

matplotlib.use("TkAgg")

My best guess as to the cause is that something in the Tk/Tcl stack makes the assumption that it’s responsible for all windows in the process and this is violated by the macosx backend “bypassing” that stack with the plot interface. Maybe I should take this to the Matplotlib issue tracker and see if it can be either fixed or documented there.