4
4
.. toctree ::
5
5
:maxdepth: 3
6
6
7
+ .. _context manger : https://docs.python.org/3/glossary.html#term-context-manager>
8
+ .. _\_ _enter__() : https://docs.python.org/3/library/stdtypes.html#contextmanager.__enter__
9
+ .. _\_ _exit__() : https://docs.python.org/3/library/stdtypes.html#contextmanager.__exit__
10
+
7
11
.. _chapter_context_manager :
8
12
9
13
.. index ::
@@ -14,7 +18,6 @@ Context Managers
14
18
***************************
15
19
16
20
This chapter describes how to write
17
- `context mangers <https://docs.python.org/3/glossary.html#term-context-manager >`_
18
21
for your C objects.
19
22
20
23
.. index ::
@@ -24,8 +27,7 @@ for your C objects.
24
27
C Functions
25
28
===========================
26
29
27
- This is a summary of what is required for the C functions implementing a context
28
- manager.
30
+ This is a summary of what is required for the C functions implementing a `context manger `_.
29
31
The is no specific ``tp_... `` slot for the context manager functions ``__enter__ `` and ``__exit__ ``, instead they are added
30
32
to the object as named, looked up, Python methods.
31
33
@@ -36,19 +38,6 @@ to the object as named, looked up, Python methods.
36
38
``__enter__ ``
37
39
--------------------------------------
38
40
39
- Note that ``__enter__ `` is declared with ``METH_NOARGS ``.
40
- Link to the Python documentation for
41
- `__enter__ <https://docs.python.org/3/library/stdtypes.html#contextmanager.__enter_ _>`_.
42
-
43
- .. code-block :: c
44
-
45
- static PyMethodDef ContextManager_methods[] = {
46
- {"__enter__", (PyCFunction) ContextManager_enter, METH_NOARGS,
47
- PyDoc_STR("__enter__() -> ContextManager")},
48
- /* ... */
49
- {NULL, NULL, 0, NULL} /* sentinel */
50
- };
51
-
52
41
The C function must, at least, increment the reference count of ``self `` and
53
42
return ``self ``:
54
43
@@ -68,20 +57,8 @@ return ``self``:
68
57
``__exit__ ``
69
58
--------------------------------------
70
59
71
- The `` __exit__ `` function is declared thus.
60
+ The `__exit__() `_ function is declared thus.
72
61
It takes three arguments so ``METH_VARARGS `` is used.
73
- Link to the Python documentation for
74
- `__exit__ <https://docs.python.org/3/library/stdtypes.html#contextmanager.__exit_ _>`_.
75
-
76
- .. code-block :: c
77
-
78
- static PyMethodDef ContextManager_methods[] = {
79
- {"__exit__", (PyCFunction) ContextManager_exit, METH_VARARGS,
80
- PyDoc_STR("__exit__(exc_type, exc_value, exc_tb) -> bool")},
81
- /* ... */
82
- {NULL, NULL, 0, NULL} /* sentinel */
83
- };
84
-
85
62
The three arguments are each ``None `` if no exception has been raised within
86
63
the ``with `` block.
87
64
If an exception *has * been raised within the ``with `` block then the
@@ -109,6 +86,24 @@ CPython interpreter:
109
86
Py_RETURN_FALSE;
110
87
}
111
88
89
+ --------------------------------------
90
+ Method declarations
91
+ --------------------------------------
92
+
93
+ Note that `__enter__() `_ is declared with ``METH_NOARGS `` and `__exit__() `_ is declared with ``METH_VARARGS ``:
94
+
95
+ .. code-block :: c
96
+
97
+ static PyMethodDef ContextManager_methods[] = {
98
+ /* ... */
99
+ {"__enter__", (PyCFunction) ContextManager_enter, METH_NOARGS,
100
+ PyDoc_STR("__enter__() -> ContextManager")},
101
+ {"__exit__", (PyCFunction) ContextManager_exit, METH_VARARGS,
102
+ PyDoc_STR("__exit__(exc_type, exc_value, exc_tb) -> bool")},
103
+ /* ... */
104
+ {NULL, NULL, 0, NULL} /* sentinel */
105
+ };
106
+
112
107
=================================
113
108
Understanding the Context Manager
114
109
=================================
@@ -153,14 +148,14 @@ The sequence of reference count changes are as follows:
153
148
(to 2) and then call ``__enter__ `` that is implemented in our C function
154
149
``ContextManager_enter ``.
155
150
#. Our ``ContextManager_enter `` function increments the reference count, so it is now 3.
156
- #. As the context manager exists the scope of the ``with `` statement the CPython interpreter
151
+ #. As the context manager ends the ``with `` statement the CPython interpreter
157
152
decrements the reference count *twice * to the value 1.
158
153
The logic is:
159
154
160
155
#. Decrement the reference count once as we are exiting the ``with `` statement. The reference count is now 2.
161
156
#. Did the ``with `` statement have a target? If not, as in this case, then decrement the reference count once more. The reference count is now 1.
162
157
163
- #. The CPython interpreter then calls ``__exit__ `` which is implemented in our function
158
+ #. after the `` pass `` statement the CPython interpreter then calls ``__exit__ `` which is implemented in our function
164
159
``ContextManager_exit ``.
165
160
This does not change the reference count which remains at 1.
166
161
#. As the context manager goes out of scope the CPython interpreter decrements the reference
@@ -211,9 +206,9 @@ The sequence of reference count changes are now as follows:
211
206
#. As above, the reference count becomes 1.
212
207
#. As above, the reference count becomes 2.
213
208
#. As above, the reference count becomes 3.
214
- #. As the context manager exists the scope of the ``with `` statement the CPython interpreter
215
- decrements the reference count just *once * to the value 2 as there *is * a target.
216
- #. The CPython interpreter then calls ``__exit__ `` which is implemented in our function
209
+ #. As the context manager ends the ``with `` statement the CPython interpreter
210
+ decrements the reference count just *once * to the value 2 as there *is * a target, called `` context `` in this case .
211
+ #. After the `` pass `` statement the CPython interpreter then calls ``__exit__ `` which is implemented in our function
217
212
``ContextManager_exit ``.
218
213
This does not change the reference count which remains at 2.
219
214
#. As the context manager goes out of scope the CPython interpreter decrements the reference
@@ -256,6 +251,10 @@ First the object declaration, allocation and de-allocation functions:
256
251
PyObject_Del(self);
257
252
}
258
253
254
+ -----------------------------------------
255
+ ``__enter__ `` and ``__exit__ `` Methods
256
+ -----------------------------------------
257
+
259
258
The ``__enter__ `` and ``__exit__ `` methods:
260
259
261
260
.. code-block :: c
@@ -279,6 +278,11 @@ The ``__enter__`` and ``__exit__`` methods:
279
278
{NULL, NULL, 0, NULL} /* sentinel */
280
279
};
281
280
281
+
282
+ -----------------------------------------
283
+ Type Declaration
284
+ -----------------------------------------
285
+
282
286
The type declaration:
283
287
284
288
.. code-block :: c
@@ -293,6 +297,9 @@ The type declaration:
293
297
.tp_new = (newfunc) ContextManager_new,
294
298
};
295
299
300
+ -----------------------------------------
301
+ Module
302
+ -----------------------------------------
296
303
297
304
Finally the module:
298
305
@@ -326,11 +333,9 @@ Finally the module:
326
333
return NULL;
327
334
}
328
335
329
- .. note ::
330
-
331
- The actual code in ``src/cpy/CtxMgr/cCtxMgr.c `` contains extra trace
332
- reporting that confirms the reference counts and (no) memory leakage.
333
-
336
+ -----------------------------------------
337
+ Setup
338
+ -----------------------------------------
334
339
335
340
This code is added to the ``setup.py `` file:
336
341
@@ -351,3 +356,188 @@ And can be used thus:
351
356
352
357
with cCtxMgr.ContextManager():
353
358
pass
359
+
360
+ -----------------------------------------
361
+ Testing
362
+ -----------------------------------------
363
+
364
+ The actual code in ``src/cpy/CtxMgr/cCtxMgr.c `` contains extra trace reporting that confirms the reference counts and
365
+ (no) memory leakage.
366
+
367
+ This can be run with:
368
+
369
+ .. code-block :: bash
370
+
371
+ $ pytest tests/unit/test_c_ctxmgr.py -vs
372
+
373
+ This test:
374
+
375
+ .. code-block :: python
376
+
377
+ def test_very_simple ():
378
+ print ()
379
+ with cCtxMgr.ContextManager():
380
+ pass
381
+
382
+ Gives this output:
383
+
384
+ .. code-block :: text
385
+
386
+ tests/unit/test_c_ctxmgr.py::test_very_simple
387
+ ContextManager_new DONE REFCNT = 1
388
+ ContextManager_enter STRT REFCNT = 2
389
+ ContextManager_enter DONE REFCNT = 3
390
+ ContextManager_exit STRT REFCNT = 1
391
+ ContextManager_exit DONE REFCNT = 1
392
+ ContextManager_dealloc STRT REFCNT = 0
393
+ ContextManager_dealloc DONE REFCNT = 4546088432
394
+
395
+ This test:
396
+
397
+ .. code-block :: python
398
+
399
+ def test_simple ():
400
+ print ()
401
+ with cCtxMgr.ContextManager() as context:
402
+ assert sys.getrefcount(context) == 3
403
+ assert context.len_buffer_lifetime() == cCtxMgr.BUFFER_LENGTH
404
+ assert context.len_buffer_context() == cCtxMgr.BUFFER_LENGTH
405
+ assert sys.getrefcount(context) == 2
406
+ assert context.len_buffer_lifetime() == cCtxMgr.BUFFER_LENGTH
407
+ assert context.len_buffer_context() == 0
408
+ del context
409
+
410
+ Gives this output:
411
+
412
+ .. code-block :: text
413
+
414
+ tests/unit/test_c_ctxmgr.py::test_simple
415
+ ContextManager_new DONE REFCNT = 1
416
+ ContextManager_enter STRT REFCNT = 2
417
+ ContextManager_enter DONE REFCNT = 3
418
+ ContextManager_exit STRT REFCNT = 2
419
+ ContextManager_exit DONE REFCNT = 2
420
+ ContextManager_dealloc STRT REFCNT = 0
421
+ ContextManager_dealloc DONE REFCNT = 4546096048
422
+
423
+
424
+ This test:
425
+
426
+ .. code-block :: python
427
+
428
+ def test_memory ():
429
+ proc = psutil.Process()
430
+ print ()
431
+ print (f ' RSS START: { proc.memory_info().rss:12,d } ' )
432
+ for i in range (8 ):
433
+ print (f ' RSS START { i:5d } : { proc.memory_info().rss:12,d } ' )
434
+ with cCtxMgr.ContextManager() as context:
435
+ print (f ' RSS START CTX: { proc.memory_info().rss:12,d } ' )
436
+ # Does not work in the debugger due to introspection.
437
+ # assert sys.getrefcount(context) == 3
438
+ assert context.len_buffer_lifetime() == cCtxMgr.BUFFER_LENGTH
439
+ assert context.len_buffer_context() == cCtxMgr.BUFFER_LENGTH
440
+ print (f ' RSS END CTX: { proc.memory_info().rss:12,d } ' )
441
+ # Does not work in the debugger due to introspection.
442
+ # assert sys.getrefcount(context) == 2
443
+ assert context.len_buffer_lifetime() == cCtxMgr.BUFFER_LENGTH
444
+ assert context.len_buffer_context() == 0
445
+ del context
446
+ print (f ' RSS END { i:5d } : { proc.memory_info().rss:12,d } ' )
447
+ print (f ' RSS END: { proc.memory_info().rss:12,d } ' )
448
+
449
+ Gives this output:
450
+
451
+ .. code-block :: text
452
+
453
+ tests/unit/test_c_ctxmgr.py::test_memory
454
+ RSS START: 300,032,000
455
+ RSS START 0: 300,048,384
456
+ ContextManager_new DONE REFCNT = 1
457
+ ContextManager_enter STRT REFCNT = 2
458
+ ContextManager_enter DONE REFCNT = 3
459
+ RSS START CTX: 300,048,384
460
+ RSS END CTX: 300,048,384
461
+ ContextManager_exit STRT REFCNT = 2
462
+ ContextManager_exit DONE REFCNT = 2
463
+ ContextManager_dealloc STRT REFCNT = 0
464
+ ContextManager_dealloc DONE REFCNT = 4546096048
465
+ RSS END 0: 300,048,384
466
+ RSS START 1: 300,048,384
467
+ ContextManager_new DONE REFCNT = 1
468
+ ContextManager_enter STRT REFCNT = 2
469
+ ContextManager_enter DONE REFCNT = 3
470
+ RSS START CTX: 300,048,384
471
+ RSS END CTX: 300,048,384
472
+ ContextManager_exit STRT REFCNT = 2
473
+ ContextManager_exit DONE REFCNT = 2
474
+ ContextManager_dealloc STRT REFCNT = 0
475
+ ContextManager_dealloc DONE REFCNT = 4546096048
476
+ RSS END 1: 300,048,384
477
+ RSS START 2: 300,048,384
478
+ ContextManager_new DONE REFCNT = 1
479
+ ContextManager_enter STRT REFCNT = 2
480
+ ContextManager_enter DONE REFCNT = 3
481
+ RSS START CTX: 300,048,384
482
+ RSS END CTX: 300,048,384
483
+ ContextManager_exit STRT REFCNT = 2
484
+ ContextManager_exit DONE REFCNT = 2
485
+ ContextManager_dealloc STRT REFCNT = 0
486
+ ContextManager_dealloc DONE REFCNT = 4546096048
487
+ RSS END 2: 300,048,384
488
+ RSS START 3: 300,048,384
489
+ ContextManager_new DONE REFCNT = 1
490
+ ContextManager_enter STRT REFCNT = 2
491
+ ContextManager_enter DONE REFCNT = 3
492
+ RSS START CTX: 300,048,384
493
+ RSS END CTX: 300,048,384
494
+ ContextManager_exit STRT REFCNT = 2
495
+ ContextManager_exit DONE REFCNT = 2
496
+ ContextManager_dealloc STRT REFCNT = 0
497
+ ContextManager_dealloc DONE REFCNT = 4546096048
498
+ RSS END 3: 300,048,384
499
+ RSS START 4: 300,048,384
500
+ ContextManager_new DONE REFCNT = 1
501
+ ContextManager_enter STRT REFCNT = 2
502
+ ContextManager_enter DONE REFCNT = 3
503
+ RSS START CTX: 300,048,384
504
+ RSS END CTX: 300,048,384
505
+ ContextManager_exit STRT REFCNT = 2
506
+ ContextManager_exit DONE REFCNT = 2
507
+ ContextManager_dealloc STRT REFCNT = 0
508
+ ContextManager_dealloc DONE REFCNT = 4546096048
509
+ RSS END 4: 300,048,384
510
+ RSS START 5: 300,048,384
511
+ ContextManager_new DONE REFCNT = 1
512
+ ContextManager_enter STRT REFCNT = 2
513
+ ContextManager_enter DONE REFCNT = 3
514
+ RSS START CTX: 300,048,384
515
+ RSS END CTX: 300,048,384
516
+ ContextManager_exit STRT REFCNT = 2
517
+ ContextManager_exit DONE REFCNT = 2
518
+ ContextManager_dealloc STRT REFCNT = 0
519
+ ContextManager_dealloc DONE REFCNT = 4546096048
520
+ RSS END 5: 300,048,384
521
+ RSS START 6: 300,048,384
522
+ ContextManager_new DONE REFCNT = 1
523
+ ContextManager_enter STRT REFCNT = 2
524
+ ContextManager_enter DONE REFCNT = 3
525
+ RSS START CTX: 300,048,384
526
+ RSS END CTX: 300,048,384
527
+ ContextManager_exit STRT REFCNT = 2
528
+ ContextManager_exit DONE REFCNT = 2
529
+ ContextManager_dealloc STRT REFCNT = 0
530
+ ContextManager_dealloc DONE REFCNT = 4546096048
531
+ RSS END 6: 300,048,384
532
+ RSS START 7: 300,048,384
533
+ ContextManager_new DONE REFCNT = 1
534
+ ContextManager_enter STRT REFCNT = 2
535
+ ContextManager_enter DONE REFCNT = 3
536
+ RSS START CTX: 300,048,384
537
+ RSS END CTX: 300,048,384
538
+ ContextManager_exit STRT REFCNT = 2
539
+ ContextManager_exit DONE REFCNT = 2
540
+ ContextManager_dealloc STRT REFCNT = 0
541
+ ContextManager_dealloc DONE REFCNT = 4546096048
542
+ RSS END 7: 300,048,384
543
+ RSS END: 300,048,384
0 commit comments