Importer dynamiquement un module depuis la mémoire dans Python 3 à l'aide de Hooks
Ce que je veux réaliser, c'est exactement ce que cette réponse propose, cependant en Python 3.
Le code ci-dessous fonctionne correctement dans 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
Cependant, lorsque vous apportez les modifications évidentes à Python 3 (en incluant exec et print entre parenthèses), j'obtiens le code suivant:
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
Non pas que le exec()
changement ait été assez significatif. Je n'ai pas compris ce que faisait cette ligne dans Python 2, je l'ai "traduite" comme je pense qu'elle est correcte. Cependant, le code Python 3 me donne l'erreur suivante:
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'
Que dois-je changer dans le code pour travailler dans Python 3 exactement de la même manière que dans Python 2?
Observation: Cela ne répond pas à ma question car je ne suis pas intéressé par l'importation d'un module depuis .pyc
.
Réponses
La réponse courte est que vous avez oublié de traduire la dernière moitié de l' exec
instruction à partir de l'exemple de code. Cela fait exec
appliquer in
le contexte de la load_module
méthode - pas le new_module
; alors spécifiez le contexte:
exec(self._modules[fullname], new_module.__dict__)
Cependant, en utilisant une version Python 3.4 ou supérieure, vous devenez soumis à PEP 451 (l'introduction des spécifications du module ), ainsi qu'à la dépréciation du impmodule, en faveur de importlib. Particulièrement:
- La imp.new_module(name)fonction est remplacée par
importlib.util.module_from_spec(spec)
. - Une classe de base abstraite pour les objets finder chemin méta est fourni:
importlib.abc.MetaPathFinder
. - Et ces objets de recherche utilisent désormais
find_spec
plutôt quefind_module
.
Voici une réimplémentation très proche de l'exemple de code.
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)