Запуск доктрин через iPython и псевдоконсоли

У меня есть довольно базовый доктрируемый файл:

class Foo():
    """
    >>> 3+2
    5
    """

if __name__ in ("__main__", "__console__"):
    import doctest
    doctest.testmod(verbose=True)

который работает, как ожидается, при запуске непосредственно через python.

Однако в iPython я получаю

1 items had no tests:
    __main__
0 tests in 1 items.
0 passed and 0 failed.
Test passed.

Так как это часть проекта Django и ему потребуется доступ ко всем соответствующим переменным, и что настройки manage.py, я также могу запустить его с помощью модифицированной команды, которая использует код .InteractiveConsole, один из которых __name__ устанавливается в '__console__'.

С приведенным выше кодом я получаю тот же результат, что и с iPython. Я попытался изменить последнюю строку:

 this = __import__(__name__)
 doctest.testmod(this, verbose=True)

и я получаю ImportError на __console__, что имеет смысл, я думаю. Это не влияет ни на python, ни на ipython.

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

Просто для разъяснения, вот что я ожидаю:

Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

Ответ 1

Следующие работы:

$ ipython
...
In [1]: %run file.py

Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

In [2]: 

Я понятия не имею, почему ipython file.py не работает. Но выше это, по крайней мере, обходное решение.

EDIT:

Я нашел причину, почему она не работает. Это довольно просто:

  • Если вы не укажете модуль для тестирования в doctest.testmod(), он предполагает, что вы хотите протестировать модуль __main__.
  • Когда IPython выполняет файл, переданный ему в командной строке, модуль __main__ - это IPython __main__, а не ваш модуль. Поэтому doctest пытается выполнить доктрины в записи IPython script.

Следующие работы, но чувствуют себя немного странно:

if __name__ == '__main__':
    import doctest
    import the_current_module
    doctest.testmod(the_current_module)

Таким образом, в основном модуль импортирует себя (что "немного странно" ). Но это работает. Что-то мне не нравится. этот подход заключается в том, что каждый модуль должен включать в свое имя свое имя.

ИЗМЕНИТЬ 2:

Следующий script, ipython_doctest, делает ipython таким образом, как вы хотите:

#! /usr/bin/env bash

echo "__IP.magic_run(\"$1\")" > __ipython_run.py
ipython __ipython_run.py

script создает python script, который выполнит %run argname в IPython.

Пример:

$ ./ipython_doctest file.py
Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Python 2.5 (r25:51908, Mar  7 2008, 03:27:42) 
Type "copyright", "credits" or "license" for more information.

IPython 0.9.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython features.
%quickref -> Quick reference.
help      -> Python own help system.
object?   -> Details about 'object'. ?object also works, ?? prints more.

In [1]:

Ответ 2

Корневая проблема заключается в том, что ipython играет странные трюки с помощью __main__ (через свой собственный модуль FakeModule), так что к моменту doctest следует, что "предполагаемый модуль" через его __dict__, Foo НЕ есть - так что доктритель не рекурсирует в него.

Здесь одно решение:

class Foo():
    """
    >>> 3+2
    5
    """

if __name__ in ("__main__", "__console__"):
    import doctest, inspect, sys
    m = sys.modules['__main__']
    m.__test__ = dict((n,v) for (n,v) in globals().items()
                            if inspect.isclass(v))
    doctest.testmod(verbose=True)

Это ПРОИЗВОДИТ, как и было запрошено:

$ ipython dot.py 
Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.__test__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Python 2.5.1 (r251:54863, Feb  6 2009, 19:02:12) 
  [[ snip snip ]]
In [1]: 

Просто установка глобального __test__ не работает, опять же потому, что, устанавливая его как глобальное, о чем вы думаете, поскольку __main__ НЕ на самом деле помещает его в __dict__ фактического объекта, который восстанавливается m = sys.modules['__main__'], а последнее точно выражает выражение doctest, использующее внутренне (на самом деле оно использует sys.modules.get, но дополнительная предосторожность здесь не нужна, так как мы знаем, что __main__ существует в sys.modules... it просто НЕ тот объект, которого вы ожидаете! -).

Кроме того, просто установка m.__test__ = globals() напрямую не работает, по другой причине: doctest проверяет, что значения в __test__ являются строками, функциями, классами или модулями, и без какого-либо выбора вы не можете гарантировать, что globals() удовлетворяет этому условию (на самом деле это не будет). Здесь я выбираю только классы, если вы также хотите использовать функции или еще не можете использовать or в предложении if в genexp в вызове dict.

Я точно не знаю, как вы запускаете оболочку Django, способную выполнить ваш script (как я полагаю, python manage.py shell не принимает аргументы, вы должны делать что-то еще, и я не могу угадайте, что именно! -), но подобный подход должен помочь (независимо от того, использует ли ваша оболочка Django ipython, по умолчанию, когда доступно, или простой Python): надлежащим образом установите __test__ в объекте, который вы получаете как sys.modules['__main__'] (или __console__, если то, что вы потом передаете на doctest.testmod, я думаю) должно работать, поскольку оно имитирует то, что будет делать доктринер, чтобы внутренне определить ваши тестовые строки.

И, в заключение, философское размышление о дизайне, архитектуре, простоте, прозрачности и "черной магии"...:

Все эти усилия в основном необходимы для победы над "черной магией", что ipython (и, возможно, Django, хотя это может быть просто делегирование этой части на ipython) делает от вашего имени "удобство"... любое время, когда две рамки (или более;-) независимо делают каждый свой собственный бренд черной магии, интероперабельность может внезапно потребовать значительных усилий и стать чем угодно, НО удобна; -).

Я не говорю, что такое же удобство могло быть предоставлено (любым одним или несколькими ipython, django и/или доктринами) без черной магии, интроспекции, поддельных модулей и т.д.; дизайнеры и сторонники каждой из этих фреймворков - превосходные инженеры, и я ожидаю, что они полностью выполнили свою домашнюю работу и будут выполнять только минимальное количество черной магии, которое необходимо для обеспечения удобства пользователя, которое им было необходимо. Тем не менее, даже в такой ситуации "черная магия" внезапно превращается из мечты удобства в кошмар отладки, как только вы хотите сделать что-то даже незначительно вне того, что задумал каркас.

ОК, может быть, в этом случае не совсем кошмар, но я замечаю, что этот вопрос был открыт некоторое время, и даже с приманкой щедрости он еще не получил много ответов - хотя у вас теперь есть два ответы на вопрос от моего, используя специальную особенность __test__ doctest, @codeape, используя особенность __IP.magic_run функции ironpython. Я предпочитаю мой, потому что он не полагается на что-либо внутреннее или недокументированное. __test__ Является документированной функцией doctest, а __IP, с этими двумя надвигающимися ведущими подчеркиваниями, кричит "глубокие внутренности, не прикасается" ко мне; -)... если он сломается в следующем выпуске, я бы не удивился. Тем не менее, вопрос вкуса - этот ответ можно считать более "удобным".

Но это точно моя точка зрения: удобство может принести огромную цену с точки зрения отказа от простоты, прозрачности и/или избежания внутренних/недокументированных/неустойчивых функций; поэтому, как урок для всех нас, наименьшая черная магия & c, мы можем уйти (даже ценой отказа от удобства пользования здесь и там), тем счастливее мы все будем в долгосрочной перспективе (и счастливее мы сделаем других разработчиков, которые должны использовать наши текущие усилия в будущем).