使用 Hooks 从 Python 3 中的内存动态导入模块
Dynamically import module from memory in Python 3 using Hooks
我想要实现的正是这个 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
.
导入模块不感兴趣
简短的回答是您忘记翻译代码示例中 exec
语句的后半部分。这导致 exec
被应用 in
load_module
方法的上下文——而不是 new_module
;所以指定上下文:
exec(self._modules[fullname], new_module.__dict__)
但是,使用 Python 3.4 或更高版本,您将受到 PEP 451 (the introduction of module specs), as well as the deprecation of the imp
module, in favor of importlib
的约束。特别是:
imp.new_module(name)
函数被 importlib.util.module_from_spec(spec)
取代。
- 提供了元路径查找器对象的抽象基础 class:
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)
我想要实现的正是这个 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
.
简短的回答是您忘记翻译代码示例中 exec
语句的后半部分。这导致 exec
被应用 in
load_module
方法的上下文——而不是 new_module
;所以指定上下文:
exec(self._modules[fullname], new_module.__dict__)
但是,使用 Python 3.4 或更高版本,您将受到 PEP 451 (the introduction of module specs), as well as the deprecation of the imp
module, in favor of importlib
的约束。特别是:
imp.new_module(name)
函数被importlib.util.module_from_spec(spec)
取代。- 提供了元路径查找器对象的抽象基础 class:
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)