在 python-3.4+ 中实现 post 导入挂钩的正确方法
Proper way of implementing post import hooks in python-3.4+
我想修改导入行为。我在 David Beazley 和 Brian K. Jones 的书“Python Cookbook”中找到了一个带有 post import hook 的例子,这应该适合我的问题。
由于最新版本是在 python-3.3
时发布的,因此提供的示例已过时。只好自己修改,让代码兼容python-3.4
后的importlib
。在 PostImportFinder
的原始版本中定义了 classfind_module(self, fullname, path = None)
并且在 PostImportLoader
而不是 create_module()
中定义了 load_module()
。
这是一个可重现的小例子:
#postimport.py
import
import importlib
import sys
from collections import defaultdict
_post_import_hooks = defaultdict(list)
class PostImportFinder:
def __init__(self):
self._skip=set()
def find_spec(self, fullname, path = None, target = None):
if fullname in self._skip:
return None
self._skip.add(fullname)
return PostImportLoader(self)
class PostImportLoader:
def __init__(self, finder):
self._finder = finder
def create_module(self, spec):
importlib.import_module(spec.name)
module = sys.modules[spec.name]
for func in _post_import_hooks[spec.name]:
func(module)
self._finder._skip.remove(spec.name)
return module
def when_imported(names):
def decorate(func):
for fullname in names:
if fullname in sys.modules:
print(f'importing {fullname}')
func(sys.modules[fullname])
else:
_post_import_hooks[fullname].append(func)
return func
return decorate
sys.meta_path.insert(0,PostImportFinder)
#postimportfunc.py
from inspect import getmembers, isfunction, isclass
from postimport import when_imported
list_of_module_names = ['simple']
#Some decorator. For example purposes pretty simple
@when_imported(list_of_module_names)
def decorate(mod):
# Decorate classes
print(f'module {mod} imported')
#simple.py
class A:
def __init__(self):
self.x=42
def bar(self):
print(self.x)
#start.py
import postimportfunc
from simple import A
foo = A()
foo.bar()
当我运行时出现start.py
以下错误:
runfile('/home/user/reproduce/start.py', wdir='/home/user/reproduce')
File "/usr/lib/python3/dist-packages/spyder_kernels/customize/spydercustomize.py", line 827, in runfile
execfile(filename, namespace)
File "/usr/lib/python3/dist-packages/spyder_kernels/customize/spydercustomize.py", line 110, in execfile
exec(compile(f.read(), filename, 'exec'), namespace)
File "/home/user/reproduce/start.py", line 2, in <module>
import postimportfunc
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
File "<frozen importlib._bootstrap>", line 971, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 914, in _find_spec
File "/home/user/reproduce/postimport.py", line 12, in find_spec
if fullname in self._skip:
AttributeError: 'str' object has no attribute '_skip'
所以我的问题很明显:我做错了什么?我根本看不出 _skip
和 str
是如何相关的,因为我将 _skip
初始化为一个集合。
是否有 another/better 方法如何更改指定模块的 post 导入行为?
更新:P.S。我忘了说,我目前使用 python-3.7 .
主要原因是 postimport.py
最后一行的一个愚蠢的拼写错误。
而不是:
sys.meta_path.insert(0,PostImportFinder)
应该是:
sys.meta_path.insert(0,PostImportFinder()) #DO NOT FORGET PARENTHESIS
但这不是唯一的问题。
我还必须更改以下内容:
在PostImportFinder
重命名find_spec(self, fullname, path = None, targer = None)
成find_module(self, fullname, path = None)
并在 PostImportLoader
中将 find_spec()
替换为:
def load_module(self, fullname):
importlib.import_module(fullname)
module = sys.modules[fullname]
for func in _post_import_hooks[fullname]:
func(module)
self._finder._skip.remove(fullname)
return module
所以,总结一下:我应该坚持问题中提到的书中的食谱。
我想修改导入行为。我在 David Beazley 和 Brian K. Jones 的书“Python Cookbook”中找到了一个带有 post import hook 的例子,这应该适合我的问题。
由于最新版本是在 python-3.3
时发布的,因此提供的示例已过时。只好自己修改,让代码兼容python-3.4
后的importlib
。在 PostImportFinder
的原始版本中定义了 classfind_module(self, fullname, path = None)
并且在 PostImportLoader
而不是 create_module()
中定义了 load_module()
。
这是一个可重现的小例子:
#postimport.py
import
import importlib
import sys
from collections import defaultdict
_post_import_hooks = defaultdict(list)
class PostImportFinder:
def __init__(self):
self._skip=set()
def find_spec(self, fullname, path = None, target = None):
if fullname in self._skip:
return None
self._skip.add(fullname)
return PostImportLoader(self)
class PostImportLoader:
def __init__(self, finder):
self._finder = finder
def create_module(self, spec):
importlib.import_module(spec.name)
module = sys.modules[spec.name]
for func in _post_import_hooks[spec.name]:
func(module)
self._finder._skip.remove(spec.name)
return module
def when_imported(names):
def decorate(func):
for fullname in names:
if fullname in sys.modules:
print(f'importing {fullname}')
func(sys.modules[fullname])
else:
_post_import_hooks[fullname].append(func)
return func
return decorate
sys.meta_path.insert(0,PostImportFinder)
#postimportfunc.py
from inspect import getmembers, isfunction, isclass
from postimport import when_imported
list_of_module_names = ['simple']
#Some decorator. For example purposes pretty simple
@when_imported(list_of_module_names)
def decorate(mod):
# Decorate classes
print(f'module {mod} imported')
#simple.py
class A:
def __init__(self):
self.x=42
def bar(self):
print(self.x)
#start.py
import postimportfunc
from simple import A
foo = A()
foo.bar()
当我运行时出现start.py
以下错误:
runfile('/home/user/reproduce/start.py', wdir='/home/user/reproduce')
File "/usr/lib/python3/dist-packages/spyder_kernels/customize/spydercustomize.py", line 827, in runfile
execfile(filename, namespace)
File "/usr/lib/python3/dist-packages/spyder_kernels/customize/spydercustomize.py", line 110, in execfile
exec(compile(f.read(), filename, 'exec'), namespace)
File "/home/user/reproduce/start.py", line 2, in <module>
import postimportfunc
File "<frozen importlib._bootstrap>", line 991, in _find_and_load
File "<frozen importlib._bootstrap>", line 971, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 914, in _find_spec
File "/home/user/reproduce/postimport.py", line 12, in find_spec
if fullname in self._skip:
AttributeError: 'str' object has no attribute '_skip'
所以我的问题很明显:我做错了什么?我根本看不出 _skip
和 str
是如何相关的,因为我将 _skip
初始化为一个集合。
是否有 another/better 方法如何更改指定模块的 post 导入行为?
更新:P.S。我忘了说,我目前使用 python-3.7 .
主要原因是 postimport.py
最后一行的一个愚蠢的拼写错误。
而不是:
sys.meta_path.insert(0,PostImportFinder)
应该是:
sys.meta_path.insert(0,PostImportFinder()) #DO NOT FORGET PARENTHESIS
但这不是唯一的问题。 我还必须更改以下内容:
在PostImportFinder
重命名find_spec(self, fullname, path = None, targer = None)
成find_module(self, fullname, path = None)
并在 PostImportLoader
中将 find_spec()
替换为:
def load_module(self, fullname):
importlib.import_module(fullname)
module = sys.modules[fullname]
for func in _post_import_hooks[fullname]:
func(module)
self._finder._skip.remove(fullname)
return module
所以,总结一下:我应该坚持问题中提到的书中的食谱。