Структурирование модуля python, чтобы его можно было запускать из командной строки с ключом -m и без него - PullRequest
2 голосов
/ 23 мая 2019

Фон

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

Желаемый результат

Модуль может быть запущен с использованием:

python -m ./my_module --help
# No -m switch
python ./my_module --help

Структура

Вслед за этим ответом Я хотел бы понять, что переключение -m имеет к файлам __main__.py и __init__.py. Текущая структура выглядит следующим образом

__main__.py

# Shebang line


###########
# Modules #
###########
import argparse
import logging
import tempfile


###################
# Package modules #
###################
from utilities import check_directory_access
# ...


#################
# Run functions #
#################
def run(args):
    """Run all functions with provided arguments"""
    # Start logging configuration
    # If file is not provided use temporary file
    if args.log_file is None:
        args.log_file = tempfile.NamedTemporaryFile(delete=False,
                                                    prefix='my_module',
                                                    suffix='.log').name
    # Create handlers: console
    # logging configuration
    logging.shutdown()


def main():
    """Process arguments and run main function"""
    parser = argparse.ArgumentParser(description='Dop stuff module',
                                     epilog='Epilog')
    parser.add_argument("-t", "--tables", nargs='+', dest='tables',
                        help="List tables to refresh", type=str)
    parser.add_argument("-l", "--log-file", dest='log_file',
                        type=str, help="Log file")
    parser.set_defaults(func=run)
    args = parser.parse_args()
    args.func(args)

if __name__ == "__main__":
    main()

__init__.py

###################
# Package modules #
###################
from .utilities import check_directory_access
# Other components

Задача

Продолжительность:

python -m my_module --help

Возвращает

ImportError: нет модуля с именем 'utilities'

* +1040 * тогда * +1041 *
python my_module --help

работает без проблем

Желаемый результат

  • Структурирование импорта таким образом, чтобы оба оператора python my_module и python -m my_module работали.
  • Не ломается import my_module при интерактивной работе
  • (бонус) Запуск без вызова python переводчика первым на ./my_module --help. Я не уверен, как это сделать с деревом:

    |-- my_module
    |   |-- my_module.py
    |   |-- __init__.py
    |   |-- __main__.py
    |   |-- module_component_A.py
    |   |-- utilities.py
    

    Есть ли конкретный контент, который должен идти на my_module.py?

1 Ответ

3 голосов
/ 23 мая 2019

Python 3 не подразумевает относительный импорт. Используйте абсолютный или явный относительный импорт:

from .utilities import check_directory_access
from my_module.utilities import check_directory_access

Это заставляет ваш пакет работать с переключателем -m. Также допускается import my_module в интерактивном сеансе.


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

Либо сохраните пакет в виде исполняемого zip-файла, либо создайте скрипт, который запускает ваш пакет.

#!/usr/bin/env python3
import my_module.__main__

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

У вас не должно быть сценария, находящегося внутри вашего пакета - для этого требуется, чтобы вы изменили sys.path на родительский каталог, что может привести к дублированию модулей. Например, utilities.py будет доступен в виде отдельных модулей my_module.utilities и utilities.

...