Skip to content

Commit 8de770d

Browse files
committed
gh-105927: Add PyWeakref_GetRef() function
* Add tests on PyWeakref_NewRef(), PyWeakref_GetObject(), PyWeakref_GET_OBJECT() and PyWeakref_GetRef(). * PyWeakref_GetObject() now raises a TypeError if the argument is not a weak reference, instead of SystemError.
1 parent 4b431d2 commit 8de770d

12 files changed

+122
-1
lines changed

Doc/c-api/weakref.rst

+10
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@ as much as it can.
5151
``None``, or ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`.
5252
5353
54+
.. c:function:: int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
55+
56+
Get the referenced object from a weak reference, *ref*, into ``*pobj``.
57+
Return 0 on success. Raise an exception and return -1 on error.
58+
59+
If the referent is no longer live, set ``*pobj`` to ``NULL`` and return 0.
60+
61+
.. versionadded:: 3.13
62+
63+
5464
.. c:function:: PyObject* PyWeakref_GetObject(PyObject *ref)
5565
5666
Return the referenced object from a weak reference, *ref*. If the referent is

Doc/data/refcounts.dat

+4
Original file line numberDiff line numberDiff line change
@@ -2810,6 +2810,10 @@ PyWeakref_GET_OBJECT:PyObject*:ref:0:
28102810
PyWeakref_GetObject:PyObject*::0:
28112811
PyWeakref_GetObject:PyObject*:ref:0:
28122812

2813+
PyWeakref_GetRef:int:::
2814+
PyWeakref_GetRef:PyObject*:ref:0:
2815+
PyWeakref_GetRef:PyObject**:pobj:+1:
2816+
28132817
PyWeakref_NewProxy:PyObject*::+1:
28142818
PyWeakref_NewProxy:PyObject*:ob:0:
28152819
PyWeakref_NewProxy:PyObject*:callback:0:

Doc/data/stable_abi.dat

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.13.rst

+4
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,10 @@ New Features
431431
of a :term:`borrowed reference`.
432432
(Contributed by Victor Stinner in :gh:`105922`.)
433433

434+
* Add :c:func:`PyWeakref_GetRef` function: similar to
435+
:c:func:`PyWeakref_GetObject` but returns a :term:`strong reference`, or
436+
``NULL`` if the referent is no longer live.
437+
(Contributed by Victor Stinner in :gh:`105927`.)
434438

435439
Porting to Python 3.13
436440
----------------------

Include/weakrefobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ PyAPI_FUNC(PyObject *) PyWeakref_NewRef(PyObject *ob,
2828
PyAPI_FUNC(PyObject *) PyWeakref_NewProxy(PyObject *ob,
2929
PyObject *callback);
3030
PyAPI_FUNC(PyObject *) PyWeakref_GetObject(PyObject *ref);
31+
PyAPI_FUNC(int) PyWeakref_GetRef(PyObject *ref, PyObject **pobj);
3132

3233

3334
#ifndef Py_LIMITED_API

Lib/test/test_stable_abi_ctypes.py

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_weakref.py

+4
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,10 @@ def __del__(self): pass
967967
del x
968968
support.gc_collect()
969969

970+
@support.cpython_only
971+
def test_capi(self):
972+
import _testcapi
973+
_testcapi.check_weakref_capi(C)
970974

971975
class SubclassableWeakrefTestCase(TestBase):
972976

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`PyWeakref_GetRef` function: similar to
2+
:c:func:`PyWeakref_GetObject` but returns a :term:`strong reference`, or
3+
``NULL`` if the referent is no longer live. Patch by Victor Stinner.

Misc/stable_abi.toml

+2
Original file line numberDiff line numberDiff line change
@@ -2430,3 +2430,5 @@
24302430
added = '3.12'
24312431
[function.PyImport_AddModuleRef]
24322432
added = '3.13'
2433+
[function.PyWeakref_GetRef]
2434+
added = '3.13'

Modules/_testcapimodule.c

+68
Original file line numberDiff line numberDiff line change
@@ -3372,6 +3372,73 @@ check_pyimport_addmodule(PyObject *self, PyObject *args)
33723372
}
33733373

33743374

3375+
static PyObject *
3376+
check_weakref_capi(PyObject *self, PyObject *factory)
3377+
{
3378+
// obj = factory()
3379+
PyObject *obj = PyObject_CallNoArgs(factory);
3380+
if (obj == NULL) {
3381+
return NULL;
3382+
}
3383+
Py_ssize_t refcnt = Py_REFCNT(obj);
3384+
assert(refcnt == 1);
3385+
3386+
// test PyWeakref_GetRef()
3387+
PyObject *weakref = PyWeakref_NewRef(obj, NULL);
3388+
if (weakref == NULL) {
3389+
Py_DECREF(obj);
3390+
return NULL;
3391+
}
3392+
assert(PyWeakref_Check(weakref));
3393+
assert(PyWeakref_CheckRefExact(weakref));
3394+
assert(PyWeakref_CheckRefExact(weakref));
3395+
assert(Py_REFCNT(obj) == refcnt);
3396+
3397+
// test PyWeakref_GetRef()
3398+
PyObject *ref1;
3399+
assert(PyWeakref_GetRef(weakref, &ref1) == 0);
3400+
assert(ref1 == obj);
3401+
assert(Py_REFCNT(obj) == (refcnt + 1));
3402+
Py_DECREF(ref1);
3403+
3404+
// test PyWeakref_GetObject()
3405+
PyObject *ref2 = PyWeakref_GetObject(weakref);
3406+
assert(ref2 == obj);
3407+
assert(Py_REFCNT(obj) == refcnt);
3408+
3409+
// test PyWeakref_GET_OBJECT()
3410+
PyObject *ref3 = PyWeakref_GET_OBJECT(weakref);
3411+
assert(ref3 == obj);
3412+
assert(Py_REFCNT(obj) == refcnt);
3413+
3414+
// delete the object
3415+
assert(refcnt == 1);
3416+
Py_DECREF(obj);
3417+
assert(PyWeakref_GET_OBJECT(weakref) == Py_None);
3418+
PyObject *ref4;
3419+
assert(PyWeakref_GetRef(weakref, &ref4) == 0);
3420+
assert(ref4 == NULL);
3421+
3422+
// None is not a weak reference object
3423+
PyObject *invalid_weakref = Py_None;
3424+
assert(!PyWeakref_Check(invalid_weakref));
3425+
assert(!PyWeakref_CheckRefExact(invalid_weakref));
3426+
assert(!PyWeakref_CheckRefExact(invalid_weakref));
3427+
3428+
assert(!PyErr_Occurred());
3429+
PyObject *ref5 = factory; // marker to check that value was set
3430+
assert(PyWeakref_GetRef(invalid_weakref, &ref5) == -1);
3431+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
3432+
PyErr_Clear();
3433+
3434+
assert(PyWeakref_GetObject(invalid_weakref) == NULL);
3435+
assert(PyErr_ExceptionMatches(PyExc_TypeError));
3436+
PyErr_Clear();
3437+
3438+
Py_RETURN_NONE;
3439+
}
3440+
3441+
33753442
static PyMethodDef TestMethods[] = {
33763443
{"set_errno", set_errno, METH_VARARGS},
33773444
{"test_config", test_config, METH_NOARGS},
@@ -3516,6 +3583,7 @@ static PyMethodDef TestMethods[] = {
35163583
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
35173584
{"test_atexit", test_atexit, METH_NOARGS},
35183585
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
3586+
{"check_weakref_capi", check_weakref_capi, METH_O},
35193587
{NULL, NULL} /* sentinel */
35203588
};
35213589

Objects/weakrefobject.c

+23-1
Original file line numberDiff line numberDiff line change
@@ -894,13 +894,35 @@ PyWeakref_NewProxy(PyObject *ob, PyObject *callback)
894894
}
895895

896896

897+
int
898+
PyWeakref_GetRef(PyObject *ref, PyObject **pobj)
899+
{
900+
if (ref == NULL) {
901+
*pobj = NULL;
902+
PyErr_BadInternalCall();
903+
return -1;
904+
}
905+
if (!PyWeakref_Check(ref)) {
906+
*pobj = NULL;
907+
PyErr_SetString(PyExc_TypeError, "expected a weakref");
908+
return -1;
909+
}
910+
*pobj = _PyWeakref_GET_REF(ref);
911+
return 0;
912+
}
913+
914+
897915
PyObject *
898916
PyWeakref_GetObject(PyObject *ref)
899917
{
900-
if (ref == NULL || !PyWeakref_Check(ref)) {
918+
if (ref == NULL) {
901919
PyErr_BadInternalCall();
902920
return NULL;
903921
}
922+
if (!PyWeakref_Check(ref)) {
923+
PyErr_SetString(PyExc_TypeError, "expected a weakref");
924+
return NULL;
925+
}
904926
return PyWeakref_GET_OBJECT(ref);
905927
}
906928

PC/python3dll.c

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)