Запустите программу python из другой программы python (с определенными требованиями)

Допустим, у меня есть два скрипта Python A.py и B.py. Я ищу способ запустить B изнутри A таким образом, чтобы:

  1. B считает, что это __main__ (так что код в блоке if __name__=="__main__" в B будет работать)
  2. B на самом деле не __main__ (чтобы, например, он не перезаписывал запись "__main__" в sys.modules)
  3. Исключения, возникающие в B, распространяются на A (т. е. могут быть перехвачены предложением except в A).
  4. Эти исключения, если они не перехвачены, генерируют правильную трассировку, ссылающуюся на номера строк в B.

Я пробовал разные методы, но ни один из них не удовлетворяет всем моим требованиям.

  • использование инструментов из модуля подпроцесса означает, что исключения в B не распространяются на A.
  • execfile("B.py", {}) запускает B, но не считает его основным.
  • execfile("B.py", {'__name__': '__main__'}) заставляет B.py думать, что он главный, но он также, похоже, искажает печать обратной трассировки исключений, так что обратные трассировки ссылаются на строки внутри A (т.е. настоящий __main__).
  • использование imp.load_source с __main__ в качестве имени почти работает, за исключением того, что оно фактически изменяет sys.modules, тем самым топча существующее значение __main__

Есть ли способ получить то, что я хочу?

(Причина, по которой я это делаю, заключается в том, что я выполняю некоторую очистку существующей библиотеки. В этой библиотеке нет реального набора тестов, просто набор «примеров» сценариев, которые производят определенный вывод. Я пытаюсь использовать их как тесты, чтобы гарантировать, что моя очистка не повлияет на способность библиотеки выполнять эти примеры, поэтому я хочу запускать каждый пример сценария из своего набора тестов. Я хотел бы иметь возможность видеть исключения из этих сценариев в тестовом сценарии, поэтому тестовый сценарий может сообщать о типе сбоя, а не просто сообщать об общей ошибке SubprocessError всякий раз, когда пример сценария вызывает какое-либо исключение.)


person BrenBarn    schedule 06.07.2012    source источник


Ответы (2)


Ваш вариант использования имеет смысл, но я все же думаю, что вам лучше реорганизовать тесты, чтобы их можно было запускать извне.

У вас в тестовых скриптах есть что-то подобное?

def test():
    pass

if __name__ == '__main__':
    test()

Если нет, возможно, вам следует преобразовать свои тесты в вызов такой функции, как test. Затем из вашего основного тестового сценария вы можете просто:

import test1
test1.test()
import test2
test2.test()

Предоставьте общий интерфейс для запуска тестов, который используют сами тесты. Наличие большого блока кода в __main__ чеке — это нехорошо.

Извините, что не ответил на заданный вами вопрос, но я считаю, что это правильное решение без слишком отклонений от исходного тестового кода.

person Josh Smeaton    schedule 06.07.2012
comment
К сожалению нет. Некоторые из тестовых сценариев делают это, но другие просто содержат код в виде кода модуля верхнего уровня. Это просто смесь странных случаев. Я разобрался с проблемой и ответил на свой вопрос, но спасибо за совет. Хотел бы я последовать за ним! :-) - person BrenBarn; 06.07.2012

Отвечая на мой собственный вопрос, потому что результат довольно интересен и может быть полезен другим:

Оказывается, я ошибался: execfile("B.py", {'__name__': '__main__'} все-таки правильный путь. Он действительно правильно создает трассировку. То, что я видел с неправильными номерами строк, было не исключениями, а предупреждениями. Эти предупреждения были созданы с использованием warnings.warn("blah", stacklevel=2). Предполагается, что аргумент stacklevel=2 позволяет создавать такие вещи, как предупреждения об устаревании, там, где используется устаревшее, а не при вызове предупреждения (см. документацию).

Однако похоже, что файл execfile-d не считается «уровнем стека» для этой цели и невидим для stacklevel целей. Таким образом, если код на верхнем уровне модуля execfile-d вызывает предупреждение с уровнем стека 2, предупреждение не возникает в строке с правильным номером в исходном файле execfile-d; вместо этого он возникает в соответствующем номере строки файла, в котором выполняется исполняемый файл.

Это прискорбно, но я могу с этим смириться, так как это всего лишь предупреждения, поэтому они не повлияют на реальную производительность тестов. (Сначала я не заметил, что несоответствие номеров строк повлияло только на предупреждения, потому что в тестовом выводе было много предупреждений и исключений.)

person BrenBarn    schedule 06.07.2012
comment
Интересное решение. Сомневаюсь (и надеюсь...), что мне это когда-нибудь понадобится, но то, что позволяет и позволяет python, довольно крутое. - person Josh Smeaton; 06.07.2012