使用 `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 个回答
这样做会违反 PEP8 的规定,PEP8 是一个关于代码风格的指南,它说:
导入的内容应该总是放在文件的最上面,紧跟在模块的注释和文档字符串后面,然后再是模块的全局变量和常量。
为了让 gui.py
成功导入 __main__.DEBUG
,你必须在 import gui
之前先设置好 DEBUG
的值。
我觉得有两个主要的原因,可能会让人建议避免使用这种模式。
- 它让你很难看出你导入的变量是从哪里来的。
- 如果你的程序有多个入口点,这种方式会让维护变得困难。想象一下,如果有人(很可能是你自己)想把某些功能提取到一个独立的库中,他们就得删除或重新定义那些孤立的引用,才能让这个库在你的应用之外可用。
如果你完全控制这个应用,并且将来不会有其他入口点或其他用途,而且你确定自己不介意这种模糊性,我认为没有什么客观的理由认为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
。这种写法意味着你可以在程序运行期间修改这个变量,并且这个变化会在其他地方反映出来(假设只有一个线程/进程,显然)。
另一个好处是,这种模式相对常见,其他开发者会很容易识别它。它可以和一些流行应用(比如django
)使用的settings.py
文件相提并论,尽管我避免使用这个特定的名字,因为settings.py
通常是一些静态对象,而不是运行时参数的命名空间。上述描述的配置命名空间模块的其他好名字可以是runtime
或params
等。