如何为基本软件包设置配置__main__.py,__ init__.py和__setup__.py?

时间:2017-07-07 18:11:26

标签: python pip setuptools

背景:

我有一个像这样的目录结构:

Package/
    setup.py
    src/
        __init__.py
        __main__.py 
        code.py

我希望能够以很多不同的方式运行代码。

  1. pip install Package然后python然后from Package import *

  2. python -m Package应该__main__.py

  3. python __main__.py也应该在__main__.py中执行此操作,但这一次,我们假设您已下载了源而不是pip installing

  4. 现在我已经让前两个工作了,但设置很乱:

    setup.py:

    setup(
        name='Package',
        packages=['Package'],
        package_dir={'Package': 'src'},
        ...
        entry_points={ 'console_scripts': ['Package = src.__main__:main' ] }
    

    __ INIT __ PY:

    from Package.code import .......
    

    __主__ PY:

    from . import .......
    

    对我来说更有意义的是在两种情况下写

    from code import ........
    

    但是这给了我导入错误。

    问题:

    我的方式真的是唯一的方法吗?

    最重要的是,我如何支持第三个用例?现在,python __main__.py抛出

    File "__main__.py", line 10, in <module>
        from . import code
    ImportError: cannot import name 'class defined in code.py'
    

    说明:

    我已阅读

3 个答案:

答案 0 :(得分:24)

你几乎拥有所需的一切(甚至更多)!我将采用以下设置:

<强> code.py

foo = 1

<强> __ INIT __ PY:

from .code import foo

在此处执行相对导入,因为导入整个包时将使用__init__.py。请注意,我们使用.语法将导入显式标记为相对,因为这是Python 3所必需的(如果您from __future__ import absolute_import,则需要在Python 2中。)

<强> __主__ PY:

from Package import foo

print('foo = ', foo)

这是包的主脚本,因此我们使用绝对import语句。通过这样做,我们假设已经安装了包(或者至少已经放在路径上);这就是应该处理包裹的方式!您可能认为这与您的第三个用例冲突,但实际上在处理包时没有理由pip install。这真的不是什么大问题(特别是在使用virtualenv时)!

如果您需要修改源文件并通过运行__main__.py文件轻松观察更改,那么您只需使用-e(&#34;可编辑的&#34; )switch:pip install -e .(假设你在目录Package中)。但是,使用当前的目录结构,这不会起作用,因为-e开关会将egg-link放置到包含setup.py文件的目录中;此目录不包含名为Package的包,而是包含src的包(我有a question about that)。

相反,如果您按照约定在包本身之后命名包源的根目录(对于您的示例是Package),那么使用-e进行安装不是问题:Python 在相应的目录中找到所需的包Package

$ tree Package/
Package/
├── setup.py
└── Package   <-- Renamed "src" to "Package" because that's the package's name.
    ├── code.py
    ├── __init__.py
    └── __main__.py

这也可以省略package_dir={'Package': 'src'}setup.py的额外定义。

关于setup.py 的说明:对于您指定的三个用例,无需定义入口点。那就是你可以跳过entry_points={ 'console_scripts': ['Package = src.__main__:main' ] }行。通过发运__main__.py模块python -m Package,可以轻松执行此模块中的代码。您还可以添加额外的if子句:

def main():
    print('foo = ', foo)

if __name__ == '__main__':
    main()

另一方面,入口点允许您直接从CLI执行__main__.main中的代码;正在运行的$ Package将执行相应的代码。

小结

最重要的是,在处理软件包时,我总是使用pip install。为什么不,特别是如果您已经创建了setup.py文件?如果要对包裹进行实时修改&#34;实时&#34;然后你可以用-e开关安装(这可能需要重命名src文件夹,见上文)。因此,您的第三个用例将读作&#34;下载源代码和pip install (-e) Package(在virtualenv中);然后你可以运行python __main__.py&#34;。

修改

不使用__main__.py

运行pip install

如果您不想通过pip安装软件包但仍然可以运行__main__.py脚本,我仍然可以使用上述设置。然后我们需要确保from Package import ...语句仍然成功,这可以通过扩展导入路径来实现(请注意,这需要将src目录重命名为包&# 39;名字!)。

修改PYTHONPATH

对于Linux bash,您可以按如下方式设置Pythonpath:

export PYTHONPATH=$PYTHONPATH:/path/to/Package

或者,如果您与__main__.py位于同一目录中:

export PYTHONPATH=$PYTHONPATH:`cd ..; pwd`

当然,不同的操作系统有不同的方式。

__main__.py

中扩展路径

您(或者更确切地说是您的同事)可以将以下行添加到脚本的顶部(from Package import ...语句之前):

import sys
sys.path.append('/path/to/Package')

sitecustomize.py

中扩展路径

您可以将名为sitecustomize.py的模块放在Python安装的lib/python3.5/site-packages/目录中,其中包含以下行:

import sys
sys.path.append('/path/to/Package')

创建单独的顶级main.py脚本

所以你有以下布局:

$ tree Package/
Package/
├── main.py   <-- Add this file.
├── setup.py
└── src
    ├── code.py
    ├── __init__.py
    └── __main__.py

main.py包含

import src.__main__

现在__main__.py被视为src包的一部分,相对导入将起作用。 您现在可以运行python src/__main__.py而不是python main.py

答案 1 :(得分:2)

from code import .........失败,因为您的系统上没有安装名为code的Python包。您的系统上有一个名为code的Python 模块,但是在您的import语句中,您没有指定可以找到code模块的包。

__init__.py中的src/文件的目的告诉Python src/目录应该被视为Python包,其内容作为包中的模块。由于code.pysrc/文件一起位于__init__.py,因此您的code模块位于src包中。

现在您知道可以找到code模块的哪个包,您可以通过以下方式从中导入内容:

from src.code import .........

另外,作为旁注:__init__.py仅通过出现在src/目录中完成其工作,因此它甚至不需要包含任何代码。因此,将__init__.py文件留空是个好主意。

答案 2 :(得分:0)

我经常使用此设置,因为它适用于python setup.py develop

Package_root/
    setup.py
    src/
        Package/
            __init__.py
            __main__.py 
            code.py

这可能不是(还)你想要的详细答案,但我认为值得尝试这三个用例。

setup( ...
    package_dir = {'': 'src'},
    entry_points = {'console_scripts': ['Package = Package.__main__:main'],},
    packages = find_packages(exclude=["Package.egg_info",]),
...)