获取给定其变量字典的模块实例



假设我有一个模块的字典(通过vars(mod),或mod.__dict__,或globals()),例如:

import mod
d = vars(mod)

给定字典d,我怎样才能取回模块mod? 即我想编写一个函数get_mod_from_dict(d),如果字典属于模块,则返回模块,或者None

>>> get_mod_from_dict(d)
<module 'mod'>

如果get_mod_from_dict返回一个模块,我必须拥有它

mod = get_mod_from_dict(d)
assert mod is None or mod.__dict__ is d

我实际上可以这样实现它:

def get_mod_from_dict(d):
mods = {id(mod.__dict__): mod for (modname, mod) in sys.modules.items()
if mod and modname != "__main__"}
return mods.get(id(d), None)

但是,这对我来说似乎效率低下,以迭代sys.modules.

有没有更好的方法?


我为什么需要这个?

  • 在某些情况下,您只能访问字典。 例如,在堆栈帧中。然后,根据您要执行的操作,可能只是出于检查/调试目的,取回模块会很有帮助。

  • 我为Pickler写了一些扩展,它可以腌制方法、函数等。其中一些具有对模块或模块字典的引用。 只要我在酸洗过程中有属于模块的字典,我都不想腌制字典,而是对模块的引用。

每个模块都有一个__name__属性,用于唯一标识导入系统中的模块:

>>> import os
>>> os.__name__
'os'
>>> vars(os)['__name__']
'os'

导入的模块也缓存在sys.modules中,这是一个将模块名称映射到模块实例的字典。您可以简单地在那里查找模块的名称:

import sys
def get_mod_from_dict(module_dict):
module_name = module_dict['__name__']
return sys.modules.get(module_name)

有些人担心这可能不适用于包中的(子)模块,但它确实:

>>> import urllib.request
>>> get_mod_from_dict(vars(urllib.request))
<module 'urllib.request' from '/usr/lib/python3.7/urllib/request.py'>

但是,有一个非常小的警告:这仅适用于已由导入机制正确导入和缓存的模块。如果模块是使用诸如如何在给定完整路径的情况下导入模块?之类的技巧导入的,则该模块可能不会缓存在sys.modules中,然后您的函数可能会意外返回None

您可以使用importlib.import_module导入给定名称的模块。numpy示例


In [77]: import numpy 
...: import importlib                                                                                                                                                                               
In [78]: d = vars(numpy)                                                                                                                                                                                
In [79]: np = importlib.import_module(d['__name__'])                                                                                                                                                    
In [80]: np.array([1,2,3])                                                                                                                                                                              
Out[80]: array([1, 2, 3])

为了完整起见,通过gc模块提供了另一种解决方案:

def get_mod_from_dict_3(d):
"""
:param dict[str] d:
:rtype: types.ModuleType|None
"""
objects = gc.get_referrers(d)
for obj in objects:
if isinstance(obj, types.ModuleType) and vars(obj) is d:
return obj
return None

不过,使用gc可能依赖于Python解释器。并非所有的 Python 解释器都有 GC。即使他们有,我不确定是否可以保证模块引用其字典(尽管很可能确实如此;它真的想不出为什么它不会有的好理由)。

因此,我认为通过sys.modules[d['__name__']]的另一种解决方案可能更好。

虽然我检查了CPython和PyPy,但在这两种情况下,这个解决方案都有效。而且,此解决方案更通用。它甚至可以工作(无需检查ModuleType),甚至对于任何任意对象。

尽管考虑到不同的Python解释器,我甚至可以想象一个Python解释器,其中vars(mod)永远不会返回相同的字典,这将即时创建字典。那么这样的功能根本无法实现。不确定。

我在这里收集了所有给定的解决方案和一些测试代码。

您最终可以通过使用生成器来改进您的解决方案:

def get_mod_from_dict_2(d):
return next((mod for modname, mod in sys.modules.items() if mod and modname != "__main__" and id(mod.__dict__) == id(d)), None)

但这不会帮助您避免使用sys.modules......

更新:正如 @Devesh Kumar Singh 的回答中所述,您可以使用 importlib 模块按名称检索已导入的模块(如果尚未导入,则导入它)。只要模块不是"__main__"模块,模块的字典就会保存模块的名称和文件。从那里,您可以执行以下操作:

import importlib
import some_module
d = vars(some_module)
print(d['__name__']) # >> 'some_module'
m = importlib.import_module(d['__name__'])
print(m)   # >> <module 'some_module' from '/path/to/some_module.py'>

最新更新