|
1 | 1 | """Subinterpreters High Level Module."""
|
2 | 2 |
|
| 3 | +import time |
3 | 4 | import _xxsubinterpreters as _interpreters
|
4 | 5 |
|
5 | 6 | # aliases:
|
|
19 | 20 |
|
20 | 21 |
|
21 | 22 | def create(*, isolated=True):
|
22 |
| - """ |
23 |
| - Initialize a new (idle) Python interpreter. |
24 |
| - """ |
| 23 | + """Return a new (idle) Python interpreter.""" |
25 | 24 | id = _interpreters.create(isolated=isolated)
|
26 | 25 | return Interpreter(id, isolated=isolated)
|
27 | 26 |
|
28 | 27 |
|
29 | 28 | def list_all():
|
30 |
| - """ |
31 |
| - Get all existing interpreters. |
32 |
| - """ |
33 |
| - return [Interpreter(id) for id in |
34 |
| - _interpreters.list_all()] |
| 29 | + """Return all existing interpreters.""" |
| 30 | + return [Interpreter(id) for id in _interpreters.list_all()] |
35 | 31 |
|
36 | 32 |
|
37 | 33 | def get_current():
|
38 |
| - """ |
39 |
| - Get the currently running interpreter. |
40 |
| - """ |
| 34 | + """Return the currently running interpreter.""" |
41 | 35 | id = _interpreters.get_current()
|
42 | 36 | return Interpreter(id)
|
43 | 37 |
|
44 | 38 |
|
45 | 39 | def get_main():
|
46 |
| - """ |
47 |
| - Get the main interpreter. |
48 |
| - """ |
| 40 | + """Return the main interpreter.""" |
49 | 41 | id = _interpreters.get_main()
|
50 | 42 | return Interpreter(id)
|
51 | 43 |
|
52 | 44 |
|
53 | 45 | class Interpreter:
|
54 |
| - """ |
55 |
| - The Interpreter object represents |
56 |
| - a single interpreter. |
57 |
| - """ |
| 46 | + """A single Python interpreter.""" |
58 | 47 |
|
59 | 48 | def __init__(self, id, *, isolated=None):
|
| 49 | + if not isinstance(id, (int, _interpreters.InterpreterID)): |
| 50 | + raise TypeError(f'id must be an int, got {id!r}') |
60 | 51 | self._id = id
|
61 | 52 | self._isolated = isolated
|
62 | 53 |
|
| 54 | + def __repr__(self): |
| 55 | + data = dict(id=int(self._id), isolated=self._isolated) |
| 56 | + kwargs = (f'{k}={v!r}' for k, v in data.items()) |
| 57 | + return f'{type(self).__name__}({", ".join(kwargs)})' |
| 58 | + |
| 59 | + def __hash__(self): |
| 60 | + return hash(self._id) |
| 61 | + |
| 62 | + def __eq__(self, other): |
| 63 | + if not isinstance(other, Interpreter): |
| 64 | + return NotImplemented |
| 65 | + else: |
| 66 | + return other._id == self._id |
| 67 | + |
63 | 68 | @property
|
64 | 69 | def id(self):
|
65 | 70 | return self._id
|
66 | 71 |
|
67 | 72 | @property
|
68 | 73 | def isolated(self):
|
69 | 74 | if self._isolated is None:
|
| 75 | + # XXX The low-level function has not been added yet. |
| 76 | + # See bpo-.... |
70 | 77 | self._isolated = _interpreters.is_isolated(self._id)
|
71 | 78 | return self._isolated
|
72 | 79 |
|
73 | 80 | def is_running(self):
|
74 |
| - """ |
75 |
| - Return whether or not the identified |
76 |
| - interpreter is running. |
77 |
| - """ |
| 81 | + """Return whether or not the identified interpreter is running.""" |
78 | 82 | return _interpreters.is_running(self._id)
|
79 | 83 |
|
80 | 84 | def close(self):
|
81 |
| - """ |
82 |
| - Finalize and destroy the interpreter. |
| 85 | + """Finalize and destroy the interpreter. |
83 | 86 |
|
84 |
| - Attempting to destroy the current |
85 |
| - interpreter results in a RuntimeError. |
| 87 | + Attempting to destroy the current interpreter results |
| 88 | + in a RuntimeError. |
86 | 89 | """
|
87 | 90 | return _interpreters.destroy(self._id)
|
88 | 91 |
|
89 | 92 | def run(self, src_str, /, *, channels=None):
|
90 |
| - """ |
91 |
| - Run the given source code in the interpreter. |
| 93 | + """Run the given source code in the interpreter. |
| 94 | +
|
92 | 95 | This blocks the current Python thread until done.
|
93 | 96 | """
|
94 |
| - _interpreters.run_string(self._id, src_str) |
| 97 | + _interpreters.run_string(self._id, src_str, channels) |
95 | 98 |
|
96 | 99 |
|
97 | 100 | def create_channel():
|
98 |
| - """ |
99 |
| - Create a new channel for passing data between |
100 |
| - interpreters. |
101 |
| - """ |
| 101 | + """Return (recv, send) for a new cross-interpreter channel. |
102 | 102 |
|
| 103 | + The channel may be used to pass data safely between interpreters. |
| 104 | + """ |
103 | 105 | cid = _interpreters.channel_create()
|
104 |
| - return (RecvChannel(cid), SendChannel(cid)) |
| 106 | + recv, send = RecvChannel(cid), SendChannel(cid) |
| 107 | + return recv, send |
105 | 108 |
|
106 | 109 |
|
107 | 110 | def list_all_channels():
|
108 |
| - """ |
109 |
| - Get all open channels. |
110 |
| - """ |
| 111 | + """Return a list of (recv, send) for all open channels.""" |
111 | 112 | return [(RecvChannel(cid), SendChannel(cid))
|
112 | 113 | for cid in _interpreters.channel_list_all()]
|
113 | 114 |
|
114 | 115 |
|
| 116 | +class _ChannelEnd: |
| 117 | + """The base class for RecvChannel and SendChannel.""" |
| 118 | + |
| 119 | + def __init__(self, id): |
| 120 | + if not isinstance(id, (int, _interpreters.ChannelID)): |
| 121 | + raise TypeError(f'id must be an int, got {id!r}') |
| 122 | + self._id = id |
| 123 | + |
| 124 | + def __repr__(self): |
| 125 | + return f'{type(self).__name__}(id={int(self._id)})' |
| 126 | + |
| 127 | + def __hash__(self): |
| 128 | + return hash(self._id) |
| 129 | + |
| 130 | + def __eq__(self, other): |
| 131 | + if isinstance(self, RecvChannel): |
| 132 | + if not isinstance(other, RecvChannel): |
| 133 | + return NotImplemented |
| 134 | + elif not isinstance(other, SendChannel): |
| 135 | + return NotImplemented |
| 136 | + return other._id == self._id |
| 137 | + |
| 138 | + @property |
| 139 | + def id(self): |
| 140 | + return self._id |
| 141 | + |
| 142 | + |
115 | 143 | _NOT_SET = object()
|
116 | 144 |
|
117 | 145 |
|
118 |
| -class RecvChannel: |
119 |
| - """ |
120 |
| - The RecvChannel object represents |
121 |
| - a receiving channel. |
122 |
| - """ |
| 146 | +class RecvChannel(_ChannelEnd): |
| 147 | + """The receiving end of a cross-interpreter channel.""" |
123 | 148 |
|
124 |
| - def __init__(self, id): |
125 |
| - self._id = id |
| 149 | + def recv(self, *, _sentinel=object(), _delay=10 / 1000): # 10 milliseconds |
| 150 | + """Return the next object from the channel. |
126 | 151 |
|
127 |
| - def recv(self, *, _delay=10 / 1000): # 10 milliseconds |
128 |
| - """ |
129 |
| - Get the next object from the channel, |
130 |
| - and wait if none have been sent. |
131 |
| - Associate the interpreter with the channel. |
| 152 | + This blocks until an object has been sent, if none have been |
| 153 | + sent already. |
132 | 154 | """
|
133 |
| - import time |
134 |
| - sentinel = object() |
135 |
| - obj = _interpreters.channel_recv(self._id, sentinel) |
136 |
| - while obj is sentinel: |
| 155 | + obj = _interpreters.channel_recv(self._id, _sentinel) |
| 156 | + while obj is _sentinel: |
137 | 157 | time.sleep(_delay)
|
138 |
| - obj = _interpreters.channel_recv(self._id, sentinel) |
| 158 | + obj = _interpreters.channel_recv(self._id, _sentinel) |
139 | 159 | return obj
|
140 | 160 |
|
141 | 161 | def recv_nowait(self, default=_NOT_SET):
|
142 |
| - """ |
143 |
| - Like recv(), but return the default |
144 |
| - instead of waiting. |
| 162 | + """Return the next object from the channel. |
145 | 163 |
|
146 |
| - This function is blocked by a missing low-level |
147 |
| - implementation of channel_recv_wait(). |
| 164 | + If none have been sent then return the default if one |
| 165 | + is provided or fail with ChannelEmptyError. Otherwise this |
| 166 | + is the same as recv(). |
148 | 167 | """
|
149 | 168 | if default is _NOT_SET:
|
150 | 169 | return _interpreters.channel_recv(self._id)
|
151 | 170 | else:
|
152 | 171 | return _interpreters.channel_recv(self._id, default)
|
153 | 172 |
|
154 | 173 |
|
155 |
| -class SendChannel: |
156 |
| - """ |
157 |
| - The SendChannel object represents |
158 |
| - a sending channel. |
159 |
| - """ |
160 |
| - |
161 |
| - def __init__(self, id): |
162 |
| - self._id = id |
| 174 | +class SendChannel(_ChannelEnd): |
| 175 | + """The sending end of a cross-interpreter channel.""" |
163 | 176 |
|
164 | 177 | def send(self, obj):
|
| 178 | + """Send the object (i.e. its data) to the channel's receiving end. |
| 179 | +
|
| 180 | + This blocks until the object is received. |
165 | 181 | """
|
166 |
| - Send the object (i.e. its data) to the receiving |
167 |
| - end of the channel and wait. Associate the interpreter |
168 |
| - with the channel. |
169 |
| - """ |
170 |
| - import time |
171 | 182 | _interpreters.channel_send(self._id, obj)
|
| 183 | + # XXX We are missing a low-level channel_send_wait(). |
| 184 | + # See bpo-32604 and gh-19829. |
| 185 | + # Until that shows up we fake it: |
172 | 186 | time.sleep(2)
|
173 | 187 |
|
174 | 188 | def send_nowait(self, obj):
|
175 |
| - """ |
176 |
| - Like send(), but return False if not received. |
| 189 | + """Send the object to the channel's receiving end. |
177 | 190 |
|
178 |
| - This function is blocked by a missing low-level |
179 |
| - implementation of channel_send_wait(). |
| 191 | + If the object is immediately received then return True |
| 192 | + (else False). Otherwise this is the same as send(). |
180 | 193 | """
|
181 |
| - |
182 |
| - _interpreters.channel_send(self._id, obj) |
183 |
| - return False |
| 194 | + # XXX Note that at the moment channel_send() only ever returns |
| 195 | + # None. This should be fixed when channel_send_wait() is added. |
| 196 | + # See bpo-32604 and gh-19829. |
| 197 | + return _interpreters.channel_send(self._id, obj) |
0 commit comments