我应该在 __main__.py 中使用啥形式的导入,然后我应该如何运行项目?

Posted

技术标签:

【中文标题】我应该在 __main__.py 中使用啥形式的导入,然后我应该如何运行项目?【英文标题】:What form of imports should I use in __main__.py and then how should I run the project?我应该在 __main__.py 中使用什么形式的导入,然后我应该如何运行项目? 【发布时间】:2020-11-28 01:26:23 【问题描述】:

假设我有以下简单的项目结构。

project/
    package/
        __init__.py
        __main__.py
        module.py
    requirements.txt
    README.md

在对 Google 进行了一些研究之后,我试图让它反映一个非常简单的控制台应用程序的一般最佳实践(但不像简单地拥有一个脚本那么简单)。假设__init__.py 只包含print("Hello from __init__.py"),而module.py 包含类似的语句。

我应该如何在__main__.py 内部进行导入,然后我应该如何运行我的项目?

首先让我们说__main__.py 看起来很简单:

import module
print("Hello from __main__.py")

如果我使用简单的命令 python package 运行我的项目,我会得到以下输出:

Hello from module.py
Hello from __main__.py

可以看出,__init__.py 没有运行。我认为这是因为我将项目的包作为脚本运行。如果我改为使用命令python -m package 将其作为模块运行,我会得到以下输出:

Hello from __init__.py
Traceback (most recent call last):
  File "C:\Users\MY_USER\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\MY_USER\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\MY_USER\PATH\TO\PROJECT\project\package\__main__.py", line 1, in <module>
    import module
ModuleNotFoundError: No module named 'module'

如果我将__main__.py 中的第一行更改为import package.module 并再次运行python -m package,我会得到以下输出:

Hello from __init__.py
Hello from module.py
Hello from __main__.py

太棒了!将我的项目包作为模块运行时,现在似乎一切正常。现在,如果我再次尝试 python package 并将其作为脚本运行会怎样?

Traceback (most recent call last):
  File "C:\Users\MY_USER\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Users\MY_USER\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "package\__main__.py", line 1, in <module>
    import package.module
ModuleNotFoundError: No module named 'package'

好的。因此,如果我错了,请纠正我,但似乎我有两个选择。我可以在我的包中编写导入以将其作为脚本运行或作为模块运行,但不能同时运行。哪个更好,如果确实更可取,为什么?你什么时候使用命令python package vs python -m package,为什么?在我可能不理解的简单项目中编写导入是否有一些一般规则?我是否缺少其他基本内容?

总结:在这种情况下,最佳做法是什么,为什么它是最佳做法,以及您何时将项目设置为替代方法(python packagepython -m package)?

【问题讨论】:

【参考方案1】:

分发可执行 python 代码的最常见方法是将其打包到可安装的.wheel 文件中。如果它只是一个文件,您也可以分发它。但是,一旦您获得两个文件,就会遇到您遇到的确切导入问题,此时您需要一些元数据来为导入提供明确定义且可见的顶层(例如,在您的情况下为 package.module) 、脚本代码的入口点、第三方依赖...所有这些都是通过“使代码可安装”来实现的。

如果您喜欢技术文档,您可以阅读 Python 打包机构 (PyPA) 提供的 this 和 this 教程,了解其具体含义。


不过,为了让您开始您的项目,您缺少的是一个 setup.py 文件,该文件将包含安装说明和一个脚本入口点,以提供一种从包中运行可执行代码的方法:

from setuptools import setup

with open("requirements.txt") as f:
    requirements = [line.strip() for line in f.readlines()]


setup(
    # obligatory, just the name of the package. you called it "package" in your
    # example so that's the name here
    name="package",
    # obligatory, when it's done you can give it a 1.0
    version="0.1",
    # point the installer to the module (read, folder) that contains your code,
    # by convention usually the same as the package name
    packages=["package"],
    # if there are dependencies, specify them here. actually you can delete the
    # requirements.txt and just paste the content here, but this here will also work
    install_requires=requirements,
    # point the installer to the function that will run your executable code.
    # the key name has got to be 'console_script', the value content is up
    # to you, with its interpretation being:
    # 'package_command' -> the name that you can call your code by
    # 'package.__main__' -> the path to the file that you want to call
    # 'test' -> the actual function that contains the code 
    entry_points='console_scripts': ['package_command=package.__main__:test']
)

将此文件添加为project/setup.py 后,您需要牢记以下几点:

将您的脚本代码(例如print('hello world)')放入您的__main__.py 文件中名为test 的函数中[1] 运行pip install -e .在本地安装你的包[2] 通过在命令行上运行 package_command 来执行脚本代码 - python packagepython -m package 都不是运行可安装 python 脚本的最佳实践

[1] 将一个简单的函数绑定到一个脚本入口点是最基本的。您可能不想重新发明诸如帮助文本、参数解析/验证之类的东西……所以如果您真的想编写一个 cli 应用程序,您可能需要查看类似 click 的东西来处理对你来说乏味的东西。

[2] 这将执行开发安装,这在开发过程中很方便,因为这意味着您可以在处理它时测试行为。如果你想分发你的代码,你会运行pip wheel .(可能需要在之前运行pip install wheel)。这将创建一个 wheel 文件,您可以将其提供给您的客户,以便他们可以使用 pip install package-0.1-py3-none-any.whl 手动安装它,之后package_command 也将在他们的系统上可用。

【讨论】:

以上是关于我应该在 __main__.py 中使用啥形式的导入,然后我应该如何运行项目?的主要内容,如果未能解决你的问题,请参考以下文章

在 __main__.py 中使用模块自己的对象

[python][转载]__init__.py使用

如何简单地理解Python中的if __name__ == '__main__'

python里的if __name__ == '__main__'是啥意思,简单点

Python中if name == 'main':的作用

如何在 Python 中获取 __main__ 模块的文件名?