Python:确定实际当前模块(而不是__main__)

20

我正在尝试确定函数的实际当前模块(在其他地方导入时可见),即使当前模块是“顶级脚本环境__main__

这听起来可能很奇怪,但背景是我需要对一个函数进行序列化和反序列化(包括参数),并在另一台机器上进行,为此我需要确保正确的模块而不是__main__在反序列化之前被导入(否则我会收到错误信息,指出“AttributeError:'module' object has no attribute my_fun”)。

到目前为止,我已经尝试过检查功能

import inspect
print inspect.getmodule(my_fun)

它给了我

<module '__main__' from 'example.py'>
当然,我也尝试使用 globals() 找到有用的东西,但没有成功。 我真正想要的是 <module 'example' from 'example.py'>。我猜一种可行的方法是使用类似于从文件名解析它的 hacky 方法。
m_name = __main__.__file__.split("/")[-1].replace(".pyc","")

然后通过名称查找模块 sys.modules[m_name].

有没有更简洁/更好的方法?

编辑: 在了解了ipython的“FakeModule”和一些更多的搜索之后,我遇到了这篇帖子,它描述了我面临的确切问题,包括我的当前解决方案(明确导入当前模块import current_module并序列化current_module.my_fun而不是my_fun)。我试图避免这种情况,因为这对我的软件包用户可能不直观。


你曾经研究过nose库的内部结构吗?特别是其中的importer模块?nose做了一些与此非常相似的事情,因此它可能是寻找灵感的好地方。 - Silas Ray
6个回答

11
我知道这已经过时了,但我在Python3中找到了一个对我有效的更简单的解决方案。长话短说,对象的_spec_也存储了实际的模块名称,而不是"_main_"。
import inspect
if obj.__class__.__module__ == "__main__":
    print(inspect.getmodule(obj).__spec__.name)

4
仅在作为模块运行时(使用python -m)才有效,否则__spec__None。但对我来说已经足够了!(此外,对于我的用例,我可以跳过inspect并直接使用import __main__; print(__main__.__spec__.name)。) - Søren Løvborg
如果您在主模块中,您也可以只使用__spec__.name而无需其他任何导入。 - Endogen
这里的obj是什么?它没有被定义。 - undefined

8

我曾经遇到过同样的问题。

我使用的方法是:

return os.path.splitext(os.path.basename(__main__.__file__))[0]

这与你的“hack”实际上是相同的。老实说,我认为这是最好的解决方案。


谢谢你,温斯顿。这是我选择的解决方案,自从使用以来我没有遇到任何问题。 - soramimo
如果您在文件夹中运行脚本,例如“bar/foo.py”,则此方法无效。 - jwayne
@jwayne,它会产生什么? - Winston Ewert
@WinstonEwert 假设我创建了一个文件 ./bar/foo.py,其中包含 import os; import __main__; print os.path.splitext(os.path.basename(__main__.__file__))[0]。从 . 运行 python bar/foo.py 会得到 foo,而不是 bar.foo - jwayne
1
@jwayne,恐怕在那个上下文中foo是该模块的正确名称。Python并不关心它是否包含在bar文件夹中。 - Winston Ewert

6
编辑:回想起来,远远最好、最干净的解决方案是首先避免陷入这种情况;如果要序列化的是您的代码,请将所有可序列化的函数移动到由主程序脚本加载的模块中。这样,在任何情况下都可以检索到函数的来源,而无需使用任何技巧或特殊情况。

如果不可能实现上述方法,我认为您最初的解决方案(从__main__.__file__中检索模块名称)是最好、最简单的。如果您担心它对用户来说似乎不直观,请将其包装在一个漂亮的函数中,并记录其用途。

当您将一个模块作为__main__运行时,Python确实不会将其与普通模块名称关联起来:如果您导入示例import example,它将第二次以独立模块的形式加载文件。事实上,在您的情况下,这可能真的发生了,否则您就找不到您的模块名在sys.modules中了:模块example和模块__main__确实是分开的运行时对象,如果您明确地更改其中一个模块变量,您会发现这一点。


2

你可以用__import__导入模块,然后使用类似以下的方式来使用getattr。这可能不是最好的方法,但对我而言可行。

(在这里,我使用了一些在此帖子中描述的动态加载模块的思路。)

def dynamic_import(name):
    mod = __import__(name)
    components = name.split('.')
    for comp in components[1:]:
        mod = getattr(mod, comp)
    return mod

tmodule = dynamic_import('modA')
# Print the module name
print tmodule
# Use the module's contents
t = tmodule.myObject()
t.someMethod()

modA.py文件的内容如下:

class myObject():
    def someMethod(self):
        print "I am module A"

从中可以看到,我们获取了导入模块的名称,并且仍然以正常方式使用模块内的对象和方法。运行时输出如下:

python experiment.py 
<module 'modA' from 'modA.pyc'>
I am module A

再次说明,这可能不是“理想”的方法,但它在大多数情况下运作良好,并且据我所知,不涉及任何不良的权衡。希望有所帮助。


1
我认为现有的回答都没有直接回答问题:当模块作为__main__运行时,如何获取模块的名称?使用inspect进行大部分步骤...
import inspect

def module_name(obj):
    module_name = obj.__module__

    if "__main__" in module_name:
        # get parent modules of object
        mod_obj = inspect.getmodule(obj) # type: module

        # from the filename of the module, get its name
        mod_suffix  = inspect.getmodulename(inspect.getmodule(obj).__file__)

        # join parent to child with a .
        module_name = '.'.join([mod_obj.__package__, mod_suffix])

    return module_name

注意,我认为module_name行需要是:如果mod_obj.__package__不为空,则module_name = '.'。join([mod_obj.__package__,mod_suffix]),否则为mod_suffix。 - Eric Zinda
有趣的是,在调试器下运行时,module.__package__为空字符串,而在非调试模式下则为None。因此,修复方法实际上应该是:name = '.'.join([mod_obj.package, mod_suffix]) if (mod_obj.package != "" and mod_obj.package is not None) else mod_suffix。 - Eric Zinda
为什么要手动组装模块名称,而不是直接使用 inspect.getmodule(obj).__spec__.name - bluenote10
为什么要手动组装模块名称,而不直接使用inspect.getmodule(obj).__spec__.name - bluenote10
已经过了几年了,但如果我没记错的话,那是因为它是为一个被以编程方式导入到非标准位置的模块,所以规范与“常规”导入位置不匹配。我会编辑进去。 - undefined

-1

从 Python 3.4 开始,

importlib.util.find_spec('__main__').name

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接