https://github.com/python/cpython/commit/ca32ebf7933742e3b536cdc0619b0e24cce36065
commit: ca32ebf7933742e3b536cdc0619b0e24cce36065
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-10T13:34:55+03:00
summary:

gh-80384: Check that callback is callable at weak reference creation (GH-151145)

* Python functions weakref.ref() and weakref.proxy() now raise TypeError
  if the callback argument is not callable or None.
* C functions PyWeakref_NewRef() and PyWeakref_NewProxy() now raise TypeError
  if the callback argument is not callable, None, or NULL.

Co-authored-by: Maxwell Bernstein <[email protected]>

files:
A Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst
A Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst
M Doc/c-api/weakref.rst
M Doc/library/weakref.rst
M Lib/test/test_capi/test_weakref.py
M Lib/test/test_traceback.py
M Lib/test/test_weakref.py
M Objects/weakrefobject.c

diff --git a/Doc/c-api/weakref.rst b/Doc/c-api/weakref.rst
index db6ae0a9d4ea3d7..8762a003c5218d3 100644
--- a/Doc/c-api/weakref.rst
+++ b/Doc/c-api/weakref.rst
@@ -43,7 +43,11 @@ as much as it can.
    should accept a single parameter, which will be the weak reference object
    itself. *callback* may also be ``None`` or ``NULL``.  If *ob* is not a
    weakly referenceable object, or if *callback* is not callable, ``None``, or
-   ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`.
+   ``NULL``, this will raise :exc:`TypeError` and return ``NULL``.
+
+   .. versionchanged:: next
+      Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or
+      ``NULL``.
 
    .. seealso::
       :c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly
@@ -59,7 +63,11 @@ as much as it can.
    collected; it should accept a single parameter, which will be the weak
    reference object itself. *callback* may also be ``None`` or ``NULL``.  If 
*ob*
    is not a weakly referenceable object, or if *callback* is not callable,
-   ``None``, or ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`.
+   ``NULL``, this will raise :exc:`TypeError` and return ``NULL``.
+
+   .. versionchanged:: next
+      Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or
+      ``NULL``.
 
    .. seealso::
       :c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly
diff --git a/Doc/library/weakref.rst b/Doc/library/weakref.rst
index fcb9e0199fad69e..952609c0e700e5e 100644
--- a/Doc/library/weakref.rst
+++ b/Doc/library/weakref.rst
@@ -132,6 +132,9 @@ See :ref:`__slots__ documentation <slots>` for details.
    .. versionchanged:: 3.4
       Added the :attr:`__callback__` attribute.
 
+   .. versionchanged:: next
+      Raise :exc:`!TypeError` if *callback* is not callable or ``None``.
+
 
 .. function:: proxy(object[, callback])
 
@@ -151,6 +154,9 @@ See :ref:`__slots__ documentation <slots>` for details.
       Extended the operator support on proxy objects to include the matrix
       multiplication operators ``@`` and ``@=``.
 
+   .. versionchanged:: next
+      Raise :exc:`!TypeError` if *callback* is not callable or ``None``.
+
 
 .. function:: getweakrefcount(object)
 
diff --git a/Lib/test/test_capi/test_weakref.py 
b/Lib/test/test_capi/test_weakref.py
index 86ebe92da8d95db..7c9e97f571979dc 100644
--- a/Lib/test/test_capi/test_weakref.py
+++ b/Lib/test/test_capi/test_weakref.py
@@ -97,6 +97,7 @@ def test_pyweakref_newref(self):
         # PyWeakref_NewRef() handles None callback as NULL callback
         wr = newref(obj, None)
         self.assertIs(type(wr), weakref.ReferenceType)
+        self.assertRaises(TypeError, newref, obj, 42)
         log = []
         wr = newref(obj, log.append)
         self.assertIs(type(wr), weakref.ReferenceType)
@@ -116,6 +117,7 @@ def test_pyweakref_newproxy(self):
         # PyWeakref_NewProxy() handles None callback as NULL callback
         wp = newproxy(obj, None)
         self.assertIs(type(wp), weakref.ProxyType)
+        self.assertRaises(TypeError, newproxy, obj, 42)
         log = []
         wp = newproxy(obj, log.append)
         self.assertIs(type(wp), weakref.ProxyType)
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 7dc3364561d8a11..2c6324a14a8e2fc 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -206,7 +206,7 @@ def test_recursion_error_during_traceback(self):
                 sys.setrecursionlimit(15)
 
                 def f():
-                    ref(lambda: 0, [])
+                    ref(lambda: 0, ord)
                     f()
 
                 try:
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index b187643e84521cc..0c4717521f16f67 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -167,6 +167,11 @@ def test_basic_callback(self):
         self.check_basic_callback(create_function)
         self.check_basic_callback(create_bound_method)
 
+    def test_non_callable_callback(self):
+        c = C()
+        self.assertRaises(TypeError, weakref.ref, c, 42)
+        self.assertRaises(TypeError, weakref.proxy, c, 42)
+
     @support.cpython_only
     def test_cfunction(self):
         _testcapi = import_helper.import_module("_testcapi")
diff --git 
a/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst 
b/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst
new file mode 100644
index 000000000000000..c4f5e8f3f9bbc2d
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst
@@ -0,0 +1,3 @@
+:c:func:`PyWeakref_NewRef` and :c:func:`PyWeakref_NewProxy` now raise
+:exc:`TypeError` if the *callback* argument is not callable, ``None``, or
+``NULL``.
diff --git 
a/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst 
b/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst
new file mode 100644
index 000000000000000..53d4d513ba757de
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst
@@ -0,0 +1,2 @@
+:func:`weakref.ref` and :func:`weakref.proxy` now raise :exc:`TypeError` if
+the *callback* argument is not callable or ``None``.
diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c
index 8446a2dbcf75593..daeba5368b21660 100644
--- a/Objects/weakrefobject.c
+++ b/Objects/weakrefobject.c
@@ -416,8 +416,15 @@ get_or_create_weakref(PyTypeObject *type, PyObject *obj, 
PyObject *callback)
                      Py_TYPE(obj)->tp_name);
         return NULL;
     }
-    if (callback == Py_None)
+    if (callback == Py_None) {
         callback = NULL;
+    }
+    if (callback != NULL && !PyCallable_Check(callback)) {
+        PyErr_Format(PyExc_TypeError,
+                     "callback must be callable or None, not '%T'",
+                     callback);
+        return NULL;
+    }
 
     PyWeakReference **list = GET_WEAKREFS_LISTPTR(obj);
     if ((type == &_PyWeakref_RefType) ||

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to