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

Posted

技术标签:

【中文标题】在 __main__.py 中使用模块自己的对象【英文标题】:Using module's own objects in __main__.py 【发布时间】:2011-03-25 14:06:42 【问题描述】:

我正在尝试从其__main__.py 内部访问模块的数据。

结构如下:

mymod/
    __init__.py
    __main__.py

现在,如果我像这样在__init__.py 中公开一个变量:

__all__ = ['foo']
foo = 'bar': 'baz'

如何从__main__.py 访问foo

【问题讨论】:

起来!我遇到过很多次这样的问题,看到如此明显的行为没有按照预期的方式工作,我感到非常失望。 【参考方案1】:

您需要在sys.path 中已有该软件包,将包含mymod 的目录添加到__main__.py 中的sys.path,或使用-m 开关。

mymod 添加到路径看起来像这样(在__main__.py 中):

import sys
import os
path = os.path.dirname(sys.modules[__name__].__file__)
path = os.path.join(path, '..')
sys.path.insert(0, path)
from myprog import function_you_referenced_from_init_file

使用-m 开关希望:

python -m mymod

更多讨论请见this answer。

【讨论】:

from mymod import foo 之前添加sys.path.append(os.getcwd()) 效果很好,谢谢 我认为这依赖于从父目录执行包的人。在这种情况下,python mymod. sys.path.append( os.path.dirname(__file__) ) 会将正在执行的文件的目录添加到sys.path中 但是请注意,在调用 sys.path.insert() 之前,包含 __main__.py 的目录位于 sys.path 中,这使得与 __main__.py 相邻的所有模块成为***模块,而不是命名空间下__main__.py 的包裹。如果包包含与 stdlib 模块相同的模块名称,则包提供的模块将在 stdlib 模块之前被选择。改用sys.path[0] = path 可能会更好(覆盖默认路径,以便只有包的命名空间在 sys.path 中)。【参考方案2】:

我遇到这类事情最多的问题是,我经常想将__init__.py 文件作为脚本运行以测试功能,但在加载包时不应运行这些文件。对于python <package>/__init__.pypython -m <package> 之间的不同执行路径,有一个有用的解决方法。

$ python -m <module> 执行 <package>/__main__.py__init__.py 未加载。 $ python <package>/__init__.py 只是像普通脚本一样执行脚本__init__.py

问题

当我们希望__init__.py 有一个if __name__ == '__main__': ... 子句使用来自__main__.py 的内容时。我们不能导入__main__.py,因为它总是会从解释器的路径中导入__main__.pyc。 (除非……我们使用绝对路径导入技巧,这会导致很多其他混乱)。

解决方案 一个解决方案:)

为模块的__main__ 使用两个脚本文件:

<package>/
         __init__.py
         __main__.py
         main.py

# __init__.py

# ...
# some code, including module methods and __all__ definitions

__all__ = ['foo', 'bar']
bar = 'key': 'value'
def foo():
    return bar
# ...
if __name__ == '__main__':
    from main import main
    main.main()

# __main__.py

# some code...such as:
import sys
if (len(sys.argv) > 1 and sys.argv[1].lower() == 'option1'):
    from main import main()
    main('option1')
elif (len(sys.argv) > 1 and sys.argv[1].lower() == 'option2'):
    from main import main()
    main('option2')
else:
    # do something else?
    print 'invalid option. please use "python -m <package> option1|option2"'

# main.py

def main(opt = None):
    if opt == 'option1':
        from __init__ import foo
        print foo()
    elif opt == 'option2':
        from __init__ import bar
        print bar.keys()
    elif opt is None:
        print 'called from __init__'

main.py 中的导入在我们从 __init__.py 运行的情况下可能并不理想,因为我们正在将它们重新加载到另一个模块的本地范围中,尽管已经在 __init__.py 中加载了它们,但是显式装载应避免循环装载。如果你确实在你的main.py 中再次加载整个__init__ 模块,它不会被加载为__main__,所以就循环加载而言应该是安全的。

【讨论】:

【参考方案3】:

a package 的__init__ 模块就像包本身的成员一样,所以直接从mymod 导入对象:

from mymod import foo

或者

from . import foo

如果您喜欢简洁,请阅读relative imports。例如,您需要像往常一样确保不以mymod/__main__.py 调用模块,因为这会阻止Python 将mymod 检测为包。你不妨看看distutils

【讨论】:

我想让模块目录可执行。 是的,我已经尝试了这些建议,但它们对我不起作用。 当我使用from . import foo 时,我得到一个ValueError: Attempted relative import in non-package,否则ImportError: No module named mymod【参考方案4】:

如果您使用python -m mymod 运行模块,则__main__.py 中的代码将能够从模块的其余部分导入,而无需将模块添加到sys.path

【讨论】:

【参考方案5】:

我发现第一个答案很有用(即破解 sys.path),但在 Python 3.4 中添加了 pathlib,我发现以下代码更加简单和 Pythonic:

import sys
from pathlib import Path

# You don't need to .insert(), just append
sys.path.append(str(Path(__file__).parent.parent))

【讨论】:

【参考方案6】:

模块目录结构如下:

py/
   __init__.py
   __main__.py

__init__.py

#!/usr/bin/python3
#
# __init__.py
#

__all__ = ['foo']
foo = 'bar': 'baz'
info =  "package": __package__,
         "name": __name__,
         "locals": [x for x in locals().copy()] 
print(info)

__main__.py

#!/usr/bin/python3
#
# __main__.py
#

info =  "package": __package__,
         "name": __name__,
         "locals": [x for x in locals().copy()] 
print(info)
from . import info as pyinfo
print("pyinfo: ": pyinfo)

使用-m 标志将模块作为脚本执行

$ python -m py

# the printout from the 'print(info)' command in __init__.py
'name': 'py', 'locals': ['__all__', '__builtins__', '__file__', '__package__', '__path__', '__name__', 'foo', '__doc__'], 'package': None
# the printout from the 'print(info)' command in __main__.py
'name': '__main__', 'locals': ['__builtins__', '__name__', '__file__', '__loader__', '__doc__', '__package__'], 'package': 'py'
# the printout from the 'print(pyinfo)' command in __main__.py
'pyinfo: ': 'name': 'py', 'locals': ['__all__', '__builtins__', '__file__', '__package__', '__path__', '__name__', 'foo', '__doc__'], 'package': None

【讨论】:

这是你的答案?来点上下文或描述来配合你正在做的事情怎么样? 这是一个巨大而丑陋的黑客。通过本地人复制变量?哈哈哈对不起,但这真的很有趣。

以上是关于在 __main__.py 中使用模块自己的对象的主要内容,如果未能解决你的问题,请参考以下文章

if__name__ == '__main__'

1.4.3 编写自己的模块和包

尽管有 __init__.py,Python 仍无法导入自定义模块

day_5.11 py main

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

关于__name__和__main__