假设我有一个模块的字典(通过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'>