Appeler exit () dans la bibliothèque C++ termine le script python qui enroule cette bibliothèque en utilisant swig
j'écris un wrapper Swig-Python pour une bibliothèque C++. Lorsque l'erreur critique se produit , la bibliothèque appelle exit(err);
, qui à son tour termine le script python entier qui exécute les fonctions à partir de cette bibliothèque.
y a-t-il un moyen de contourner la fonction exit()
pour revenir au script ou faire une exception?
2 réponses
vous pouvez assembler un hack massif pour cela en utilisant longjmp
et on_exit
, bien que je recommande fortement d'éviter cela en faveur d'une solution avec des processus multiples, que je décrirai plus tard dans la réponse.
supposons que nous ayons le fichier d'en-tête suivant (brisé par le dessin):
#ifndef TEST_H
#define TEST_H
#include <stdlib.h>
inline void fail_test(int fail) {
if (fail) exit(fail);
}
#endif//TEST_H
nous voulons l'envelopper et convertir l'appel à exit()
en une exception Python à la place. Une façon d'y parvenir serait quelque chose comme l'interface suivante, qui utilise %exception
pour insérer du code C autour de l'appel à chaque fonction C de votre interface Python:
%module test
%{
#include "test.h"
#include <setjmp.h>
static __thread int infunc = 0;
static __thread jmp_buf buf;
static void exithack(int code, void *data) {
if (!infunc) return;
(void)data;
longjmp(buf,code);
}
%}
%init %{
on_exit(exithack, NULL);
%}
%exception {
infunc = 1;
int err = 0;
if (!(err=setjmp(buf))) {
$action
}
else {
// Raise exception, code=err
PyErr_Format(PyExc_Exception, "%d", err);
infunc = 0;
on_exit(exithack, NULL);
SWIG_fail;
}
infunc = 0;
}
%include "test.h"
cela "fonctionne" quand nous le compilons:
swig3.0 -python -py3 -Wall test.i
gcc -shared test_wrap.c -o _test.so -I/usr/include/python3.4 -Wall -Wextra -lpython3.4m
et nous pouvons le démontrer avec:
Python 3.4.2 (default, Oct 8 2014, 13:14:40)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.fail_test(0)
>>> test.fail_test(123)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: 123
>>> test.fail_test(0)
>>> test.fail_test(999)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: 999
>>>
il est très laid cependant, presque certainement pas portable et le plus probable comportement non défini ainsi.
mon conseil serait de ne pas le faire et utiliser une solution avec deux processus de communication au lieu. Nous pouvons encore avoir SWIG pour nous aider à générer un module agréable et mieux encore, nous pouvons compter sur quelques constructions Python de haut niveau pour nous aider avec cela. L'exemple complet ressemble à:
%module test
%{
#include "test.h"
static void exit_handler(int code, void *fd) {
FILE *f = fdopen((int)fd, "w");
fprintf(stderr, "In exit handler: %d\n", code);
fprintf(f, "(dp0\nVexited\np1\nL%dL\ns.", code);
fclose(f);
}
%}
%typemap(in) int fd %{
= PyObject_AsFileDescriptor($input);
%}
%inline %{
void enter_work_loop(int fd) {
on_exit(exit_handler, (void*)fd);
}
%}
%pythoncode %{
import os
import pickle
serialize=pickle.dump
deserialize=pickle.load
def do_work(wrapped, args_pipe, results_pipe):
wrapped.enter_work_loop(results_pipe)
while True:
try:
args = deserialize(args_pipe)
f = getattr(wrapped, args['name'])
result = f(*args['args'], **args['kwargs'])
serialize({'value':result},results_pipe)
results_pipe.flush()
except Exception as e:
serialize({'exception': e},results_pipe)
results_pipe.flush()
class ProxyModule():
def __init__(self, wrapped):
self.wrapped = wrapped
self.prefix = "_worker_"
def __dir__(self):
return [x.strip(self.prefix) for x in dir(self.wrapped) if x.startswith(self.prefix)]
def __getattr__(self, name):
def proxy_call(*args, **kwargs):
serialize({
'name': '%s%s' % (self.prefix, name),
'args': args,
'kwargs': kwargs
}, self.args[1])
self.args[1].flush()
result = deserialize(self.results[0])
if 'exception' in result: raise result['exception']
if 'exited' in result: raise Exception('Library exited with code: %d' % result['exited'])
return result['value']
return proxy_call
def init_library(self):
def pipes():
r,w=os.pipe()
return os.fdopen(r,'rb',0), os.fdopen(w,'wb',0)
self.args = pipes()
self.results = pipes()
self.worker = os.fork()
if 0==self.worker:
do_work(self.wrapped, self.args[0], self.results[1])
%}
// rename all our wrapped functions to be _worker_FUNCNAME to hide them - we'll call them from within the other process
%rename("_worker_%s") "";
%include "test.h"
%pythoncode %{
import sys
sys.modules[__name__] = ProxyModule(sys.modules[__name__])
%}
qui utilise les idées suivantes:
- Pickle sérialiser des données avant de les écrire sur des pipes à un processus ouvrier.
-
os.fork
pour lancer le processus de travail, avecos.fdopen
créant un objet plus agréable à utiliser en Python - SWIG's advanced rename pour cacher les fonctions réelles que nous avons enveloppé des utilisateurs du module, mais les envelopper tout de même
- une astuce pour remplacer le module par un objet Python qui implémente
__getattr__
pour retourner les fonctions de proxy pour le travailleur le processus de -
__dir__
pour garder TAB travailler à l'intérieur d'ipython -
on_exit
pour intercepter la sortie (mais pas la dévier) et rapporter le code via un ascii pré-écrit objet décapé
vous pouvez faire l'appel à library_init
transparent et automatique si vous le souhaitez. Vous devez également gérer le cas où le travailleur n'a pas été démarré ou a déjà sorti mieux (il va juste bloquer dans mon exemple). Et vous devrez également vous assurer que le travailleur est nettoyé correctement à la sortie, mais il vous permet maintenant de courir:
Python 3.4.2 (default, Oct 8 2014, 13:14:40)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> test.init_library()
>>> test.fail_test(2)
In exit handler: 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/mnt/lislan/ajw/code/scratch/swig/pyatexit/test.py", line 117, in proxy_call
if 'exited' in result: raise Exception('Library exited with code: %d' % result['exited'])
Exception: Library exited with code: 2
>>>
et être encore (quelque peu) portable, mais certainement bien défini.
sans plus d'information il est difficile d'offrir une solution, cependant:
avez-vous écrit la bibliothèque? Si oui, pouvez-vous le retravailler pour lancer un logic_error
au lieu d'appeler exit
?
si la bibliothèque appelle exit
, cela implique un échec totalement catastrophique. L'état interne de la bibliothèque pourrait bien être incohérente (vous devez supposer que c'est!) - êtes-vous sûr de vouloir poursuivre le processus après cela? Si vous ne pas écrire de la bibliothèque vous n'êtes pas en mesure de raison à ce sujet. Si vous l'avez fait, voir ci-dessus.
peut-être pourriez-vous écrire un processus d'enveloppement autour de la bibliothèque, et marshall appelle à travers la limite du processus? Cela sera plus lent dans l'exécution et plus douloureux pour écrire et maintenir, mais cela permettra au processus parent (python) de détecter la terminaison de l'enfant (l'enrubanneur de bibliothèque).