diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 956fa290f0ad0e7..e5f38f9e887da30 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -660,6 +660,7 @@ struct _warnings_runtime_state { PyObject *filters; /* List */ PyObject *once_registry; /* Dict */ PyObject *default_action; /* String */ + PyObject *module; /* Weakref, used after sys.modules cleanup */ _PyRecursiveMutex lock; long filters_version; PyObject *context; diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index bf1bcf8e6ed5d9a..088f1857d40252c 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -1693,6 +1693,30 @@ def __del__(self): self.assertEqual(err.decode().rstrip(), ':7: UserWarning: test') + def test_showwarning_during_finalization(self): + # gh-149211: warnings.warn() should use a custom showwarning() + # during Python finalization. + code = """ +import sys +import warnings + +def showwarning(message, category, filename, lineno, + file=None, line=None, stderr=sys.stderr): + print(f"custom showwarning: {message}", file=file or stderr) + +warnings.showwarning = showwarning + +class A: + def __del__(self): + warnings.warn("test") + +a = A() + """ + rc, out, err = assert_python_ok("-c", code) + self.assertEqual(out, b"") + self.assertEqual(err.decode().rstrip(), + "custom showwarning: test") + def test_late_resource_warning(self): # Issue #21925: Emitting a ResourceWarning late during the Python # shutdown must be logged. diff --git a/Lib/warnings.py b/Lib/warnings.py index 6759857d9093992..d7a9bd6406c8628 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -68,6 +68,7 @@ _filters_mutated_lock_held, _onceregistry as onceregistry, _release_lock, + _set_module as _set_c_module, _warnings_context, filters, warn, @@ -91,9 +92,13 @@ def __exit__(self, *args): # Module initialization _set_module(sys.modules[__name__]) +if _warnings_defaults: + _set_c_module(sys.modules[__name__]) _processoptions(sys.warnoptions) if not _warnings_defaults: _setup_defaults() del _warnings_defaults +if "_set_c_module" in globals(): + del _set_c_module del _setup_defaults diff --git a/Misc/NEWS.d/next/Library/2026-06-05-21-05-00.gh-issue-149211.a1b2c3.rst b/Misc/NEWS.d/next/Library/2026-06-05-21-05-00.gh-issue-149211.a1b2c3.rst new file mode 100644 index 000000000000000..6416e45a715ce6b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-05-21-05-00.gh-issue-149211.a1b2c3.rst @@ -0,0 +1,2 @@ +Fix :func:`warnings.warn` to call a custom :func:`warnings.showwarning` +during Python finalization. diff --git a/Python/_warnings.c b/Python/_warnings.c index 4f6de50efa14a8e..cff7b00f6b8aabb 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -69,6 +69,7 @@ warnings_clear_state(WarningsState *st) Py_CLEAR(st->filters); Py_CLEAR(st->once_registry); Py_CLEAR(st->default_action); + Py_CLEAR(st->module); Py_CLEAR(st->context); } @@ -245,8 +246,13 @@ get_warnings_attr(PyInterpreterState *interp, PyObject *attr, int try_import) return NULL; } warnings_module = PyImport_GetModule(&_Py_ID(warnings)); - if (warnings_module == NULL) - return NULL; + if (warnings_module == NULL) { + WarningsState *st = warnings_get_state(interp); + if (st->module == NULL || + PyWeakref_GetRef(st->module, &warnings_module) <= 0) { + return NULL; + } + } } (void)PyObject_GetOptionalAttr(warnings_module, attr, &obj); @@ -375,6 +381,25 @@ warnings_release_lock_impl(PyObject *module) Py_RETURN_NONE; } +static PyObject * +warnings_set_module(PyObject *Py_UNUSED(module), PyObject *warnings_module) +{ + PyInterpreterState *interp = get_current_interp(); + if (interp == NULL) { + return NULL; + } + WarningsState *st = warnings_get_state(interp); + if (st == NULL) { + return NULL; + } + PyObject *ref = PyWeakref_NewRef(warnings_module, NULL); + if (ref == NULL) { + return NULL; + } + Py_XSETREF(st->module, ref); + Py_RETURN_NONE; +} + static PyObject * get_once_registry(PyInterpreterState *interp) { @@ -1593,6 +1618,7 @@ static PyMethodDef warnings_functions[] = { WARNINGS_FILTERS_MUTATED_LOCK_HELD_METHODDEF WARNINGS_ACQUIRE_LOCK_METHODDEF WARNINGS_RELEASE_LOCK_METHODDEF + {"_set_module", warnings_set_module, METH_O, NULL}, /* XXX(brett.cannon): add showwarning? */ /* XXX(brett.cannon): Reasonable to add formatwarning? */ {NULL, NULL} /* sentinel */