【问题标题】:Dynamically import module from memory in Python 3 using Hooks在 Python 3 中使用 Hooks 从内存中动态导入模块
【发布时间】:2021-03-08 13:58:04
【问题描述】:

我想要实现的正是 this answer 所建议的,但是在 Python 3 中。

以下代码在 Python 2 中运行良好:

import sys
import imp

modules = {
"my_module":
"""class Test:
    def __init__(self):
        self.x = 5
    def print_number(self):
        print self.x"""}    

class StringImporter(object):

   def __init__(self, modules):
       self._modules = dict(modules)


   def find_module(self, fullname, path):
      if fullname in self._modules.keys():
         return self
      return None

   def load_module(self, fullname):
      if not fullname in self._modules.keys():
         raise ImportError(fullname)

      new_module = imp.new_module(fullname)
      exec self._modules[fullname] in new_module.__dict__
      return new_module


if __name__ == '__main__':
   sys.meta_path.append(StringImporter(modules))

   from my_module import Test
   my_test = Test()
   my_test.print_number() # prints 5

但是,当对 Python 3 进行明显更改时(将 exec 和 print 括在括号中),我得到以下代码:

import sys
import imp

modules = {
"my_module":
"""class Test:
    def __init__(self):
        self.x = 5
    def print_number(self):
        print(self.x)"""}    

class StringImporter(object):

   def __init__(self, modules):
       self._modules = dict(modules)


   def find_module(self, fullname, path):
      if fullname in self._modules.keys():
         return self
      return None

   def load_module(self, fullname):
      if not fullname in self._modules.keys():
         raise ImportError(fullname)

      new_module = imp.new_module(fullname)
      exec(self._modules[fullname])
      return new_module


if __name__ == '__main__':
   sys.meta_path.append(StringImporter(modules))

   from my_module import Test
   my_test = Test()
   my_test.print_number() # Should print 5

并不是说exec() 的变化非常重要。我不明白那行在 Python 2 中做了什么,我按照我认为正确的方式“翻译”了它。但是,Python 3 代码给了我以下错误:

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    from my_module import Test
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 655, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 626, in _load_backward_compatible
KeyError: 'my_module'

为了在 Python 3 中以与在 Python 2 中完全相同的方式工作,我应该对代码进行哪些更改?

观察:This 没有回答我的问题,因为我对从 .pyc 导入模块不感兴趣。

【问题讨论】:

    标签: python python-3.x python-import python-module


    【解决方案1】:

    简短的回答是您忘记翻译代码示例中exec 语句的后半部分。这导致exec 被应用in load_module 方法的上下文——而不是new_module;所以指定上下文:

    exec(self._modules[fullname], new_module.__dict__)
    

    但是,使用 Python 版本 3.4 或更高版本时,您将受制于 PEP 451(引入 module specs),以及弃用 imp 模块,转而支持 importlib。特别是:

    • imp.new_module(name) 函数已替换为 importlib.util.module_from_spec(spec)
    • 提供了元路径查找器对象的抽象基类:importlib.abc.MetaPathFinder
    • 并且此类查找器对象现在使用find_spec 而不是find_module

    这是代码示例的非常接近的重新实现。

    import importlib
    import sys
    import types
    
    
    class StringLoader(importlib.abc.Loader):
    
        def __init__(self, modules):
            self._modules = modules
    
        def has_module(self, fullname):
            return (fullname in self._modules)
    
        def create_module(self, spec):
            if self.has_module(spec.name):
                module = types.ModuleType(spec.name)
                exec(self._modules[spec.name], module.__dict__)
                return module
    
        def exec_module(self, module):
            pass
    
    
    class StringFinder(importlib.abc.MetaPathFinder):
    
        def __init__(self, loader):
            self._loader = loader
    
        def find_spec(self, fullname, path, target=None):
            if self._loader.has_module(fullname):
                return importlib.machinery.ModuleSpec(fullname, self._loader)
    
    
    if __name__ == '__main__':
        modules = {
            'my_module': """
        BAZ = 42
    
        class Foo:
            def __init__(self, *args: str):
                self.args = args
            def bar(self):
                return ', '.join(self.args)
        """}
    
        finder = StringFinder(StringLoader(modules))
        sys.meta_path.append(finder)
    
        import my_module
        foo = my_module.Foo('Hello', 'World!')
        print(foo.bar())
        print(my_module.BAZ)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-04
      • 1970-01-01
      • 1970-01-01
      • 2019-10-07
      • 2011-05-06
      • 1970-01-01
      • 2018-04-29
      相关资源
      最近更新 更多