フックを使用してPython3のメモリからモジュールを動的にインポートする
Nov 25 2020
私が達成したいのは、まさにこの答えが提案していることですが、Python3ではそうです。
以下のコードはPython2で正常に機能します。
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()
変更がかなり重要だったというわけではありません。その行がPython2で何をするのか理解できませんでしたが、正しいと思う方法で「翻訳」しました。ただし、Python3コードでは次のエラーが発生します。
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 2で動作するのとまったく同じようにPython3で動作するには、コードで何を変更する必要がありますか?
観察:からモジュールをインポートすることに興味がないので、これは私の質問に答えません.pyc
。
回答
1 ZachGates Nov 27 2020 at 08:28
簡単な答えはexec
、コードサンプルからステートメントの後半を翻訳するのを忘れたということです。これにより、;ではなくメソッドのコンテキストexec
が適用されます。したがって、コンテキストを指定します。in
load_module
new_module
exec(self._modules[fullname], new_module.__dict__)
ただし、バージョン3.4以降のPythonを使用すると、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)