我正在为live coding in Python编写一个工具,如果用户导入它,我想要monkeypatch matplotlib。虽然导入速度有点慢,所以我不想导入它并在用户不导入它时进行monkeypatch。
我通读了导入钩子documentation,并且我在Python 3中使用了它。
# scratch.py
from importlib import import_module
import sys
class PatchedModuleFinder(object):
def find_module(self, fullname, path=None):
if fullname not in ('matplotlib', 'matplotlib.pyplot'):
return None
is_after = False
for finder in sys.meta_path:
if not is_after:
is_after = finder is self
continue
loader = finder.find_module(fullname, path)
if loader is not None:
return PatchedMatplotlibLoader(fullname, loader)
class PatchedMatplotlibLoader(object):
def __init__(self, fullname, main_loader):
self.fullname = fullname
self.main_loader = main_loader
def load_module(self, fullname):
if self.main_loader is not None:
module = self.main_loader.load_module(fullname)
else:
module = import_module(fullname)
PatchedModuleFinder.is_desperate = False
if fullname == 'matplotlib':
module.use('Agg')
elif fullname == 'matplotlib.pyplot':
module.show = self.mock_show
return module
def mock_show(self, *args, **kwargs):
print('mock_show:', args, kwargs)
sys.meta_path.insert(0, PatchedModuleFinder())
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
plt.plot([1, 2, 5])
plt.show()
这适用于Python 3,但不适用于Python 2.7:
$ .tox/py36/bin/python scratch.py
mock_show: () {}
$ .tox/py27/bin/python scratch.py
$
我的工具在Python 2.7和Python 3.x上都运行,那么我怎样才能在两个版本中懒洋洋地对一个模块进行monkeypatch?
答案 0 :(得分:1)
这很难看,但确实有效。希望有人能提出更好的选择。
# scratch.py
from importlib import import_module
import sys
class PatchedModuleFinder(object):
is_desperate = False
def find_module(self, fullname, path=None):
if fullname not in ('matplotlib', 'matplotlib.pyplot'):
return None
is_after = False
for finder in sys.meta_path:
if not is_after:
is_after = finder is self
continue
loader = finder.find_module(fullname, path)
if loader is not None:
return PatchedMatplotlibLoader(fullname, loader)
if sys.version_info < (3, 0) and not PatchedModuleFinder.is_desperate:
# Didn't find anyone to load the module, get desperate.
PatchedModuleFinder.is_desperate = True
return PatchedMatplotlibLoader(fullname, None)
class PatchedMatplotlibLoader(object):
def __init__(self, fullname, main_loader):
self.fullname = fullname
self.main_loader = main_loader
self.plt = None
def load_module(self, fullname):
if self.main_loader is not None:
module = self.main_loader.load_module(fullname)
else:
module = import_module(fullname)
PatchedModuleFinder.is_desperate = False
if fullname == 'matplotlib':
module.use('Agg')
elif fullname == 'matplotlib.pyplot':
self.plt = module
module.show = self.mock_show
return module
def mock_show(self, *args, **kwargs):
print('mock_show:', args, kwargs)
sys.meta_path.insert(0, PatchedModuleFinder())
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
plt.plot([1, 2, 5])
plt.show('x', 23, foo='bar')
现在它适用于:
$ .tox/py36/bin/python scratch.py
mock_show: ('x', 23) {'foo': 'bar'}
$ .tox/py27/bin/python scratch.py
('mock_show:', ('x', 23), {'foo': 'bar'})