Python: получение трассировки из многопроцессорной обработки.Процесс
Я пытаюсь получить объект трассировки из многопроцессорной обработки.Процесс. К сожалению, передача информации об исключении через канал не работает, потому что объекты трассировки не могут быть маринованы:
def foo(pipe_to_parent):
try:
raise Exception('xxx')
except:
pipe_to_parent.send(sys.exc_info())
to_child, to_self = multiprocessing.Pipe()
process = multiprocessing.Process(target = foo, args = (to_self,))
process.start()
exc_info = to_child.recv()
process.join()
print traceback.format_exception(*exc_info)
to_child.close()
to_self.close()
Traceback:
Traceback (most recent call last):
File "/usr/lib/python2.6/multiprocessing/process.py", line 231, in _bootstrap
self.run()
File "/usr/lib/python2.6/multiprocessing/process.py", line 88, in run
self._target(*self._args, **self._kwargs)
File "foo", line 7, in foo
to_parent.send(sys.exc_info())
PicklingError: Can't pickle <type 'traceback'>: attribute lookup __builtin__.traceback failed
есть ли другой способ получить доступ к информации об исключении? Я бы хотел избежать передачи отформатированной строки.
5 ответов
используя tblib
вы можете передать завернутые исключения и перезаправить их позже:
import tblib.pickling_support
tblib.pickling_support.install()
from multiprocessing import Pool
import sys
class ExceptionWrapper(object):
def __init__(self, ee):
self.ee = ee
__, __, self.tb = sys.exc_info()
def re_raise(self):
raise self.ee.with_traceback(self.tb)
# for Python 2 replace the previous line by:
# raise self.ee, None, self.tb
# example how to use ExceptionWrapper
def inverse(i):
"""will fail for i == 0"""
try:
return 1.0 / i
except Exception as e:
return ExceptionWrapper(e)
def main():
p = Pool(1)
results = p.map(inverse, [0, 1, 2, 3])
for result in results:
if isinstance(result, ExceptionWrapper):
result.re_raise()
if __name__ == "__main__":
main()
Итак, если вы поймаете исключение в удаленном процессе, оберните его ExceptionWrapper
и затем передать его обратно. Зову re_reraise
в основном процессе будет делать свою работу.
С multiprocessing
печатает строковое содержимое исключений, вызванных в дочерних процессах, вы можете обернуть весь код дочернего процесса в попытке-за исключением того, что ловит любые исключения, форматирует трассировки стека relavent и вызывает новый Exception
, который содержит всю необходимую информацию в строку:
пример функции, которую я использую с multiprocessing.map
:
def run_functor(functor):
"""
Given a no-argument functor, run it and return its result. We can
use this with multiprocessing.map and map it over a list of job
functors to do them.
Handles getting more than multiprocessing's pitiful exception output
"""
try:
# This is where you do your actual work
return functor()
except:
# Put all exception text into an exception and raise that
raise Exception("".join(traceback.format_exception(*sys.exc_info())))
вы получаете трассировку стека с другой отформатированной трассировкой стека в качестве сообщения об ошибке, которое помогает отладка.
кажется, трудно сделать picklable объект traceback.
Но вы можете отправить только 2 первых пункта sys.exc_info()
, и preformated вывод информации с вывод.extract_tb способ :
import multiprocessing
import sys
import traceback
def foo(pipe_to_parent):
try:
raise Exception('xxx')
except:
except_type, except_class, tb = sys.exc_info()
pipe_to_parent.send((except_type, except_class, traceback.extract_tb(tb)))
to_child, to_self = multiprocessing.Pipe()
process = multiprocessing.Process(target = foo, args = (to_self,))
process.start()
exc_info = to_child.recv()
process.join()
print exc_info
to_child.close()
to_self.close()
, которые дают вам :
(<type 'exceptions.Exception'>, Exception('xxx',), [('test_tb.py', 7, 'foo', "raise Exception('xxx')")])
и затем вы сможете получить больше информации о причине исключения (имя файла, номер строки, где возникло исключение, имя метода и оператор, который вызывает исключение)
Python 3
в Python 3, Теперь get
метод multiprocessing.pool.Async
возвращает полную трассировку, см. http://bugs.python.org/issue13831.
Python 2
использовать traceback.format_exc
(что означает отформатированное expetion), чтобы получить строку обратной трассировки.
Это было бы гораздо более ковенант с созданием декоратора, как показано ниже.
def full_traceback(func):
import traceback, functools
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
msg = "{}\n\nOriginal {}".format(e, traceback.format_exc())
raise type(e)(msg)
return wrapper
пример:
def func0():
raise NameError("func0 exception")
def func1():
return func0()
# Key is here!
@full_traceback
def main(i):
return func1()
if __name__ == '__main__':
from multiprocessing import Pool
pool = Pool(4)
try:
results = pool.map_async(main, range(5)).get(1e5)
finally:
pool.close()
pool.join()
укажите С в декоратор:
Traceback (most recent call last):
File "bt.py", line 34, in <module>
results = pool.map_async(main, range(5)).get(1e5)
File "/opt/anaconda/lib/python2.7/multiprocessing/pool.py", line 567, in get
raise self._value
NameError: Exception in func0
Original Traceback (most recent call last):
File "bt.py", line 13, in wrapper
return func(*args, **kwargs)
File "bt.py", line 27, in main
return func1()
File "bt.py", line 23, in func1
return func0()
File "bt.py", line 20, in func0
raise NameError("Exception in func0")
NameError: Exception in func0
укажите без декоратор:
Traceback (most recent call last):
File "bt.py", line 34, in <module>
results = pool.map_async(main, range(5)).get(1e5)
File "/opt/anaconda/lib/python2.7/multiprocessing/pool.py", line 567, in get
raise self._value
NameError: Exception in func0
Это вариант это отличный ответ. Оба полагаются на tblib для хранения трассировки.
однако вместо того, чтобы возвращать объект исключения (как просил OP),worker
функция может быть оставлена как есть и просто завернута в try
/except
для хранения исключений для повторного повышения.
import tblib.pickling_support
tblib.pickling_support.install()
import sys
class DelayedException(Exception):
def __init__(self, ee):
self.ee = ee
__, __, self.tb = sys.exc_info()
super(DelayedException, self).__init__(str(ee))
def re_raise(self):
raise self.ee, None, self.tb
пример
def worker():
try:
raise ValueError('Something went wrong.')
except Exception as e:
raise DelayedException(e)
if __name__ == '__main__':
import multiprocessing
pool = multiprocessing.Pool()
try:
pool.imap(worker, [1, 2, 3])
except DelayedException as e:
e.re_raise()