Захват трассировки из R при возникновении RRuntimeError

Класс Python выполняет функции R через rpy2, и я хотел бы иметь возможность получить обратную трассировку от R в случае, если функция R генерирует ошибку.

Код R является устаревшим, поэтому его изменение было бы очень рискованным; Я бы предпочел сделать что-то на стороне Python.

Вот как выглядит код Python в настоящее время:

from rpy2.rinterface import RRuntimeError
from rpy2.robjects import DataFrame
from rpy2.robjects.packages import InstalledPackage

class RAdapter(BaseRAdapter):
    _module = None # type: InstalledPackage

    def call_raw(self, function_name, *args, **kwargs):
        # type: (str, tuple, dict) -> DataFrame
        """
        Invokes an R function and returns the result as a DataFrame.
        """
        try:
            return getattr(self._module, function_name)(*args, **kwargs)
        except RRuntimeError as e:
            # :todo: Capture traceback from R and attach to `e`.
            e.context = {'r_traceback': '???'}
            raise

    ...

Как я должен изменить call_raw так что он захватывает трассировку от R в случае, если функция R вызывает ошибку?

2 ответа

Решение

traceback() является функцией перехода для генерации трассировок ошибок в R. Использование rpy2.robjects.r Вы можете оценить traceback() Функция и сохранить результат непосредственно в переменную Python.

Примечание для rpy2 v2.8.x: результат traceback() это pairlist, с которым rpy2 может работать просто отлично, но есть проблема, которая мешает repr работать правильно. Чтобы сделать код проще для отладки, он использует unlist чтобы преобразовать pairlist в список.

Быть в курсе, что traceback() также отправляет трассировку в stdout, и я не знаю (как я знаю) способа избежать этого, кроме как [временно] переопределить sys.stdout,

Вот как RAdapter.call_raw() может захватить трассировку R:

from rpy2.rinterface import RRuntimeError
from rpy2.robjects import DataFrame
from rpy2.robjects.packages import InstalledPackage

class RAdapter(BaseRAdapter):
    _module = None # type: InstalledPackage

    def call_raw(self, function_name, *args, **kwargs):
        # type: (str, tuple, dict) -> DataFrame
        """
        Invokes an R function and returns the result as a DataFrame.
        """
        try:
            return getattr(self._module, function_name)(*args, **kwargs)
        except RRuntimeError as e:
            # Attempt to capture the traceback from R.
            # noinspection SpellCheckingInspection
            try:
                # noinspection SpellCheckingInspection
                e.context = {
                    # :kludge: Have to use `unlist` because `traceback`
                    #   returns a pairlist, which rpy2 doesn't know how
                    #   to handle.
                    'r_traceback': '\n'.join(r('unlist(traceback())'))
                }
            except Exception as traceback_exc:
                e.context = {
                    'r_traceback':
                        '(an error occurred while getting traceback from R)',

                    'r_traceback_err':  traceback_exc,
                }

            raise

    ...

Протестировано с rpy2==2.8.3,

rpy2 может справиться с R pairlists (в основном) нормально. Однако их представление (метод __repr__), кажется, есть ошибка: общее __repr__ для векторов R используются срезы, а срезы недоступны для объектов паирлистов.

>>> from rpy2.robjects import baseenv
>>> opts = baseenv['.Options']
>>> opts.typeof # this is a pairlist
2
>>> print(opts) # working
...
>>> str(opts) # working
>>> opts.items() # working
>>> repr(opts) # ValueError: Cannot handle R type 2
Другие вопросы по тегам