Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
forked fromsagemath/sage

Commitc162d51

Browse files
author
Release Manager
committed
sagemathgh-41021: Refactor ``atexit.pyx`` <!-- ^ Please provide a concise and informative title. --><!-- ^ Don't put issue numbers in the title, do this in the PRdescription below. --><!-- ^ For example, instead of "Fixessagemath#12345" use "Introduce new methodto calculate 1 + 2". --><!-- v Describe your changes below in detail. --><!-- v Why is this change required? What problem does it solve? --><!-- v If this PR resolves an open issue, please link to it here. Forexample, "Fixessagemath#12345". -->I have refactor the ``atexit.pyx`` since for python 3.14, the changes of``atexit`` is so much for supporting the nogil version.### Python<=3.13 Implementation:- The ``atexit`` module stores callbacks in a C array of structs(``atexit_callback``).- The structure is defined in C as:```typedef struct {  PyObject *func;    PyObject *args;    PyObject *kwargs;} atexit_callback;```- Callbacks are stored in the interp->atexit.callbacks field as a Carray- The array can be directly accessed from Cython code using pointerarithmetic### Python 3.14 Implementation:- The ``atexit`` module was refactored to use a Python ``PyList`` objectinstead of a C array.- The structure is now:```state.callbacks = [(func, args, kwargs), ...] # A Python list of tuples```- In the C implementation, callbacks are managed with:```PyObject *callbacks; // This is now a PyList```- Callbacks are inserted at the beginning (LIFO order) using``PyList_Insert(state->callbacks, 0, callback)``### 📝 Checklist<!-- Put an `x` in all the boxes that apply. -->- [x] The title is concise and informative.- [x] The description explains in detail what this PR is about.- [x] I have linked a relevant issue or discussion.- [ ] I have created tests covering the changes.- [ ] I have updated the documentation and checked the documentationpreview.### ⌛ Dependencies<!-- List all open PRs that this PR logically depends on. For example,--><!-- -sagemath#12345: short description why this is a dependency --><!-- -sagemath#34567: ... -->python/cpython@3b7668284d4461afpython/cpython#127935 URL:sagemath#41021Reported by: Chenxin ZhongReviewer(s): Copilot, da-woods
2 parentsa746b10 +5f363a8 commitc162d51

File tree

1 file changed

+75
-24
lines changed

1 file changed

+75
-24
lines changed

‎src/sage/cpython/atexit.pyx‎

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -143,51 +143,99 @@ cdef class restore_atexit:
143143
_set_exithandlers(self._exithandlers)
144144

145145
from cpython.ref cimport PyObject
146+
import sys
146147

147-
# Implement"_atexit_callbacks()" for each supported python version
148+
# Implementa uniform interface for getting atexit callbacks
148149
cdef externfrom*:
149150
"""
151+
#ifndef Py_BUILD_CORE
150152
#define Py_BUILD_CORE
153+
#endif
151154
#undef _PyGC_FINALIZED
152155
#include "internal/pycore_interp.h"
153156
#include "internal/pycore_pystate.h"
154-
#if PY_VERSION_HEX >= 0x030c0000
155-
// struct atexit_callback was renamed in 3.12 to atexit_py_callback
156-
#define atexit_callback atexit_py_callback
157-
#endif
158-
static atexit_callback ** _atexit_callbacks(PyObject *self) {
157+
158+
// Always define this struct for Cython's use
159+
typedef struct {
160+
PyObject *func;
161+
PyObject *args;
162+
PyObject *kwargs;
163+
} atexit_callback_struct;
164+
165+
#if PY_VERSION_HEX >= 0x030e0000
166+
// Python 3.14+: atexit uses a PyList
167+
static PyObject* get_atexit_callbacks_list(PyObject *self) {
159168
PyInterpreterState *interp = _PyInterpreterState_GET();
160169
struct atexit_state state = interp->atexit;
161170
return state.callbacks;
162171
}
172+
173+
// Dummy function for Python 3.14+ (never called)
174+
static atexit_callback_struct** get_atexit_callbacks_array(PyObject *self) {
175+
PyErr_SetString(PyExc_RuntimeError, "Python >= 3.14 has no atexit array");
176+
return NULL;
177+
}
178+
#else
179+
// Python < 3.14: atexit uses C array
180+
static atexit_callback_struct** get_atexit_callbacks_array(PyObject *self) {
181+
PyInterpreterState *interp = _PyInterpreterState_GET();
182+
struct atexit_state state = interp->atexit;
183+
// Cast from atexit_callback** to our struct type
184+
return (atexit_callback_struct**)state.callbacks;
185+
}
186+
187+
// Dummy function for Python < 3.14 (never called)
188+
static PyObject* get_atexit_callbacks_list(PyObject *self) {
189+
PyErr_SetString(PyExc_RuntimeError, "Python < 3.14 has no atexit list");
190+
return NULL;
191+
}
192+
#endif
163193
"""
164-
ctypedefstruct atexit_callback:
194+
# Declare both functions - they exist in all Python versions (one is dummy)
195+
object get_atexit_callbacks_list(object module)
196+
197+
ctypedefstruct atexit_callback_struct:
165198
PyObject* func
166199
PyObject* args
167200
PyObject* kwargs
168-
atexit_callback**_atexit_callbacks(object module)
201+
atexit_callback_struct**get_atexit_callbacks_array(object module)exceptNULL
169202

170203

171204
def_get_exithandlers():
172205
"""Return list of exit handlers registered with the atexit module."""
173-
cdefatexit_callback** callbacks
174-
cdefatexit_callback callback
175-
cdeflist exithandlers
206+
cdeflist exithandlers= []
207+
cdefatexit_callback_struct** callbacks
208+
cdefatexit_callback_struct callback
176209
cdefint idx
177210
cdefobject kwargs
178-
179-
exithandlers= []
180-
callbacks= _atexit_callbacks(atexit)
181-
182-
for idxinrange(atexit._ncallbacks()):
183-
callback= callbacks[idx][0]
184-
if callback.kwargs:
185-
kwargs=<object>callback.kwargs
186-
else:
187-
kwargs= {}
188-
exithandlers.append((<object>callback.func,
189-
<object>callback.args,
190-
kwargs))
211+
212+
# Python 3.14+ uses a PyList directly
213+
if sys.version_info>= (3,14):
214+
callbacks_list= get_atexit_callbacks_list(atexit)
215+
if callbacks_listisNone:
216+
return exithandlers
217+
# callbacks is a list of tuples: [(func, args, kwargs), ...]
218+
# Normalize kwargs to ensure it's always a dict (not None)
219+
# Note: In Python 3.14+, atexit stores callbacks in LIFO order
220+
# (most recently registered first), but we return them in FIFO
221+
# order (registration order) for consistency with earlier versions
222+
for iteminreversed(callbacks_list):
223+
func, args, kwargs= item
224+
if kwargsisNone:
225+
kwargs= {}
226+
exithandlers.append((func, args, kwargs))
227+
else:
228+
# Python < 3.14 uses C array
229+
callbacks= get_atexit_callbacks_array(atexit)
230+
for idxinrange(atexit._ncallbacks()):
231+
callback= callbacks[idx][0]
232+
if callback.kwargs:
233+
kwargs=<object>callback.kwargs
234+
else:
235+
kwargs= {}
236+
exithandlers.append((<object>callback.func,
237+
<object>callback.args,
238+
kwargs))
191239
return exithandlers
192240

193241

@@ -202,6 +250,9 @@ def _set_exithandlers(exithandlers):
202250

203251
# We could do this more efficiently by directly rebuilding the array
204252
# of atexit_callbacks, but this is much simpler
253+
# Note: exithandlers is in registration order (FIFO).
254+
# In Python 3.14+, atexit.register prepends to the list (LIFO),
255+
# so registering in forward order gives us the correct execution order.
205256
for callbackin exithandlers:
206257
atexit.register(callback[0],*callback[1],**callback[2])
207258

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp