Skip to content

Commit 52deabe

Browse files
gh-132775: Expand the Capability of Interpreter.call() (gh-133484)
It now supports most callables, full args, and return values.
1 parent eb145fa commit 52deabe

File tree

10 files changed

+1257
-301
lines changed

10 files changed

+1257
-301
lines changed

Include/internal/pycore_crossinterp.h

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,9 @@ typedef enum error_code {
317317
_PyXI_ERR_ALREADY_RUNNING = -4,
318318
_PyXI_ERR_MAIN_NS_FAILURE = -5,
319319
_PyXI_ERR_APPLY_NS_FAILURE = -6,
320-
_PyXI_ERR_NOT_SHAREABLE = -7,
320+
_PyXI_ERR_PRESERVE_FAILURE = -7,
321+
_PyXI_ERR_EXC_PROPAGATION_FAILURE = -8,
322+
_PyXI_ERR_NOT_SHAREABLE = -9,
321323
} _PyXI_errcode;
322324

323325

@@ -350,16 +352,33 @@ typedef struct xi_session _PyXI_session;
350352
PyAPI_FUNC(_PyXI_session *) _PyXI_NewSession(void);
351353
PyAPI_FUNC(void) _PyXI_FreeSession(_PyXI_session *);
352354

355+
typedef struct {
356+
PyObject *preserved;
357+
PyObject *excinfo;
358+
_PyXI_errcode errcode;
359+
} _PyXI_session_result;
360+
PyAPI_FUNC(void) _PyXI_ClearResult(_PyXI_session_result *);
361+
353362
PyAPI_FUNC(int) _PyXI_Enter(
354363
_PyXI_session *session,
355364
PyInterpreterState *interp,
356-
PyObject *nsupdates);
357-
PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session);
358-
359-
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(_PyXI_session *);
360-
361-
PyAPI_FUNC(PyObject *) _PyXI_ApplyCapturedException(_PyXI_session *session);
362-
PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
365+
PyObject *nsupdates,
366+
_PyXI_session_result *);
367+
PyAPI_FUNC(int) _PyXI_Exit(
368+
_PyXI_session *,
369+
_PyXI_errcode,
370+
_PyXI_session_result *);
371+
372+
PyAPI_FUNC(PyObject *) _PyXI_GetMainNamespace(
373+
_PyXI_session *,
374+
_PyXI_errcode *);
375+
376+
PyAPI_FUNC(int) _PyXI_Preserve(
377+
_PyXI_session *,
378+
const char *,
379+
PyObject *,
380+
_PyXI_errcode *);
381+
PyAPI_FUNC(PyObject *) _PyXI_GetPreserved(_PyXI_session_result *, const char *);
363382

364383

365384
/*************/

Lib/test/_code_definitions.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ def spam_with_globals_and_builtins():
5757
print(res)
5858

5959

60+
def spam_full_args(a, b, /, c, d, *args, e, f, **kwargs):
61+
return (a, b, c, d, e, f, args, kwargs)
62+
63+
64+
def spam_full_args_with_defaults(a=-1, b=-2, /, c=-3, d=-4, *args,
65+
e=-5, f=-6, **kwargs):
66+
return (a, b, c, d, e, f, args, kwargs)
67+
68+
6069
def spam_args_attrs_and_builtins(a, b, /, c, d, *args, e, f, **kwargs):
6170
if args.__len__() > 2:
6271
return None
@@ -67,6 +76,10 @@ def spam_returns_arg(x):
6776
return x
6877

6978

79+
def spam_raises():
80+
raise Exception('spam!')
81+
82+
7083
def spam_with_inner_not_closure():
7184
def eggs():
7285
pass
@@ -177,8 +190,11 @@ def ham_C_closure(z):
177190
spam_minimal,
178191
spam_with_builtins,
179192
spam_with_globals_and_builtins,
193+
spam_full_args,
194+
spam_full_args_with_defaults,
180195
spam_args_attrs_and_builtins,
181196
spam_returns_arg,
197+
spam_raises,
182198
spam_with_inner_not_closure,
183199
spam_with_inner_closure,
184200
spam_annotated,
@@ -219,8 +235,10 @@ def ham_C_closure(z):
219235
spam,
220236
spam_minimal,
221237
spam_with_builtins,
238+
spam_full_args,
222239
spam_args_attrs_and_builtins,
223240
spam_returns_arg,
241+
spam_raises,
224242
spam_annotated,
225243
spam_with_inner_not_closure,
226244
spam_with_inner_closure,
@@ -238,6 +256,7 @@ def ham_C_closure(z):
238256
STATELESS_CODE = [
239257
*STATELESS_FUNCTIONS,
240258
script_with_globals,
259+
spam_full_args_with_defaults,
241260
spam_with_globals_and_builtins,
242261
spam_full,
243262
]
@@ -248,6 +267,7 @@ def ham_C_closure(z):
248267
script_with_explicit_empty_return,
249268
spam_minimal,
250269
spam_with_builtins,
270+
spam_raises,
251271
spam_with_inner_not_closure,
252272
spam_with_inner_closure,
253273
]

Lib/test/support/interpreters/__init__.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -226,33 +226,32 @@ def exec(self, code, /):
226226
if excinfo is not None:
227227
raise ExecutionFailed(excinfo)
228228

229-
def call(self, callable, /):
230-
"""Call the object in the interpreter with given args/kwargs.
229+
def _call(self, callable, args, kwargs):
230+
res, excinfo = _interpreters.call(self._id, callable, args, kwargs, restrict=True)
231+
if excinfo is not None:
232+
raise ExecutionFailed(excinfo)
233+
return res
231234

232-
Only functions that take no arguments and have no closure
233-
are supported.
235+
def call(self, callable, /, *args, **kwargs):
236+
"""Call the object in the interpreter with given args/kwargs.
234237
235-
The return value is discarded.
238+
Nearly all callables, args, kwargs, and return values are
239+
supported. All "shareable" objects are supported, as are
240+
"stateless" functions (meaning non-closures that do not use
241+
any globals). This method will fall back to pickle.
236242
237243
If the callable raises an exception then the error display
238-
(including full traceback) is send back between the interpreters
244+
(including full traceback) is sent back between the interpreters
239245
and an ExecutionFailed exception is raised, much like what
240246
happens with Interpreter.exec().
241247
"""
242-
# XXX Support args and kwargs.
243-
# XXX Support arbitrary callables.
244-
# XXX Support returning the return value (e.g. via pickle).
245-
excinfo = _interpreters.call(self._id, callable, restrict=True)
246-
if excinfo is not None:
247-
raise ExecutionFailed(excinfo)
248+
return self._call(callable, args, kwargs)
248249

249-
def call_in_thread(self, callable, /):
250+
def call_in_thread(self, callable, /, *args, **kwargs):
250251
"""Return a new thread that calls the object in the interpreter.
251252
252253
The return value and any raised exception are discarded.
253254
"""
254-
def task():
255-
self.call(callable)
256-
t = threading.Thread(target=task)
255+
t = threading.Thread(target=self._call, args=(callable, args, kwargs))
257256
t.start()
258257
return t

Lib/test/test_code.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,26 @@ def test_local_kinds(self):
701701
'checks': CO_FAST_LOCAL,
702702
'res': CO_FAST_LOCAL,
703703
},
704+
defs.spam_full_args: {
705+
'a': POSONLY,
706+
'b': POSONLY,
707+
'c': POSORKW,
708+
'd': POSORKW,
709+
'e': KWONLY,
710+
'f': KWONLY,
711+
'args': VARARGS,
712+
'kwargs': VARKWARGS,
713+
},
714+
defs.spam_full_args_with_defaults: {
715+
'a': POSONLY,
716+
'b': POSONLY,
717+
'c': POSORKW,
718+
'd': POSORKW,
719+
'e': KWONLY,
720+
'f': KWONLY,
721+
'args': VARARGS,
722+
'kwargs': VARKWARGS,
723+
},
704724
defs.spam_args_attrs_and_builtins: {
705725
'a': POSONLY,
706726
'b': POSONLY,
@@ -714,6 +734,7 @@ def test_local_kinds(self):
714734
defs.spam_returns_arg: {
715735
'x': POSORKW,
716736
},
737+
defs.spam_raises: {},
717738
defs.spam_with_inner_not_closure: {
718739
'eggs': CO_FAST_LOCAL,
719740
},
@@ -934,6 +955,20 @@ def new_var_counts(*,
934955
purelocals=5,
935956
globalvars=6,
936957
),
958+
defs.spam_full_args: new_var_counts(
959+
posonly=2,
960+
posorkw=2,
961+
kwonly=2,
962+
varargs=1,
963+
varkwargs=1,
964+
),
965+
defs.spam_full_args_with_defaults: new_var_counts(
966+
posonly=2,
967+
posorkw=2,
968+
kwonly=2,
969+
varargs=1,
970+
varkwargs=1,
971+
),
937972
defs.spam_args_attrs_and_builtins: new_var_counts(
938973
posonly=2,
939974
posorkw=2,
@@ -945,6 +980,9 @@ def new_var_counts(*,
945980
defs.spam_returns_arg: new_var_counts(
946981
posorkw=1,
947982
),
983+
defs.spam_raises: new_var_counts(
984+
globalvars=1,
985+
),
948986
defs.spam_with_inner_not_closure: new_var_counts(
949987
purelocals=1,
950988
),
@@ -1097,10 +1135,16 @@ def new_var_counts(*,
10971135
def test_stateless(self):
10981136
self.maxDiff = None
10991137

1138+
STATELESS_FUNCTIONS = [
1139+
*defs.STATELESS_FUNCTIONS,
1140+
# stateless with defaults
1141+
defs.spam_full_args_with_defaults,
1142+
]
1143+
11001144
for func in defs.STATELESS_CODE:
11011145
with self.subTest((func, '(code)')):
11021146
_testinternalcapi.verify_stateless_code(func.__code__)
1103-
for func in defs.STATELESS_FUNCTIONS:
1147+
for func in STATELESS_FUNCTIONS:
11041148
with self.subTest((func, '(func)')):
11051149
_testinternalcapi.verify_stateless_code(func)
11061150

@@ -1110,7 +1154,7 @@ def test_stateless(self):
11101154
with self.assertRaises(Exception):
11111155
_testinternalcapi.verify_stateless_code(func.__code__)
11121156

1113-
if func not in defs.STATELESS_FUNCTIONS:
1157+
if func not in STATELESS_FUNCTIONS:
11141158
with self.subTest((func, '(func)')):
11151159
with self.assertRaises(Exception):
11161160
_testinternalcapi.verify_stateless_code(func)

0 commit comments

Comments
 (0)