使用 Hooks 在 Python 3 中从内存中动态导入模块

问题描述 投票:0回答:2

我想要实现的正是这个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 中完全相同的方式工作,我应该在代码中更改什么?

观察:没有回答我的问题,因为我对从

.pyc
导入模块不感兴趣。

python python-3.x python-import python-module
2个回答
2
投票

简短的回答是你忘记翻译代码示例中

exec
语句的后半部分。这导致
exec
被应用
in
load_module
方法的上下文——而不是
new_module
;所以指定上下文:

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

但是,使用 Python 3.4 或更高版本,您将受到 PEP 451模块规范 的介绍),以及

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)

0
投票

有加载子模块的方法吗?

    modules = {
        'my_module.submodule': """
    BAZ = 42

    class Foo:
        def __init__(self, *args: str):
            self.args = args
        def bar(self):
            return ', '.join(self.args)
    """}

© www.soinside.com 2019 - 2024. All rights reserved.