Skip to content

Commit a0bc0c4

Browse files
sergey-miryanovneonenejunkmdencukou
authored
gh-100926: Move ctype's pointers cache from _pointer_type_cache to StgInfo (GH-131282)
Deprecate _pointer_type_cache and calling POINTER on a string. Co-authored-by: neonene <[email protected]> Co-authored-by: Jun Komoda <[email protected]> Co-authored-by: Petr Viktorin <[email protected]>
1 parent 7e7e49b commit a0bc0c4

17 files changed

+697
-213
lines changed

Doc/library/ctypes.rst

+21-1
Original file line numberDiff line numberDiff line change
@@ -2172,10 +2172,20 @@ Utility functions
21722172

21732173
.. function:: POINTER(type, /)
21742174

2175-
Create and return a new ctypes pointer type. Pointer types are cached and
2175+
Create or return a ctypes pointer type. Pointer types are cached and
21762176
reused internally, so calling this function repeatedly is cheap.
21772177
*type* must be a ctypes type.
21782178

2179+
.. impl-detail::
2180+
2181+
The resulting pointer type is cached in the ``__pointer_type__``
2182+
attribute of *type*.
2183+
It is possible to set this attribute before the first call to
2184+
``POINTER`` in order to set a custom pointer type.
2185+
However, doing this is discouraged: manually creating a suitable
2186+
pointer type is difficult without relying on implementation
2187+
details that may change in future Python versions.
2188+
21792189

21802190
.. function:: pointer(obj, /)
21812191

@@ -2340,6 +2350,16 @@ Data types
23402350
library. *name* is the name of the symbol that exports the data, *library*
23412351
is the loaded shared library.
23422352

2353+
Common class variables of ctypes data types:
2354+
2355+
.. attribute:: __pointer_type__
2356+
2357+
The pointer type that was created by calling
2358+
:func:`POINTER` for corresponding ctypes data type. If a pointer type
2359+
was not yet created, the attribute is missing.
2360+
2361+
.. versionadded:: next
2362+
23432363
Common instance variables of ctypes data types:
23442364

23452365
.. attribute:: _b_base_

Doc/whatsnew/3.14.rst

+13-1
Original file line numberDiff line numberDiff line change
@@ -807,11 +807,16 @@ ctypes
807807
loaded by the current process.
808808
(Contributed by Brian Ward in :gh:`119349`.)
809809

810+
* Move :func:`ctypes.POINTER` types cache from a global internal cache
811+
(``_pointer_type_cache``) to the :attr:`ctypes._CData.__pointer_type__`
812+
attribute of the corresponding :mod:`ctypes` types.
813+
This will stop the cache from growing without limits in some situations.
814+
(Contributed by Sergey Miryanov in :gh:`100926`).
815+
810816
* The :class:`ctypes.py_object` type now supports subscription,
811817
making it a :term:`generic type`.
812818
(Contributed by Brian Schubert in :gh:`132168`.)
813819

814-
815820
datetime
816821
--------
817822

@@ -1679,6 +1684,13 @@ Deprecated
16791684
:func:`codecs.open` is now deprecated. Use :func:`open` instead.
16801685
(Contributed by Inada Naoki in :gh:`133036`.)
16811686

1687+
* :mod:`ctypes`:
1688+
Calling :func:`ctypes.POINTER` on a string is deprecated.
1689+
Use :ref:`ctypes-incomplete-types` for self-referential structures.
1690+
Also, the internal ``ctypes._pointer_type_cache`` is deprecated.
1691+
See :func:`ctypes.POINTER` for updated implementation details.
1692+
(Contributed by Sergey Myrianov in :gh:`100926`.)
1693+
16821694
* :mod:`functools`:
16831695
Calling the Python implementation of :func:`functools.reduce` with *function*
16841696
or *sequence* as keyword arguments is now deprecated.

Lib/ctypes/__init__.py

+67-9
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,72 @@ class c_void_p(_SimpleCData):
266266
class c_bool(_SimpleCData):
267267
_type_ = "?"
268268

269-
from _ctypes import POINTER, pointer, _pointer_type_cache
269+
def POINTER(cls):
270+
"""Create and return a new ctypes pointer type.
271+
272+
Pointer types are cached and reused internally,
273+
so calling this function repeatedly is cheap.
274+
"""
275+
if cls is None:
276+
return c_void_p
277+
try:
278+
return cls.__pointer_type__
279+
except AttributeError:
280+
pass
281+
if isinstance(cls, str):
282+
# handle old-style incomplete types (see test_ctypes.test_incomplete)
283+
import warnings
284+
warnings._deprecated("ctypes.POINTER with string", remove=(3, 19))
285+
try:
286+
return _pointer_type_cache_fallback[cls]
287+
except KeyError:
288+
result = type(f'LP_{cls}', (_Pointer,), {})
289+
_pointer_type_cache_fallback[cls] = result
290+
return result
291+
292+
# create pointer type and set __pointer_type__ for cls
293+
return type(f'LP_{cls.__name__}', (_Pointer,), {'_type_': cls})
294+
295+
def pointer(obj):
296+
"""Create a new pointer instance, pointing to 'obj'.
297+
298+
The returned object is of the type POINTER(type(obj)). Note that if you
299+
just want to pass a pointer to an object to a foreign function call, you
300+
should use byref(obj) which is much faster.
301+
"""
302+
typ = POINTER(type(obj))
303+
return typ(obj)
304+
305+
class _PointerTypeCache:
306+
def __setitem__(self, cls, pointer_type):
307+
import warnings
308+
warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
309+
try:
310+
cls.__pointer_type__ = pointer_type
311+
except AttributeError:
312+
_pointer_type_cache_fallback[cls] = pointer_type
313+
314+
def __getitem__(self, cls):
315+
import warnings
316+
warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
317+
try:
318+
return cls.__pointer_type__
319+
except AttributeError:
320+
return _pointer_type_cache_fallback[cls]
321+
322+
def get(self, cls, default=None):
323+
import warnings
324+
warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
325+
try:
326+
return cls.__pointer_type__
327+
except AttributeError:
328+
return _pointer_type_cache_fallback.get(cls, default)
329+
330+
def __contains__(self, cls):
331+
return hasattr(cls, '__pointer_type__')
332+
333+
_pointer_type_cache_fallback = {}
334+
_pointer_type_cache = _PointerTypeCache()
270335

271336
class c_wchar_p(_SimpleCData):
272337
_type_ = "Z"
@@ -277,15 +342,14 @@ class c_wchar(_SimpleCData):
277342
_type_ = "u"
278343

279344
def _reset_cache():
280-
_pointer_type_cache.clear()
345+
_pointer_type_cache_fallback.clear()
281346
_c_functype_cache.clear()
282347
if _os.name == "nt":
283348
_win_functype_cache.clear()
284349
# _SimpleCData.c_wchar_p_from_param
285350
POINTER(c_wchar).from_param = c_wchar_p.from_param
286351
# _SimpleCData.c_char_p_from_param
287352
POINTER(c_char).from_param = c_char_p.from_param
288-
_pointer_type_cache[None] = c_void_p
289353

290354
def create_unicode_buffer(init, size=None):
291355
"""create_unicode_buffer(aString) -> character array
@@ -319,13 +383,7 @@ def create_unicode_buffer(init, size=None):
319383
def SetPointerType(pointer, cls):
320384
import warnings
321385
warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
322-
if _pointer_type_cache.get(cls, None) is not None:
323-
raise RuntimeError("This type already exists in the cache")
324-
if id(pointer) not in _pointer_type_cache:
325-
raise RuntimeError("What's this???")
326386
pointer.set_type(cls)
327-
_pointer_type_cache[cls] = pointer
328-
del _pointer_type_cache[id(pointer)]
329387

330388
def ARRAY(typ, len):
331389
return typ * len

Lib/test/test_ctypes/test_byteswap.py

-2
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ class TestStructure(parent):
232232
self.assertEqual(len(data), sizeof(TestStructure))
233233
ptr = POINTER(TestStructure)
234234
s = cast(data, ptr)[0]
235-
del ctypes._pointer_type_cache[TestStructure]
236235
self.assertEqual(s.point.x, 1)
237236
self.assertEqual(s.point.y, 2)
238237

@@ -371,7 +370,6 @@ class TestUnion(parent):
371370
self.assertEqual(len(data), sizeof(TestUnion))
372371
ptr = POINTER(TestUnion)
373372
s = cast(data, ptr)[0]
374-
del ctypes._pointer_type_cache[TestUnion]
375373
self.assertEqual(s.point.x, 1)
376374
self.assertEqual(s.point.y, 2)
377375

0 commit comments

Comments
 (0)