根据模块的变量获取模块实例

时间:2019-05-16 15:17:17

标签: python

假设我有一个模块的字典(通过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似乎效率很低。

有更好的方法吗?


我为什么需要这个?

  • 在某些情况下,您只能访问dict。例如。在堆栈框架中。然后,根据您想做什么,也许只是出于检查/调试的目的,找回模块很有帮助。

  • 我为Pickler写了一些扩展,可以扩展方法,函数等的内容。其中一些引用了模块或模块字典。在腌制过程中,无论我有一个属于模块的字典,我都不想腌制该字典,而要引用该模块。

4 个答案:

答案 0 :(得分:5)

每个模块都有一个__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'>

有一个 警告,但是:仅适用于已由导入机制正确导入并缓存的模块。如果某个模块已使用How to import a module given the full path?之类的技巧导入,则该模块可能不会缓存在sys.modules中,然后您的函数可能会意外返回None

答案 1 :(得分:4)

您可以使用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])

答案 2 :(得分:1)

为完整起见,通过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)永远不会返回相同的dict,而这将动态地创建dict。这样的功能根本无法实现。不确定。

我收集了所有给定的解决方案以及一些测试代码here

答案 3 :(得分:0)

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

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'>