Я создаю приложение для управления некоторым оборудованием. У меня есть различное оборудование, которое я реализовал в пакетах: двигатели и измерительные устройства. Моя файловая структура следующая:
name_of_my_app/
__init__.py
main.py
config.ini
CONFIG.py
motors/
__init__.py
one_kind_of_motor.py
measurement_devices/
__init__.py
one_kind_of_measurement_device.py
Я хотел бы иметь один файл конфигурации (ini) для хранения настроек двигателей и измерительных устройств.
Я начал с ответа пользователя u/symmitchry на этот пост на Reddit: https://www.reddit.com/r/learnpython/comments/2hjxk5/whats_the_proper_way_to_use_configparser_across/ поскольку это казалось самым разумным поступком. Насколько я понимаю, я мог бы иметь config.ini
в корне моего приложения вместе с CONFIG.py
. CONFIG.py
позаботится о чтении настроек из config.ini
и сделает их доступными посредством импорта CONFIG.py
. Это работает, когда я импортирую конфигурацию из main.py
. Однако, когда я пытаюсь импортировать конфигурацию из one_kind_of_motor.py
с помощью:
from ... import CONFIG
как предложено @f3lix в этом посте: Импортируя модули из родительской папки, я получаю:
ImportError: attempted relative import with no known parent package
Именно тогда я добавил __init__.py
в корень, надеясь, что это решит мою проблему, но этого не произошло. Я также попробовал использовать ..
и .
вместо ...
, и единственное, что это сделало, это показало, как мало я понимаю в этой теме... (он продолжает выдавать ту же ошибку выше).
В настоящее время, чтобы обойти эту проблему, я читаю нужные мне настройки из config.ini
в каждом файле. Но я знаю, что это неоптимально.
Я программирую на Python как ученый, т. е. у меня нет опыта разработчика программного обеспечения, и я обычно использую базовый Python, но я хотел бы научиться простым способам распространения своих исследований. Я видел несколько постов с подобными вопросами (и вы можете позвать меня по этому поводу), но мне не удалось понять их ответ. Можете ли вы объяснить мне это с точки зрения непрофессионала?
Обновлено: В ответ на подробный ответ @ImpecableChicken я создал упрощенную файловую структуру и загрузил ее на GitHub, чтобы продемонстрировать проблему. С использованием
from .. import CONFIG
выдает ошибку
ImportError: attempted relative import with no known parent package
Я использую компьютер под управлением Windows и выполняю свои сценарии из визуального кода.
Я установил Visual Code и нажимаю «Выполнить»..Выполнить без отладки.
Этот ответ вместе с его верхними комментариями довольно хорошо объясняет относительный импорт. Важно отметить, что для относительного импорта точки указывают на то, что система импорта должна двигаться вверх в иерархии пакетов, а не в дереве файловой системы.
Предположим, что вы импортируете one_kind_of_motor.py
в main.py
:
python main.py
добавляет родительский каталог main.py
в sys.path
(т. е. name_of_my_app/
будет искать пакеты и модули).__main__
(соответствующего файлу main.py
) import motors.one_kind_of_motor
создаётся пакет motors
с подмодулем motors.one_kind_of_motor
. Пакет motors
не имеет родительского пакета; это видно из того, что в его названии нет точек. Родителем модуля motors.one_kind_of_motor
является motors
.motors.one_kind_of_motor
from .. import CONFIG
указывает, что система импорта должна подняться на два уровня вверх в иерархии пакетов. Первый .
соответствует пакету motors
, который является родительским для текущего модуля. Второй .
соответствует родительскому элементу motors
, но motors
не имеет родительского пакета, что приводит к ошибке.Возможно, будет полезно помнить, что в большинстве случаев количество начальных точек в операторе относительного импорта не может превышать количество точек в имени модуля, содержащего этот оператор. (Хотя это правило весьма полезно, в общем случае оно неверно; см., например, __package__ и __spec__.)
Важно отметить, что основной причиной проблемы является не оператор относительного импорта в one_kind_of_motor.py
, а скорее способ импорта модуля в main.py
. Относительный импорт в one_kind_of_motor.py
не удался, поскольку one_kind_of_motor.py
импортируется как motors.one_kind_of_motor
в другой файл.
Если вы собираетесь запускать только сценарии верхнего уровня (т. е. сценарии непосредственно в name_of_my_app/
), вы можете импортировать конфигурацию из one_kind_of_motor.py
с помощью
import CONFIG
При запуске python main.py
импорт завершается успешно, поскольку name_of_my_app/
находится в sys.path
.
(Для этого решения файл __init__.py
в name_of_my_app/
не нужен.)
Если вы хотите, чтобы относительный импорт работал, вы можете избежать ошибки, обеспечив, чтобы все файлы, предназначенные для импорта, были вложены в каталог, соседний со сценариями. Например, вы можете организовать свой проект следующим образом:
name_of_my_app/
main.py
devices/
__init__.py
config.ini
CONFIG.py
motors/
__init__.py
one_kind_of_motor.py
measurement_devices/
__init__.py
one_kind_of_measurement_device.py
Затем вы можете соответствующим образом настроить импорт в main.py
. С помощью этой структуры вы можете импортировать конфигурацию из one_kind_of_motor.py
с помощью
from .. import CONFIG
При запуске python main.py
one_kind_of_motor.py
теперь соответствует модулю devices.motors.one_kind_of_motor
, а оператор относительного импорта эквивалентен
from devices import CONFIG
Предполагая, что вы используете one_kind_of_motor.py
как скрипт:
python one_kind_of_motor.py
добавляет родительский каталог one_kind_of_motor.py
в sys.path
(т. е. name_of_my_app/motors/
будет искать пакеты и модули).__main__
(соответствует файлу one_kind_of_motor.py
) from .. import CONFIG
указывает, что система импорта должна подняться на два уровня вверх в иерархии пакетов. Первый .
соответствует родителю __main__
. В этом случае система импорта не может найти родительский пакет __main__
, поскольку __main__.__spec__
— это None
. Такое поведение описано в документации Python:Обратите внимание, что
__main__.__spec__
всегда являетсяNone
в последнем случае, даже если технически файл можно импортировать напрямую как модуль. Используйте переключатель -m, если в__main__
требуются действительные метаданные модуля.
Чтобы избежать ошибки, вы можете вызвать команду python
с ключом -m и указать имя модуля. В частности, настройте операторы импорта и структуру проекта, как описано в одном из решений выше, и убедитесь, что вы находитесь в каталоге name_of_my_app/
. Затем, для случая с несколькими пакетами, запустите
python -m motors.one_kind_of_motor
или, в случае с одним пакетом, запустите
python -m devices.motors.one_kind_of_motor
Это позволит гарантировать, что модуль __main__
(соответствующий файлу one_kind_of_motor.py
) содержит действительные метаданные модуля (как описано здесь).
Однако в документации упоминается важное предостережение, связанное с этим подходом:
Также обратите внимание, что даже если
__main__
соответствует импортируемому модулю и__main__.__spec__
установлено соответствующим образом, они все равно считаются отдельными модулями.
Следствием этого является то, что если one_kind_of_motor.py
является частью цикла импорта (т. е. запуск one_kind_of_motor.py
косвенно вызывает импорт самого себя), в итоге вы получите две копии всех классов и функций, определенных в one_kind_of_motor.py
. Если цикл импорта необходим (по возможности избегайте его), вы можете определить функцию точки входа в one_kind_of_motor.py
и вызывать эту функцию из main.py
или другого скрипта верхнего уровня. То есть в one_kind_of_motor.py
создайте функцию, содержащую нужный код точки входа:
# Name this whatever makes sense
def main():
...
Затем где-нибудь в main.py
или в другом скрипте в name_of_my_app/
вызовите эту функцию:
import devices.motors.one_kind_of_motor
...
# Call the entry point
# Alternatively, call this only for certain script arguments or other conditions
devices.motors.one_kind_of_motor.main()
Теперь вместо запуска one_kind_of_motor.py
напрямую запустите скрипт верхнего уровня, содержащий вызов импорта и точки входа.
Спасибо за подробный ответ @ImpecableChicken. Это понятно, но у меня почему-то это не работает. Должно быть, я совершаю ошибку, но я не знаю, что делаю не так. Я создал репозиторий, чтобы показать свою проблему. Когда я выполняю импорт из one_kind_of_motor.py с помощью from .. import CONFIG, я получаю: ImportError: попытка относительного импорта без известного родительского пакета. Я использую Windows, если это имеет значение. Не могли бы вы проверить мой репозиторий? Я обновлю свой пост соответствующим образом, если вы найдете что-то актуальное.
@Omnistic, я никогда не думал, что ты можешь выполнять one_kind_of_motor.py
напрямую. Я обновил свой ответ, включив в него решение и для этого случая.
Спасибо @ImpecableChicken за ваш ответ. В терминале VS Code, если я наберу python -m devices.motors.one_kind_of_motor
, это сработает и ошибок больше не будет. Однако я все еще обрабатываю всю эту информацию. Люди, которые программируют на Python с помощью VS Code, обычно запускают свои сценарии с помощью терминала вместо использования «Выполнить..Выполнить без отладки»?
@Omnistic, я никогда не использовал VS Code, но кажется этот ответ может помочь вам запустить любой файл в качестве модуля (хотя для этого требуется установка расширения).
Спасибо @ImpeccableChicken. Я не знаю, как я продвинулся так далеко в Python, даже не используя терминал, не говоря уже о «-m». Очень признателен.
Я не знаком с кодом VS, но в конфигурации запуска необходимо использовать ключ -m. То, что делает VS, вероятно, python.exe main.py
из папки name_of_my_app
. Вместо этого вам нужно бежать python -m name_of_my_app.main # note no .py
. Это сообщает Python, что в пакете name_of_my_app запустите модуль main.py как скрипт. Родительский пакет, который ищет Python, — это именно имя_my_app (должно содержать только символы, которые могут присутствовать в имени пакета Python).
Спасибо @Mr_and_Mrs_D за ваш ответ. Я понял, что это -m
важно и узнаю об этом больше.
Как вы используете свое приложение?