Является ли хорошей практикой использование `import __main__`?

Я работаю над сравнительно большим Python-приложением, и есть несколько ресурсов, которые я хотел бы сохранить в качестве глобальных переменных, доступных через несколько различных модулей. Эти значения, такие как номер версии, дата версии, глобальная конфигурация и некоторые статические пути к ресурсам. Я также включил DEBUG флаг, который устанавливается параметром командной строки, чтобы я мог запускать свое приложение в режиме отладки без необходимости использования полной среды.

Значения, которые я импортирую, я тщательно проверял, чтобы они не менялись в ходе работы программы, и я задокументировал их как глобальные постоянные переменные, к которым не следует прикасаться. Мой код выглядит по существу как


# Main.py
import wx
from gui import Gui

DEBUG = False
GLOBAL_CONFIG = None
VERSION = '1.0'
ICON_PATH = 'some/path/to/the/app.ico'

def main():
    global DEBUG, GLOBAL_CONFIG

    # Simplified
    import sys
    DEBUG = '--debug' in sys.argv

    GLOBAL_CONFIG = load_global_config()
    # Other set-up for the application, e.g. setting up logging, configs, etc

    app = wx.App()
    gui = Gui()
    app.MainLoop()

if __name__ == '__main__':
    main()

# gui.py
import wx
from __main__ import DEBUG, GLOBAL_CONFIG, ICON_PATH

import controller


class Gui(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None)

        icon = wx.Icon(ICON_PATH, wx.BITMAP_TYPE_ICO)
        self.SetIcon(icon)

        # Always make a copy so we don't accidentally modify it
        conf = GLOBAL_CONFIG.copy()
        self.controller = controller.Controller(conf)

        # More setup, building the layout, etc

# controller.py
from __main__ import DEBUG

import logging
log = logging.getLogger('controller')

class Controller(object):
    def __init__(self, conf):
        if DEBUG:
            log.info("Initializing controller in DEBUG mode")
        self.conf = conf
        # Other setup ...

Это явно далеко от того, что на самом деле представляет собой мое приложение, и пренебрегает обработкой ошибок, документацией и, в основном, всеми деталями реализации.

Я видел, что это говорит о том, что это плохая идея, но без объяснения причин. Поскольку большинство результатов при поиске вариантов "python import __main__" - это вопросы о том, что if __name__ == '__main__' Это трудно найти надежную информацию по этой теме. До сих пор у меня не было проблем с этим, и на самом деле это было действительно удобно.

Так это считается хорошей практикой Python, или есть причина, по которой я должен избегать этого дизайна?

2 ответа

Решение

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

  • Он запутывает происхождение переменных, которые вы импортируете.
  • Он ломается (или, по крайней мере, трудно поддерживать), если ваша программа имеет несколько точек входа. Представьте себе, если бы кто-то, возможно, вы захотели извлечь какое-то подмножество ваших функций в автономную библиотеку - им пришлось бы удалить или переопределить каждую из этих потерянных ссылок, чтобы сделать ее пригодной для использования вне вашего приложения.

Если у вас есть полный контроль над приложением, и никогда не будет другой точки входа или другого использования ваших функций, и вы уверены, что не против двусмысленности, я не думаю, что есть какая-либо объективная причина, почему from __main__ import foo картина плохая. Мне не нравится это лично, но опять же, это в основном по двум причинам выше.


Я думаю, что более надежное / дружественное к разработчикам решение может быть примерно таким, создав специальный модуль специально для хранения этих суперглобальных переменных. Затем вы можете импортировать модуль и обратиться к module.VAR в любое время вам нужна настройка. По сути, просто создаем специальное пространство имен модулей для хранения суперглобальной конфигурации времени выполнения.

# conf.py (for example)
# This module holds all the "super-global" stuff.
def init(args):
    global DEBUG
    DEBUG = '--debug' in args
    # set up other global vars here.

Затем вы бы использовали его более примерно так:

# main.py
import conf
import app

if __name__ == '__main__':
    import sys
    conf.init(sys.argv[1:])

    app.run()

# app.py
import conf

def run():
    if conf.DEBUG:
        print('debug is on')

Обратите внимание на использование conf.DEBUG скорее, чем from conf import DEBUG, Эта конструкция означает, что вы можете изменять переменную в течение срока действия программы и отражать это изменение в другом месте (очевидно, предполагая один поток / процесс).


Другим преимуществом является то, что это довольно распространенная модель, поэтому другие разработчики с готовностью распознают ее. Это легко сопоставимо с settings.py файл, используемый различными популярными приложениями (например, django), хотя я избегал этого конкретного имени, потому что settings.py обычно это набор статических объектов, а не пространство имен для параметров времени выполнения. Другие хорошие имена для модуля пространства имен конфигурации, описанного выше, могут быть runtime или же params, например.

Это требует нарушения PEP8, в котором указано

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

Для того чтобы gui.py успешно импортировать __main__.DEBUGвам нужно будет установить значение DEBUG до import gui,

Другие вопросы по тегам