Tự động nhập mô-đun từ bộ nhớ trong Python 3 bằng Hooks
Tuy nhiên, những gì tôi muốn đạt được là chính xác những gì câu trả lời này đề xuất trong Python 3.
Đoạn mã dưới đây hoạt động tốt trong 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
Tuy nhiên, khi thực hiện các thay đổi rõ ràng đối với Python 3 (bao gồm tệp thực thi và in trong dấu ngoặc đơn), tôi nhận được mã sau:
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
Không phải là sự exec()thay đổi là khá quan trọng. Tôi không hiểu dòng đó đã làm gì trong Python 2, tôi đã "dịch" nó theo cách tôi nghĩ là đúng. Tuy nhiên, mã Python 3 mang lại cho tôi lỗi sau:
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'
Tôi nên thay đổi điều gì trong mã để hoạt động trong Python 3 giống hệt như cách nó hoạt động trong Python 2?
Quan sát: Điều này không trả lời câu hỏi của tôi vì tôi không quan tâm đến việc nhập mô-đun từ .pyc.
Trả lời
Câu trả lời ngắn gọn là bạn đã quên dịch nửa sau của execcâu lệnh từ mẫu mã. Điều đó làm execcho inngữ cảnh của load_modulephương pháp được áp dụng - không phải new_module; vì vậy hãy chỉ định ngữ cảnh:
exec(self._modules[fullname], new_module.__dict__)
Tuy nhiên, sử dụng phiên bản Python 3.4 hoặc cao hơn, bạn phải tuân theo PEP 451 (giới thiệu các thông số kỹ thuật của mô-đun ), cũng như việc ngừng sử dụng impmô-đun importlib. Đặc biệt:
- Các imp.new_module(name)chức năng được thay thế bằng
importlib.util.module_from_spec(spec). - Một lớp cơ sở trừu tượng cho các đối tượng cụ tìm con đường meta được cung cấp:
importlib.abc.MetaPathFinder. - Và các đối tượng tìm kiếm như vậy bây giờ sử dụng
find_spechơn làfind_module.
Dưới đây là phần thực hiện lại rất gần của mẫu mã.
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)