https://github.com/python/cpython/commit/3758955e2fb844cd764a59ba8519c916877dd8f5
commit: 3758955e2fb844cd764a59ba8519c916877dd8f5
branch: main
author: Peter Bierma <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-10-20T12:55:15-04:00
summary:
gh-71990: Call `_as_parameter_` on `ctypes.POINTER` arguments (GH-159763)
files:
A Misc/NEWS.d/next/Library/2026-10-19-16-26-57.gh-issue-71990.K3FlJ9.rst
M Doc/library/ctypes.rst
M Doc/whatsnew/3.16.rst
M Lib/test/test_ctypes/test_as_parameter.py
M Lib/test/test_ctypes/test_parameters.py
M Modules/_ctypes/_ctypes.c
diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index bafe00a6a95703..311ab65e802310 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -1107,6 +1107,17 @@ In addition, if a function argument is explicitly
declared to be a pointer type
type (``c_int`` in this case) can be passed to the function. ctypes will apply
the required :func:`byref` conversion in this case automatically.
+These conversions are also applied to the :attr:`!_as_parameter_` attribute of
+an object passed for a pointer argument (see
+:ref:`ctypes-calling-functions-with-own-custom-data-types`). So, for a
+``POINTER(c_int)`` argument, an object whose :attr:`!_as_parameter_` is a
+``c_int`` instance is passed by reference, and one whose
:attr:`!_as_parameter_`
+is a ``c_int`` array is accepted as well.
+
+.. versionchanged:: next
+ Objects with an :attr:`!_as_parameter_` attribute now receive the same
+ implicit conversions for pointer arguments as the object they wrap.
+
To set a POINTER type field to ``NULL``, you can assign ``None``::
>>> bar.values = None
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 565cd7504d7ea9..58f129bedcb371 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -214,6 +214,10 @@ ctypes
They are only available with libffi 3.5 or later.
(Contributed by Serhiy Storchaka in :gh:`157487`.)
+* Objects with an :attr:`!_as_parameter_` attribute are now
+ implicitly converted when passed for a :func:`~ctypes.POINTER` argument.
+ (Contributed by Peter Bierma in :gh:`71990`.)
+
curses
------
diff --git a/Lib/test/test_ctypes/test_as_parameter.py
b/Lib/test/test_ctypes/test_as_parameter.py
index abbfb6efdcea55..8634c3f03a02ec 100644
--- a/Lib/test/test_ctypes/test_as_parameter.py
+++ b/Lib/test/test_ctypes/test_as_parameter.py
@@ -208,6 +208,7 @@ class A:
ctypes.c_void_p,
ctypes.c_int, # PyCSimpleType
POINT, # CDataType
+ POINTER(c_int), # PyCPointerType
):
with self.subTest(c_type=c_type):
with self.assertRaises(RecursionError):
diff --git a/Lib/test/test_ctypes/test_parameters.py
b/Lib/test/test_ctypes/test_parameters.py
index 6dadb7b410d703..ba3de0e5908494 100644
--- a/Lib/test/test_ctypes/test_parameters.py
+++ b/Lib/test/test_ctypes/test_parameters.py
@@ -15,6 +15,7 @@
c_long, c_ulong,
c_longlong, c_ulonglong,
c_float, c_double, c_longdouble)
+from ctypes.util import struct
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
@@ -168,6 +169,71 @@ def test_array_pointers(self):
self.assertRaises(TypeError, LPINT.from_param, c_long*3)
self.assertRaises(TypeError, LPINT.from_param, c_uint*3)
+ def test_pointer_as_parameter(self):
+ class Wrapper:
+ def __init__(self, param):
+ self._as_parameter_ = param
+
+ p_int = POINTER(c_int)
+
+ # Implicit byref() of a wrapped instance
+ v = c_int(42)
+ x = p_int.from_param(Wrapper(v))
+ self.assertIs(x._obj, v)
+
+ # Wrapped pointer
+ p = pointer(c_int(99))
+ self.assertIs(p_int.from_param(Wrapper(p)), p)
+
+ # Wrapped byref()
+ p_int.from_param(Wrapper(byref(c_int(7))))
+
+ # Wrapped array
+ a = (c_int * 3)()
+ self.assertIs(p_int.from_param(Wrapper(a)), a)
+
+ @struct
+ class Base:
+ x: c_int
+
+ class Derived(Base):
+ pass
+
+ p_base = POINTER(Base)
+
+ # Pointer upcast of a wrapped pointer and a wrapped instance.
+ d = Derived()
+ pd = pointer(d)
+ self.assertIs(p_base.from_param(Wrapper(pd)), pd)
+ self.assertIs(p_base.from_param(Wrapper(d))._obj, d)
+
+ # Nested _as_parameter_.
+ self.assertIs(p_int.from_param(Wrapper(Wrapper(Wrapper(v))))._obj, v)
+
+ # None is still accepted.
+ self.assertIsNone(p_int.from_param(Wrapper(None)))
+
+ # Type mismatches are still rejected.
+ with self.assertRaises(TypeError):
+ p_int.from_param(Wrapper(c_short(1)))
+ with self.assertRaises(TypeError):
+ p_int.from_param(Wrapper(pointer(c_short(1))))
+ with self.assertRaises(TypeError):
+ p_int.from_param(Wrapper(byref(c_short(1))))
+ with self.assertRaises(TypeError):
+ p_int.from_param(Wrapper((c_short * 3)()))
+ with self.assertRaises(TypeError):
+ p_int.from_param(Wrapper(object()))
+
+ # End-to-end through a function call. The c_int must stay alive
+ # while the returned pointer is used.
+ func = CDLL(_ctypes_test.__file__)._testfunc_p_p
+ func.restype = p_int
+ func.argtypes = [p_int]
+ v = c_int(123)
+ result = func(Wrapper(v))
+ self.assertEqual(result.contents.value, 123)
+
def test_noctypes_argtype(self):
func = CDLL(_ctypes_test.__file__)._testfunc_p_p
func.restype = c_void_p
diff --git
a/Misc/NEWS.d/next/Library/2026-10-19-16-26-57.gh-issue-71990.K3FlJ9.rst
b/Misc/NEWS.d/next/Library/2026-10-19-16-26-57.gh-issue-71990.K3FlJ9.rst
new file mode 100644
index 00000000000000..87276db65054a0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-19-16-26-57.gh-issue-71990.K3FlJ9.rst
@@ -0,0 +1,2 @@
+:mod:`ctypes`: objects with an :attr:`!_as_parameter_` attribute are now
+implicitly converted when passed for a :func:`~ctypes.POINTER` argument.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 67e4835ea28bdb..605925a8eaaae7 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -1436,6 +1436,21 @@ PyCPointerType_from_param_impl(PyObject *type,
PyTypeObject *cls,
return Py_NewRef(value);
}
}
+
+ PyObject *as_parameter;
+ if (PyObject_GetOptionalAttr(value, &_Py_ID(_as_parameter_),
&as_parameter) < 0) {
+ return NULL;
+ }
+ if (as_parameter) {
+ if (_Py_EnterRecursiveCall(" while processing _as_parameter_")) {
+ Py_DECREF(as_parameter);
+ return NULL;
+ }
+ value = PyCPointerType_from_param_impl(type, cls, as_parameter);
+ Py_DECREF(as_parameter);
+ _Py_LeaveRecursiveCall();
+ return value;
+ }
return CDataType_from_param_impl(type, cls, value);
}
_______________________________________________
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]