Skip to content

Commit 50237e9

Browse files
committed
Complete the containers_and_refcounts.rst by adding code, tests and documentation for sets.
1 parent 5fcd2d4 commit 50237e9

File tree

7 files changed

+446
-16
lines changed

7 files changed

+446
-16
lines changed

doc/sphinx/source/containers_and_refcounts.rst

+121-11
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,6 @@ Summary
823823

824824

825825
.. Links, mostly to the Python documentation:
826-
TODO: Investigate/create tests for all of these.
827826
828827
.. Setters
829828
@@ -1481,10 +1480,14 @@ The C function signature is:
14811480
``Py_BuildValue("{OO}", key, value);`` will increment the refcount of the key and value and this can,
14821481
potentially, leak.
14831482

1484-
.. code-block:: c
14851483

1486-
int PyDict_Next(PyObject *p, Py_ssize_t *ppos, PyObject **pkey, PyObject **pvalue);
1484+
.. Links, mostly to the Python documentation:
1485+
1486+
.. Setters
14871487
1488+
.. _PySet_Add(): https://docs.python.org/3/c-api/set.html#c.PySet_Add
1489+
.. _PySet_Discard(): https://docs.python.org/3/c-api/set.html#c.PySet_Discard
1490+
.. _PySet_Pop(): https://docs.python.org/3/c-api/set.html#c.PySet_Pop
14881491

14891492
.. _chapter_containers_and_refcounts.sets:
14901493

@@ -1495,21 +1498,128 @@ potentially, leak.
14951498
Sets
14961499
-----------------------
14971500

1498-
.. todo::
1501+
.. index::
1502+
single: PySet_Add()
1503+
single: Set; PySet_Add()
14991504

1500-
Complete chapter :ref:`chapter_containers_and_refcounts` section :ref:`chapter_containers_and_refcounts.sets`.
1505+
``PySet_Add()``
1506+
--------------------
15011507

1508+
`PySet_Add()`_ is fairly straightforward.
1509+
The set will increment the reference count of the value thus:
15021510

1503-
.. _chapter_containers_and_refcounts.summary:
1511+
.. code-block:: c
15041512
1505-
-----------------------
1506-
Summary
1507-
-----------------------
1513+
PyObject *container = PySet_New(NULL);
1514+
/* Py_REFCNT(container) is 1 */
1515+
PyObject *value = new_unique_string(__FUNCTION__, NULL);
1516+
/* Py_REFCNT(value) is 1 */
1517+
int ret_val = PySet_Add(container, value);
1518+
assert(ret_val == 0);
1519+
/* Py_REFCNT(value) is now 2 */
15081520
1509-
.. todo::
1521+
/* Add duplicate. */
1522+
ret_val = PySet_Add(container, value);
1523+
assert(ret_val == 0);
1524+
/* Py_REFCNT(value) is still 2 */
1525+
1526+
/* Clean up. */
1527+
Py_DECREF(container);
1528+
/* Py_REFCNT(value) is now 1 */
1529+
Py_DECREF(value);
1530+
1531+
.. index::
1532+
single: PySet_Discard()
1533+
single: Set; PySet_Discard()
1534+
1535+
``PySet_Discard()``
1536+
--------------------
15101537

1511-
Complete chapter :ref:`chapter_containers_and_refcounts` section :ref:`chapter_containers_and_refcounts.summary`.
1538+
`PySet_Discard()`_ is also fairly straightforward.
1539+
The set will discard (:ref:`chapter_containers_and_refcounts.discarded`) the value thus:
15121540

1541+
.. code-block:: c
1542+
1543+
PyObject *container = PySet_New(NULL);
1544+
/* Py_REFCNT(container) is 1 */
1545+
PyObject *value = new_unique_string(__FUNCTION__, NULL);
1546+
/* Py_REFCNT(value) is 1 */
1547+
int ret_val = PySet_Add(container, value);
1548+
assert(ret_val == 0);
1549+
/* Py_REFCNT(value) is now 2 */
1550+
1551+
/* Discard. */
1552+
ret_val = PySet_Discard(container, value);
1553+
assert(ret_val == 1);
1554+
/* Py_REFCNT(value) is now 1 */
1555+
1556+
/* Clean up. */
1557+
Py_DECREF(container);
1558+
/* Py_REFCNT(value) is still 1 */
1559+
Py_DECREF(value);
1560+
1561+
.. index::
1562+
single: PySet_Pop()
1563+
single: Set; PySet_Pop()
1564+
1565+
``PySet_Pop()``
1566+
--------------------
1567+
1568+
`PySet_Pop()`_ will return a *new* reference to the existing object and it is up to caller to decrement the
1569+
reference count appropriately.
1570+
1571+
For example, the reference counts work as follows:
1572+
1573+
.. code-block:: c
1574+
1575+
PyObject *container = PySet_New(NULL);
1576+
/* Py_REFCNT(container) is 1 */
1577+
PyObject *value = new_unique_string(__FUNCTION__, NULL);
1578+
/* Py_REFCNT(value) is 1 */
1579+
int ret_val = PySet_Add(container, value);
1580+
assert(ret_val == 0);
1581+
/* Py_REFCNT(value) is now 2 */
1582+
1583+
/* Pop. */
1584+
PyObject *popped_value = PySet_Pop(container);
1585+
assert(popped_value == value);
1586+
/* Py_REFCNT(value) is still 2 */
1587+
1588+
/* Clean up. */
1589+
Py_DECREF(container);
1590+
/* Py_REFCNT(value) is still 2 so need to double Py_DECREF calls. */
1591+
Py_DECREF(value);
1592+
Py_DECREF(value);
1593+
1594+
So this is how `PySet_Pop()`_ might be used in practice:
1595+
1596+
.. code-block:: c
1597+
1598+
1599+
void add_to_set(PyObject *container) {
1600+
PyObject *value = new_unique_string(__FUNCTION__, NULL);
1601+
/* Py_REFCNT(value) is 1 */
1602+
int ret_val = PySet_Add(container, value);
1603+
assert(ret_val == 0);
1604+
/* Py_REFCNT(value) is now 2 */
1605+
/* Decrement our local value */
1606+
Py_DECREF(value);
1607+
/* Now the container has the only reference to the value. */
1608+
}
1609+
1610+
void pop_from_set(PyObject *container) {
1611+
PyObject *value = PySet_Pop(container);
1612+
/* Do something with the value... */
1613+
1614+
/* Then as we 'own' value we need to free it. */
1615+
Py_DECREF(value);
1616+
}
1617+
1618+
PyObject *container = PySet_New(NULL);
1619+
add_to_set(container);
1620+
pop_from_set(container);
1621+
/* Clean up. */
1622+
Py_DECREF(container);
15131623
15141624
.. Example footnote [#]_.
15151625

doc/sphinx/source/introduction.rst

+5-4
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,14 @@ Python extension in C.
3838
I really struggled to bring my knowledge of both languages to their very complicated, and crucial, codebase.
3939
To be honest I don't think I did a great job, but as I was the 'owner' I somehow got away with it.
4040

41-
After about six months of anxiety it occurred to me that, rather learning from their scrappy CPython C code,
41+
After some time it occurred to me that, rather learning from their scrappy CPython C code,
4242
I asked myself "how would you write a Python C Extension from scratch?".
4343
So on the commute and at weekends I did just that and slowly things became clearer.
4444
This eventually lead me to being invited to PyConUS to give a talk about the subject.
4545
This document is a synthesis of the latter journey which ended up giving me far more confidence about the subject than
4646
during my earlier difficulties.
4747

48-
My fond hope is that you will find that this document makes it much easier to work in this field.
48+
My fond hope is that you will find that this document makes it much easier to work in this field than I found initially.
4949
Another way of saying that is that I dedicate this document to you, and your work.
5050

5151
So why write Python C Extensions?
@@ -88,7 +88,8 @@ C and C++ have more specific deallocation policies than with a garbage collected
8888
The GIL
8989
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9090

91-
C Extensions do not need the Global Interpreter Lock (GIL) when working with C/C++ code.
91+
C Extensions do not need the Global Interpreter Lock (GIL) when working with C/C++ code which do *not* make calls to
92+
the CPython API.
9293

9394
---------------------
9495
Why Not?
@@ -178,4 +179,4 @@ Next up: a simple example showing the effect on code performance.
178179
.. rubric:: Footnotes
179180

180181
.. [#] Huge, but pretty consistent once mastered.
181-
.. [#] Version 0.2 of this project supports Python versions: 3.9, 3.10, 3.11, 3.12, 3.13.
182+
.. [#] Version 0.3 of this project supports Python versions: 3.9, 3.10, 3.11, 3.12, 3.13.

src/cpy/Containers/DebugContainers.c

+167
Original file line numberDiff line numberDiff line change
@@ -2336,6 +2336,173 @@ void dbg_PyDict_Pop_key_absent(void) {
23362336

23372337
#endif // #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13
23382338

2339+
#pragma mark - Sets
2340+
2341+
void dbg_PySet_Add(void) {
2342+
printf("%s():\n", __FUNCTION__);
2343+
if (PyErr_Occurred()) {
2344+
fprintf(stderr, "%s(): On entry PyErr_Print() %s#%d:\n", __FUNCTION__, __FILE_NAME__, __LINE__);
2345+
PyErr_Print();
2346+
return;
2347+
}
2348+
assert(!PyErr_Occurred());
2349+
Py_ssize_t ref_count;
2350+
2351+
PyObject *container = PySet_New(NULL);
2352+
assert(container);
2353+
2354+
ref_count = Py_REFCNT(container);
2355+
assert(ref_count == 1);
2356+
2357+
PyObject *value = new_unique_string(__FUNCTION__, NULL);
2358+
ref_count = Py_REFCNT(value);
2359+
assert(ref_count == 1);
2360+
2361+
assert(PySet_GET_SIZE(container) == 0);
2362+
2363+
if (PySet_Add(container, value)) {
2364+
assert(0);
2365+
}
2366+
assert(PySet_GET_SIZE(container) == 1);
2367+
2368+
ref_count = Py_REFCNT(value);
2369+
assert(ref_count == 2);
2370+
2371+
assert(PySet_Contains(container, value) == 1);
2372+
2373+
// Now insert the same object again, dupe of the code above.
2374+
if (PySet_Add(container, value)) {
2375+
assert(0);
2376+
}
2377+
assert(PySet_GET_SIZE(container) == 1);
2378+
2379+
ref_count = Py_REFCNT(value);
2380+
assert(ref_count == 2);
2381+
2382+
assert(PySet_Contains(container, value) == 1);
2383+
2384+
Py_DECREF(container);
2385+
2386+
ref_count = Py_REFCNT(value);
2387+
assert(ref_count == 1);
2388+
2389+
/* Clean up. */
2390+
Py_DECREF(value);
2391+
2392+
assert(!PyErr_Occurred());
2393+
}
2394+
2395+
void dbg_PySet_Discard(void) {
2396+
printf("%s():\n", __FUNCTION__);
2397+
if (PyErr_Occurred()) {
2398+
fprintf(stderr, "%s(): On entry PyErr_Print() %s#%d:\n", __FUNCTION__, __FILE_NAME__, __LINE__);
2399+
PyErr_Print();
2400+
return;
2401+
}
2402+
assert(!PyErr_Occurred());
2403+
Py_ssize_t ref_count;
2404+
2405+
PyObject *container = PySet_New(NULL);
2406+
assert(container);
2407+
2408+
ref_count = Py_REFCNT(container);
2409+
assert(ref_count == 1);
2410+
2411+
PyObject *value = new_unique_string(__FUNCTION__, NULL);
2412+
ref_count = Py_REFCNT(value);
2413+
assert(ref_count == 1);
2414+
2415+
assert(PySet_GET_SIZE(container) == 0);
2416+
2417+
if (PySet_Add(container, value)) {
2418+
assert(0);
2419+
}
2420+
assert(PySet_GET_SIZE(container) == 1);
2421+
2422+
ref_count = Py_REFCNT(value);
2423+
assert(ref_count == 2);
2424+
2425+
assert(PySet_Contains(container, value) == 1);
2426+
2427+
if (PySet_Discard(container, value) != 1) {
2428+
assert(0);
2429+
}
2430+
assert(PySet_GET_SIZE(container) == 0);
2431+
2432+
ref_count = Py_REFCNT(value);
2433+
assert(ref_count == 1);
2434+
2435+
assert(PySet_Contains(container, value) == 0);
2436+
2437+
Py_DECREF(container);
2438+
2439+
ref_count = Py_REFCNT(value);
2440+
assert(ref_count == 1);
2441+
2442+
/* Clean up. */
2443+
Py_DECREF(value);
2444+
2445+
assert(!PyErr_Occurred());
2446+
}
2447+
2448+
void dbg_PySet_Pop(void) {
2449+
printf("%s():\n", __FUNCTION__);
2450+
if (PyErr_Occurred()) {
2451+
fprintf(stderr, "%s(): On entry PyErr_Print() %s#%d:\n", __FUNCTION__, __FILE_NAME__, __LINE__);
2452+
PyErr_Print();
2453+
return;
2454+
}
2455+
assert(!PyErr_Occurred());
2456+
Py_ssize_t ref_count;
2457+
2458+
PyObject *container = PySet_New(NULL);
2459+
assert(container);
2460+
2461+
ref_count = Py_REFCNT(container);
2462+
assert(ref_count == 1);
2463+
2464+
PyObject *value = new_unique_string(__FUNCTION__, NULL);
2465+
ref_count = Py_REFCNT(value);
2466+
assert(ref_count == 1);
2467+
2468+
assert(PySet_GET_SIZE(container) == 0);
2469+
2470+
if (PySet_Add(container, value)) {
2471+
assert(0);
2472+
}
2473+
assert(PySet_GET_SIZE(container) == 1);
2474+
2475+
ref_count = Py_REFCNT(value);
2476+
assert(ref_count == 2);
2477+
2478+
assert(PySet_Contains(container, value) == 1);
2479+
2480+
// Now pop()
2481+
PyObject *popped_value = PySet_Pop(container);
2482+
2483+
assert(popped_value == value);
2484+
2485+
assert(PySet_GET_SIZE(container) == 0);
2486+
2487+
ref_count = Py_REFCNT(value);
2488+
assert(ref_count == 2);
2489+
2490+
assert(PySet_Contains(container, value) == 0);
2491+
2492+
Py_DECREF(container);
2493+
2494+
ref_count = Py_REFCNT(value);
2495+
assert(ref_count == 2);
2496+
2497+
/* Clean up. */
2498+
Py_DECREF(value);
2499+
Py_DECREF(value);
2500+
2501+
assert(!PyErr_Occurred());
2502+
}
2503+
2504+
#pragma mark - Code that sefgfaults
2505+
23392506
#if ACCEPT_SIGSEGV
23402507

23412508
void dbg_PyTuple_SetItem_SIGSEGV_on_same_value(void) {

src/cpy/Containers/DebugContainers.h

+7
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ void dbg_PyDict_Pop_key_present(void);
7878
void dbg_PyDict_Pop_key_absent(void);
7979
#endif // #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 13
8080

81+
#pragma mark - Sets
82+
83+
void dbg_PySet_Add(void);
84+
void dbg_PySet_Discard(void);
85+
void dbg_PySet_Pop(void);
86+
87+
8188
#if ACCEPT_SIGSEGV
8289
void dbg_PyTuple_SetItem_SIGSEGV_on_same_value(void);
8390
void dbg_PyList_SetItem_SIGSEGV_on_same_value(void);

0 commit comments

Comments
 (0)