Skip to content

Commit dfd529c

Browse files
Merge encukou seggestions from PR
2 parents 78b6c15 + 0b5de27 commit dfd529c

File tree

7 files changed

+138
-62
lines changed

7 files changed

+138
-62
lines changed

Doc/whatsnew/3.14.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,14 @@ Deprecated
15801580
as a single positional argument.
15811581
(Contributed by Serhiy Storchaka in :gh:`109218`.)
15821582

1583+
* :mod:`ctypes`:
1584+
Calling :func:`ctypes.POINTER` on a string is deprecated.
1585+
Use :ref:`ctypes-incomplete-types` for self-referential structures.
1586+
Also, ``ctypes._pointer_type_cache`` is deprecated as a courtesy to
1587+
existing users of this internal API.
1588+
See :func:`ctypes.POINTER` for updated implementation details.
1589+
(Contributed by Sergey Myrianov in :gh:`100926`.)
1590+
15831591
* :mod:`functools`:
15841592
Calling the Python implementation of :func:`functools.reduce` with *function*
15851593
or *sequence* as keyword arguments is now deprecated.

Lib/ctypes/__init__.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,15 @@ def POINTER(cls):
279279
except AttributeError:
280280
pass
281281
if isinstance(cls, str):
282-
# handle old-style incomplete types
283-
# in this case pointer type is not cached and calling this function
284-
# repeatedly will give different result
285-
return type(f'LP_{cls}', (_Pointer,), {})
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
286291

287292
# create pointer type and set __pointer_type__ for cls
288293
return type(f'LP_{cls.__name__}', (_Pointer,), {'_type_': cls})
@@ -297,22 +302,22 @@ def pointer(obj):
297302
typ = POINTER(type(obj))
298303
return typ(obj)
299304

300-
class PointerTypeCache:
305+
class _PointerTypeCache:
301306
def __setitem__(self, cls, pointer_type):
302307
import warnings
303308
warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
304309
try:
305310
cls.__pointer_type__ = pointer_type
306311
except AttributeError:
307-
pass
312+
_pointer_type_cache_fallback[cls] = pointer_type
308313

309314
def __getitem__(self, cls):
310315
import warnings
311316
warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
312317
try:
313318
return cls.__pointer_type__
314319
except AttributeError:
315-
raise KeyError(cls)
320+
return _pointer_type_cache_fallback[cls]
316321

317322
def get(self, cls, default=None):
318323
import warnings
@@ -325,7 +330,8 @@ def get(self, cls, default=None):
325330
def __contains__(self, cls):
326331
return hasattr(cls, '__pointer_type__')
327332

328-
_pointer_type_cache = PointerTypeCache()
333+
_pointer_type_cache_fallback = {}
334+
_pointer_type_cache = _PointerTypeCache()
329335

330336
class c_wchar_p(_SimpleCData):
331337
_type_ = "Z"
@@ -376,9 +382,6 @@ def create_unicode_buffer(init, size=None):
376382
def SetPointerType(pointer, cls):
377383
import warnings
378384
warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
379-
if _pointer_type_cache.get(cls, None) is not None:
380-
raise RuntimeError("This type already exists in the cache")
381-
382385
pointer.set_type(cls)
383386

384387
def ARRAY(typ, len):

Lib/test/test_ctypes/test_c_simple_type_meta.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ def set_non_ctypes_pointer_type(cls, pointer_type):
1010
cls.__pointer_type__ = pointer_type
1111

1212
class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
13-
def tearDown(self):
14-
ctypes._reset_cache()
15-
1613
def test_creating_pointer_in_dunder_new_1(self):
1714
# Test metaclass whose instances are C types; when the type is
1815
# created it automatically creates a pointer type for itself.

Lib/test/test_ctypes/test_incomplete.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@
33
import warnings
44
from ctypes import Structure, POINTER, pointer, c_char_p
55

6+
# String-based "incomplete pointers" wers implemented ctypes 0.6.3 (2003, when
7+
# ctypes was an external project). They made obsolete by the current
8+
# incomplete *types* (setting `_fields_` late) in 0.9.5 (2005).
9+
# ctypes was added to Python 2.5 (2006), without any mention in docs.
610

7-
# The incomplete pointer example from the tutorial
11+
# This tests incomplete pointer example from the old tutorial
12+
# (https://svn.python.org/projects/ctypes/tags/release_0_6_3/ctypes/docs/tutorial.stx)
813
class TestSetPointerType(unittest.TestCase):
914
def tearDown(self):
10-
ctypes._reset_cache()
15+
ctypes._pointer_type_cache_fallback.clear()
1116

1217
def test_incomplete_example(self):
13-
lpcell = POINTER("cell")
18+
with self.assertWarns(DeprecationWarning):
19+
lpcell = POINTER("cell")
1420
class cell(Structure):
1521
_fields_ = [("name", c_char_p),
1622
("next", lpcell)]
@@ -38,7 +44,8 @@ class cell(Structure):
3844
self.assertEqual(result, [b"foo", b"bar"] * 4)
3945

4046
def test_deprecation(self):
41-
lpcell = POINTER("cell")
47+
with self.assertWarns(DeprecationWarning):
48+
lpcell = POINTER("cell")
4249
class cell(Structure):
4350
_fields_ = [("name", c_char_p),
4451
("next", lpcell)]

Lib/test/test_ctypes/test_pointers.py

Lines changed: 81 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
1111
c_long, c_ulong, c_longlong, c_ulonglong,
1212
c_float, c_double)
13-
from ctypes import _pointer_type_cache
13+
from ctypes import _pointer_type_cache, _pointer_type_cache_fallback
1414
from test.support import import_helper
1515
from weakref import WeakSet
1616
_ctypes_test = import_helper.import_module("_ctypes_test")
@@ -25,6 +25,9 @@
2525

2626

2727
class PointersTestCase(unittest.TestCase):
28+
def tearDown(self):
29+
_pointer_type_cache_fallback.clear()
30+
2831
def test_inheritance_hierarchy(self):
2932
self.assertEqual(_Pointer.mro(), [_Pointer, _CData, object])
3033

@@ -246,7 +249,8 @@ def test_pointer_type_name(self):
246249

247250
def test_pointer_type_str_name(self):
248251
large_string = 'T' * 2 ** 25
249-
P = POINTER(large_string)
252+
with self.assertWarns(DeprecationWarning):
253+
P = POINTER(large_string)
250254
self.assertTrue(P)
251255

252256
def test_abstract(self):
@@ -267,14 +271,17 @@ def test_pointer_types_equal(self):
267271
self.assertIs(type(p1), t1)
268272
self.assertIs(type(p2), t1)
269273

270-
def test_incomplete_pointer_types_not_equal(self):
271-
t1 = POINTER("LP_C")
272-
t2 = POINTER("LP_C")
274+
def test_incomplete_pointer_types_still_equal(self):
275+
with self.assertWarns(DeprecationWarning):
276+
t1 = POINTER("LP_C")
277+
with self.assertWarns(DeprecationWarning):
278+
t2 = POINTER("LP_C")
273279

274-
self.assertIsNot(t1, t2)
280+
self.assertIs(t1, t2)
275281

276282
def test_incomplete_pointer_types_cannot_instantiate(self):
277-
t1 = POINTER("LP_C")
283+
with self.assertWarns(DeprecationWarning):
284+
t1 = POINTER("LP_C")
278285
with self.assertRaisesRegex(TypeError, "has no _type_"):
279286
t1()
280287

@@ -288,15 +295,29 @@ def test_pointer_set_type_twice(self):
288295
self.assertIs(t1._type_, c_int)
289296

290297
def test_pointer_set_wrong_type(self):
291-
class C(c_int):
292-
pass
293-
294-
t1 = POINTER(c_int)
295-
with self.assertRaisesRegex(TypeError, "pointer type already set"):
298+
int_ptr = POINTER(c_int)
299+
float_ptr = POINTER(float_ptr)
300+
try:
301+
class C(c_int):
302+
pass
303+
304+
t1 = POINTER(c_int)
305+
t2 = POINTER(c_float)
296306
t1.set_type(c_float)
307+
self.assertEqual(t1(c_float(1.5))[0], 1.5)
308+
self.assertIs(c_int._type_, c_float)
309+
self.assertIs(c_int.__pointer_type__, t1)
310+
self.assertIs(c_float.__pointer_type__, float_ptr)
297311

298-
with self.assertRaisesRegex(TypeError, "cls type already set"):
299312
t1.set_type(C)
313+
self.assertEqual(t1(C(123))[0].value, 123)
314+
self.assertIs(c_int.__pointer_type__, t1)
315+
self.assertIs(c_float.__pointer_type__, float_ptr)
316+
finally:
317+
POINTER(c_int).set_type(c_int)
318+
self.assertIs(POINTER(c_int), int_ptr)
319+
self.assertIs(POINTER(c_int)._type_, c_int)
320+
self.assertIs(c_int.__pointer_type__, int_ptr)
300321

301322
def test_pointer_not_ctypes_type(self):
302323
with self.assertRaisesRegex(TypeError, "must have storage info"):
@@ -326,6 +347,34 @@ class Cls(Structure):
326347
p = POINTER(Cls)
327348
self.assertIs(Cls.__pointer_type__, p)
328349

350+
def test_arbitrary_pointer_type_attribute(self):
351+
class Cls(Structure):
352+
_fields_ = (
353+
('a', c_int),
354+
('b', c_float),
355+
)
356+
357+
garbage = 'garbage'
358+
359+
P = POINTER(Cls)
360+
self.assertIs(Cls.__pointer_type__, P)
361+
Cls.__pointer_type__ = garbage
362+
self.assertIs(Cls.__pointer_type__, garbage)
363+
self.assertIs(POINTER(Cls), garbage)
364+
self.assertIs(P._type_, Cls)
365+
366+
instance = Cls(1, 2.0)
367+
pointer = P(instance)
368+
self.assertEqual(pointer[0].a, 1)
369+
self.assertEqual(pointer[0].b, 2)
370+
371+
del Cls.__pointer_type__
372+
373+
NewP = POINTER(Cls)
374+
self.assertIsNot(NewP, P)
375+
self.assertIs(Cls.__pointer_type__, NewP)
376+
self.assertIs(P._type_, Cls)
377+
329378
def test_pointer_types_factory(self):
330379
"""Shouldn't leak"""
331380
def factory():
@@ -357,21 +406,31 @@ class Cls(Structure):
357406

358407
class PointerTypeCacheTestCase(unittest.TestCase):
359408
# dummy tests to check warnings and base behavior
409+
def tearDown(self):
410+
_pointer_type_cache_fallback.clear()
360411

361412
def test_deprecated_cache_with_not_ctypes_type(self):
362413
class C:
363414
pass
364415

365-
P = POINTER("C")
366416
with self.assertWarns(DeprecationWarning):
367-
_pointer_type_cache[C] = P
417+
P = POINTER("C")
368418

419+
with self.assertWarns(DeprecationWarning):
420+
self.assertIs(_pointer_type_cache["C"], P)
421+
422+
with self.assertWarns(DeprecationWarning):
423+
_pointer_type_cache[C] = P
369424
self.assertIs(C.__pointer_type__, P)
370425
with self.assertWarns(DeprecationWarning):
371426
self.assertIs(_pointer_type_cache[C], P)
372427

428+
def test_deprecated_cache_with_ints(self):
429+
with self.assertWarns(DeprecationWarning):
430+
_pointer_type_cache[123] = 456
431+
373432
with self.assertWarns(DeprecationWarning):
374-
self.assertIs(_pointer_type_cache.get(C), P)
433+
self.assertEqual(_pointer_type_cache[123], 456)
375434

376435
def test_deprecated_cache_with_ctypes_type(self):
377436
class C(Structure):
@@ -380,21 +439,20 @@ class C(Structure):
380439
("c", c_int)]
381440

382441
P1 = POINTER(C)
383-
P2 = POINTER("C")
384442
with self.assertWarns(DeprecationWarning):
385-
_pointer_type_cache[C] = P1
443+
P2 = POINTER("C")
386444

387445
with self.assertWarns(DeprecationWarning):
388-
_pointer_type_cache[C] = P2 # silently do nothing
446+
_pointer_type_cache[C] = P2
389447

390-
self.assertIs(C.__pointer_type__, P1)
391-
self.assertIsNot(C.__pointer_type__, P2)
448+
self.assertIs(C.__pointer_type__, P2)
449+
self.assertIsNot(C.__pointer_type__, P1)
392450

393451
with self.assertWarns(DeprecationWarning):
394-
self.assertIs(_pointer_type_cache[C], P1)
452+
self.assertIs(_pointer_type_cache[C], P2)
395453

396454
with self.assertWarns(DeprecationWarning):
397-
self.assertIs(_pointer_type_cache.get(C), P1)
455+
self.assertIs(_pointer_type_cache.get(C), P2)
398456

399457
def test_get_not_registered(self):
400458
with self.assertWarns(DeprecationWarning):

Modules/_ctypes/_ctypes.c

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,23 @@ ctype_get_pointer_type(PyObject *self, void *Py_UNUSED(ignored))
599599
return NULL;
600600
}
601601

602+
static int
603+
ctype_set_pointer_type(PyObject *self, PyObject *tp, void *Py_UNUSED(ignored))
604+
{
605+
ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
606+
StgInfo *info;
607+
if (PyStgInfo_FromType(st, self, &info) < 0) {
608+
return -1;
609+
}
610+
if (!info) {
611+
PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
612+
return -1;
613+
}
614+
615+
Py_XSETREF(info->pointer_type, Py_XNewRef(tp));
616+
return 0;
617+
}
618+
602619
static PyObject *
603620
CType_Type_repeat(PyObject *self, Py_ssize_t length);
604621

@@ -609,7 +626,8 @@ static PyMethodDef ctype_methods[] = {
609626
};
610627

611628
static PyGetSetDef ctype_getsets[] = {
612-
{ "__pointer_type__", ctype_get_pointer_type, NULL, "pointer type", NULL },
629+
{ "__pointer_type__", ctype_get_pointer_type, ctype_set_pointer_type,
630+
"pointer type", NULL },
613631
{ NULL, NULL }
614632
};
615633

@@ -1235,25 +1253,9 @@ PyCPointerType_SetProto(ctypes_state *st, PyObject *self, StgInfo *stginfo, PyOb
12351253
PyErr_Format(PyExc_TypeError, "%R must have storage info", proto);
12361254
return -1;
12371255
}
1238-
if (info->pointer_type && info->pointer_type != self) {
1239-
PyErr_Format(PyExc_TypeError,
1240-
"pointer type already set: old=%R, new=%R",
1241-
info->pointer_type, self);
1242-
return -1;
1243-
}
1244-
if (stginfo->proto && stginfo->proto != proto) {
1245-
PyErr_Format(PyExc_TypeError,
1246-
"cls type already set: old=%R, new=%R",
1247-
stginfo->proto, proto);
1248-
return -1;
1249-
}
1250-
1251-
if (!stginfo->proto) {
1252-
stginfo->proto = Py_NewRef(proto);
1253-
}
1254-
1255-
if (!info->pointer_type) {
1256-
info->pointer_type = Py_NewRef(self);
1256+
Py_XSETREF(stginfo->proto, Py_NewRef(proto));
1257+
if (info->pointer_type == NULL) {
1258+
Py_XSETREF(info->pointer_type, Py_NewRef(self));
12571259
}
12581260
return 0;
12591261
}

Modules/_ctypes/ctypes.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,8 @@ typedef struct {
388388
PyObject *converters; /* tuple([t.from_param for t in argtypes]) */
389389
PyObject *restype; /* CDataObject or NULL */
390390
PyObject *checker;
391-
PyObject *pointer_type;
391+
PyObject *pointer_type; /* __pointer_type__ attribute;
392+
arbitrary object or NULL */
392393
PyObject *module;
393394
int flags; /* calling convention and such */
394395
#ifdef Py_GIL_DISABLED

0 commit comments

Comments
 (0)