Могу ли я использовать основной модуль python для тестирования? - PullRequest
0 голосов
/ 16 апреля 2020

Я занимаюсь разработкой библиотеки Python на Python 3.8.2, и я хотел бы запустить модуль в качестве основного для целей тестирования. Когда я пытаюсь, я получаю ошибку ModuleNotFound.

Вот моя структура библиотеки:

.
├── foo
│   ├── __init__.py
│   ├── bar.py
│   └── quux
│       ├── __init__.py
│       ├── corge.py
│       └── garply.py
├── main.py

bary.py:

def baz():
    print("baz")

corge.py

from foo.quux.garply import *


def grault():
    waldo()
    print("grault")


if __name__ == '__main__':
    grault()

garply.py

def waldo():
    print("waldo")

main.py

from foo.bar import *
from foo.quux.corge import *

if __name__ == '__main__':
    baz()
    grault()

(все __init__.py файлы пусты)

Когда я запускаю main.py , это работает.

$ python main.py
baz
waldo
grault

Если я пытаюсь запустить corge.py, я получаю следующую ошибку:

$ python foo/quux/corge.py 
Traceback (most recent call last):
  File "foo/quux/corge.py", line 1, in <module>
    from foo.quux.garply import *
ModuleNotFoundError: No module named 'foo'

Не имеет значения, какой у меня текущий рабочий каталог, всегда выдает эту ошибку.

$ cd foo/quux/
$ python corge.py 
Traceback (most recent call last):
  File "corge.py", line 1, in <module>
    from foo.quux.garply import *
ModuleNotFoundError: No module named 'foo'

Пока я тестировал это, я создал новый проект PyCharm с PyCharm 2020.1 и реализовал структуру, которую я описал. К моему удивлению, он работает с обнаруженной конфигурацией запуска по умолчанию.

Я пытался использовать venv, который автоматически создает PyCharm, но он все равно не работал. Это не работает, если я напрямую копирую / вставляю команду и использую их CWD. Он не работает с терминалом, встроенным в PyCharm. Он только работает с кнопкой запуска PyCharm.

Я что-то не так делаю со своей структурой модуля? Если так, что PyCharm может сделать, чтобы заставить это работать? Если нет, то почему он не работает вне PyCharm?

Ответы [ 2 ]

1 голос
/ 16 апреля 2020

Python должен знать каталог, в котором находится foo, чтобы импортировать его. sys.path перечисляет каталоги, в которых python будет искать.

Когда вы устанавливаете пакет, установщик беспокоится об этом - обычно помещая модуль в хорошо известный каталог или добавляя путь к пакету установки в sys.path.

Когда вы запускаете скрипт, python автоматически добавляет путь этого скрипта к sys.path, поэтому при запуске main.py вы найдете foo.

Как запустить модуль как __main__

Один из вариантов - сделать пакет устанавливаемым (setup.py, wheel, et c .... ) и используйте режим разработки ( "pip install --editable ./" vs "python setup.pyvelop" для некоторого обсуждения). Это то, что я делаю, когда разрабатываю что-то, что планирую сделать доступным для других.

Другой способ - добавить свой каталог в PYTHONPATH, возможно, даже при запуске программы. На linux это будет

PYTHONPATH=path/to/fooproject:$PYTHONPATH python foo/quux/corge.py

Еще один, и я тоже это делаю, это взломать путь в самом модуле. __file__ дает ваше имя файла относительно текущего рабочего каталога, и вы знаете, насколько глубоко вы находитесь в иерархии вашего пакета. Таким образом, вы можете просто сделать __file__ абсолютным и удалить пару имен каталогов

corge.py

import sys
import os

if __name__ == "__main__":
    # I'm two levels deep in the package so package directory is
    packagedir = os.path.abspath(os.path.join(os.path.dirname(__file__),
        "..", ".."))
    sys.path.insert(0, packagedir)
    import foo

Наконец, не делайте этого в первую очередь. Когда вы запускаете corge.py как скрипт, он получает другое пространство имен __main__, чем foo.bar.corge, импортированное как модуль. Его глобальные переменные / классы / функции загружаются дважды, и вы получаете разные в зависимости от того, вызываете ли вы их через пространство имен __main__ или foo.bar.corge.

Лучше взять что-нибудь, что вам хотелось бы поместить в основную часть в corge.py, сделать их отдельными скриптами. Например, вы можете добавить def main() к своим модулям. В main.py вы можете добавить опцию --run foo.bar.corge, сообщающую main для импорта corge.py, и запустить ее main(). Для этого можно использовать подкоманды argparse .

1 голос
/ 16 апреля 2020

Ваш вызов main.py работает, потому что Python будет искать в непосредственных подкаталогах модули. Любой модуль в любом другом месте, который хочет импортировать foo.yadda.whatever, должен будет найти foo, выполнив поиск по PYTHONPATH. Поэтому вам нужно добавить родительский каталог foo в ваш PYTHONPATH.

...