Importieren Sie das Modul mithilfe von Hooks dynamisch aus dem Speicher in Python 3

Nov 25 2020

Was ich erreichen möchte, ist genau das, was diese Antwort vorschlägt, jedoch in Python 3.

Der folgende Code funktioniert in Python 2 einwandfrei:

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

Wenn ich jedoch die offensichtlichen Änderungen an Python 3 vornehme (einschließlich exec und print in Klammern), erhalte ich den folgenden Code:

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

Nicht, dass die exec()Änderung ziemlich bedeutend gewesen wäre. Ich habe nicht verstanden, was diese Zeile in Python 2 bewirkt hat. Ich habe sie so "übersetzt", wie ich es für richtig halte. Der Python 3-Code gibt mir jedoch den folgenden Fehler:

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'

Was muss ich im Code ändern, um in Python 3 genauso zu arbeiten wie in Python 2?

Beobachtung: Dies beantwortet meine Frage nicht, da ich nicht daran interessiert bin, ein Modul aus zu importieren .pyc.

Antworten

1 ZachGates Nov 27 2020 at 08:28

Die kurze Antwort lautet, dass Sie vergessen haben, die zweite Hälfte der execAnweisung aus dem Codebeispiel zu übersetzen . Das bewirkt exec, dass inder Kontext der load_moduleMethode angewendet wird - nicht der new_module; Geben Sie also den Kontext an:

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

Bei Verwendung einer Python-Version 3.4 oder höher unterliegen Sie jedoch PEP 451 (Einführung der Modulspezifikationen ) sowie der Ablehnung des impModuls zugunsten von importlib. Insbesondere:

  • Die imp.new_module(name)Funktion wird ersetzt durch importlib.util.module_from_spec(spec).
  • Eine abstrakte Basisklasse für Meta Path Finder-Objekte wird bereitgestellt : importlib.abc.MetaPathFinder.
  • Und solche Finder-Objekte verwenden jetzt find_speceher als find_module.

Hier ist eine sehr genaue Neuimplementierung des Codebeispiels.

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)