-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy path_test.pyx
123 lines (97 loc) · 3.56 KB
/
_test.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# distutils: language = c++
# distutils: sources = testclass.cpp
from pytestclass import PyTestClass
from testclass cimport TestClass
def test_the_cppclass():
"""
Testing the class directly. This is rather dangerous style.
Notice all the cruft we need to avoid memory leaks. Any C++
or Python exception can separate the C++ object allocation
and deallocation. If you use this style it will be very easy
to create memory leaks. In a program more complicated than this
trivial code it might be easy to loose track of every possible
scenario. I.e. Never program like this! It is for the same
reason RAII is the preferred style in C++.
"""
cdef TestClass *T
T = new TestClass() # might raise MemoryError
# the MemoryError must be raised outside the
# try-finally block where we actually do something
# with the C++ object
try:
T.x = 15 # might raise ValueError
print(T.x) # might e.g. raise ValueError, AttribueError, IOError...
finally:
# we need this finally block to ensure del is called
# even if an exception is raised
del T
def test_memory_leak():
"""
This shows that we can produce a memory leak if
del is not called on the C++ object. That is, Cython does
not help us with garbage collection of C++ classes.
"""
cdef TestClass *T = new TestClass()
def test_the_wrapped_class():
"""
Here we wrap the C++ class with an extension class.
We do not any longer have to worry about memory leaks,
but the Python GC decides when the C++ object is
reclaimed. Also, your Cython code should not rely on a
particular GC implementation. If you have a reference
cycle object destruction will not happen deterministically
even with the current CPython.
"""
T = PyTestClass()
T.x = 15
print(T.x)
T = None
def test_as_context_manager():
"""
Here we wrap the C++ class with an extension class that
supports the context manager protocol. This gives us full
RAII-like control over the lifetime of the C++ object.
The Python GC only decides when the extension object is
reclaimed. The lifetime of the C++ object is manually
controlled.
"""
with PyTestClass() as T:
T.x = 15
print(T.x)
def test_with_raii():
"""
Putting the C++ on the stack allows us to precisely control the
lifetime of the C++ object, but Cython does not yet (as of 0.21)
support C++ objects as context managers, so the RAII capabilities
are currenty somewhat limited compared to C++, unless we wrap the
C++ class with an extension object. This is also safe against
memory leaks.
"""
# Cython does not support "cdef TestClass T()" so it
# is not possible to pass arguments to the constructor
cdef TestClass T
# We can also use a pointer
cdef TestClass *pT = &T
## Cython generates invalid C++ if you try this.
## References are still not implemented correctly.
# cdef TestClass &rT = T
# either
T.x = 15
print(T.x)
# or
pT.x = 15
print(pT.x)
## Not possible yet because references are incorrectly
## implemented
# rT.x = 15
# print(rT.x)
## In the future, Cython is planning to support this
## syntax or something similar, which will solve many of
## the problems metioned above:
#
# cdef TestClass *T
# with new TestClass() as T:
# T.x = 15
# print(T.x)
#
#