diff --git a/Lib/_py_warnings.py b/Lib/_py_warnings.py index cbaa94458629ac..5070caea6bb054 100644 --- a/Lib/_py_warnings.py +++ b/Lib/_py_warnings.py @@ -449,9 +449,12 @@ def warn(message, category=None, stacklevel=1, source=None, # Check category argument if category is None: category = UserWarning - if not (isinstance(category, type) and issubclass(category, Warning)): - raise TypeError("category must be a Warning subclass, " - "not '{:s}'".format(type(category).__name__)) + elif not isinstance(category, type): + raise TypeError(f"category must be a Warning subclass, not " + f"'{type(category).__name__}'") + elif not issubclass(category, Warning): + raise TypeError(f"category must be a Warning subclass, not " + f"class '{category.__name__}'") if not isinstance(skip_file_prefixes, tuple): # The C version demands a tuple for implementation performance. raise TypeError('skip_file_prefixes must be a tuple of strs.') diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index f89e94449b3031..694cfc97064c30 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -596,25 +596,19 @@ def test_warning_classes(self): class MyWarningClass(Warning): pass - class NonWarningSubclass: - pass - # passing a non-subclass of Warning should raise a TypeError - with self.assertRaises(TypeError) as cm: + expected = "category must be a Warning subclass, not 'str'" + with self.assertRaisesRegex(TypeError, expected): self.module.warn('bad warning category', '') - self.assertIn('category must be a Warning subclass, not ', - str(cm.exception)) - with self.assertRaises(TypeError) as cm: - self.module.warn('bad warning category', NonWarningSubclass) - self.assertIn('category must be a Warning subclass, not ', - str(cm.exception)) + expected = "category must be a Warning subclass, not class 'int'" + with self.assertRaisesRegex(TypeError, expected): + self.module.warn('bad warning category', int) # check that warning instances also raise a TypeError - with self.assertRaises(TypeError) as cm: + expected = "category must be a Warning subclass, not '.*MyWarningClass'" + with self.assertRaisesRegex(TypeError, expected): self.module.warn('bad warning category', MyWarningClass()) - self.assertIn('category must be a Warning subclass, not ', - str(cm.exception)) with self.module.catch_warnings(): self.module.resetwarnings() diff --git a/Misc/NEWS.d/next/Library/2025-08-14-10-27-07.gh-issue-125854.vDzFcZ.rst b/Misc/NEWS.d/next/Library/2025-08-14-10-27-07.gh-issue-125854.vDzFcZ.rst new file mode 100644 index 00000000000000..40925a4ab19fbc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-14-10-27-07.gh-issue-125854.vDzFcZ.rst @@ -0,0 +1 @@ +Improve error messages for invalid category in :func:`warnings.warn`. diff --git a/Python/_warnings.c b/Python/_warnings.c index 12e6172b0cf828..243a5e88e9dbbc 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -823,11 +823,7 @@ warn_explicit(PyThreadState *tstate, PyObject *category, PyObject *message, /* Normalize message. */ Py_INCREF(message); /* DECREF'ed in cleanup. */ - rc = PyObject_IsInstance(message, PyExc_Warning); - if (rc == -1) { - goto cleanup; - } - if (rc == 1) { + if (PyObject_TypeCheck(message, (PyTypeObject *)PyExc_Warning)) { text = PyObject_Str(message); if (text == NULL) goto cleanup; @@ -1124,26 +1120,25 @@ setup_context(Py_ssize_t stack_level, static PyObject * get_category(PyObject *message, PyObject *category) { - int rc; - - /* Get category. */ - rc = PyObject_IsInstance(message, PyExc_Warning); - if (rc == -1) - return NULL; - - if (rc == 1) - category = (PyObject*)Py_TYPE(message); - else if (category == NULL || category == Py_None) - category = PyExc_UserWarning; + if (PyObject_TypeCheck(message, (PyTypeObject *)PyExc_Warning)) { + /* Ignore the category argument. */ + return (PyObject*)Py_TYPE(message); + } + if (category == NULL || category == Py_None) { + return PyExc_UserWarning; + } /* Validate category. */ - rc = PyObject_IsSubclass(category, PyExc_Warning); - /* category is not a subclass of PyExc_Warning or - PyObject_IsSubclass raised an error */ - if (rc == -1 || rc == 0) { + if (!PyType_Check(category)) { + PyErr_Format(PyExc_TypeError, + "category must be a Warning subclass, not '%T'", + category); + return NULL; + } + if (!PyType_IsSubtype((PyTypeObject *)category, (PyTypeObject *)PyExc_Warning)) { PyErr_Format(PyExc_TypeError, - "category must be a Warning subclass, not '%s'", - Py_TYPE(category)->tp_name); + "category must be a Warning subclass, not class '%N'", + (PyTypeObject *)category); return NULL; }