导入 AST 修改
Importing AST modification
我正在尝试在导入之前动态重写 pywin32 win32com.client
模块,以下似乎有效 - 但我对导入的 6 行代码(在最后一条评论之后)不满意。谁能推荐一种更简洁的方法来创建/导入生成的模块(包)?
import sys, ast, types
import os.path
import importlib.util
import win32com
# import ast
_win32com_client_spec = importlib.util.find_spec("win32com.client")
_win32com_client_ast = ast.parse(open(_win32com_client_spec.origin).read())
(_win32com_client_dispatch_ast,) = (x for x in _win32com_client_ast.body if isinstance(x, ast.FunctionDef) and x.name == 'Dispatch')
# rename Dispatch
_win32com_client_dispatch_ast.name = '_OldDispatch'
# create new Dispatch
_my_dispatch_mod_ast = ast.parse("""
def Dispatch(*args, **kwds):
base_inst = _OldDispatch(*args, **kwds)
spec_inst = _OldDispatch(base_inst)
return spec_inst
""")
(_my_dispatch_ast,) = _my_dispatch_mod_ast.body
# insert new Dispatch in module
_win32com_client_ast.body.insert(_win32com_client_ast.body.index(_win32com_client_dispatch_ast)+1, _my_dispatch_ast)
# import the package
_my_win32com_client_co = compile(_win32com_client_ast, '<patched win32com.client>', 'exec')
_my_win32com_client_mod = types.ModuleType('win32com.client')
_my_win32com_client_mod.__path__ = (os.path.dirname(_win32com_client_spec.origin),)
sys.modules[_my_win32com_client_mod.__name__] = _my_win32com_client_mod
win32com.client = _my_win32com_client_mod
exec(_my_win32com_client_co, _my_win32com_client_mod.__dict__)
进一步阅读文档显示使用 ModuleSpec
对象优于使用 types.ModuleType
。
参考:https://docs.python.org/3/library/importlib.html#importlib.util.module_from_spec
生成的代码隐式处理包的 __path__
,并允许在整个过程中重用规范对象。请注意,如果使用 spec origin 来显示补丁,file 属性应如下所示明确设置。
模块应该在被执行之前被导入,这似乎对于子模块导入工作很重要,就像 gencache
模块中发现的那样。 import win32com.client.CLSISToClass
:
import sys, ast
import importlib.util
import win32com
if 'win32com.client' not in sys.modules:
# import ast
_win32com_client_spec = importlib.util.find_spec("win32com.client")
_win32com_client_path = _win32com_client_spec.origin
_win32com_client_spec.origin = '<patched win32com.client>'
_win32com_client_ast = ast.parse(open(_win32com_client_path).read())
# rename Dispatch
(_win32com_client_dispatch_ast,) = (x for x in _win32com_client_ast.body if isinstance(x, ast.FunctionDef) and x.name == 'Dispatch')
_win32com_client_dispatch_ast.name = '_OldDispatch'
# create new Dispatch (disregard line numbers)
_my_dispatch_mod_ast = ast.parse("""
def Dispatch(*args, **kwds):
base_inst = _OldDispatch(*args, **kwds)
spec_inst = _OldDispatch(base_inst)
return spec_inst
""")
(_my_dispatch_ast,) = _my_dispatch_mod_ast.body
# insert new Dispatch in module
_win32com_client_ast.body.insert(_win32com_client_ast.body.index(_win32com_client_dispatch_ast)+1, _my_dispatch_ast)
# compile (original path for debugging), create, import and exec the module
_my_win32com_client_co = compile(_win32com_client_ast, _win32com_client_path, 'exec')
_my_win32com_client_mod = importlib.util.module_from_spec(_win32com_client_spec)
_my_win32com_client_mod.__file__ = _win32com_client_path
win32com.client = sys.modules['win32com.client'] = _my_win32com_client_mod
try:
exec(_my_win32com_client_co, _my_win32com_client_mod.__dict__)
except:
del win32com.client, sys.modules['win32com.client']
raise
我正在尝试在导入之前动态重写 pywin32 win32com.client
模块,以下似乎有效 - 但我对导入的 6 行代码(在最后一条评论之后)不满意。谁能推荐一种更简洁的方法来创建/导入生成的模块(包)?
import sys, ast, types
import os.path
import importlib.util
import win32com
# import ast
_win32com_client_spec = importlib.util.find_spec("win32com.client")
_win32com_client_ast = ast.parse(open(_win32com_client_spec.origin).read())
(_win32com_client_dispatch_ast,) = (x for x in _win32com_client_ast.body if isinstance(x, ast.FunctionDef) and x.name == 'Dispatch')
# rename Dispatch
_win32com_client_dispatch_ast.name = '_OldDispatch'
# create new Dispatch
_my_dispatch_mod_ast = ast.parse("""
def Dispatch(*args, **kwds):
base_inst = _OldDispatch(*args, **kwds)
spec_inst = _OldDispatch(base_inst)
return spec_inst
""")
(_my_dispatch_ast,) = _my_dispatch_mod_ast.body
# insert new Dispatch in module
_win32com_client_ast.body.insert(_win32com_client_ast.body.index(_win32com_client_dispatch_ast)+1, _my_dispatch_ast)
# import the package
_my_win32com_client_co = compile(_win32com_client_ast, '<patched win32com.client>', 'exec')
_my_win32com_client_mod = types.ModuleType('win32com.client')
_my_win32com_client_mod.__path__ = (os.path.dirname(_win32com_client_spec.origin),)
sys.modules[_my_win32com_client_mod.__name__] = _my_win32com_client_mod
win32com.client = _my_win32com_client_mod
exec(_my_win32com_client_co, _my_win32com_client_mod.__dict__)
进一步阅读文档显示使用 ModuleSpec
对象优于使用 types.ModuleType
。
参考:https://docs.python.org/3/library/importlib.html#importlib.util.module_from_spec
生成的代码隐式处理包的 __path__
,并允许在整个过程中重用规范对象。请注意,如果使用 spec origin 来显示补丁,file 属性应如下所示明确设置。
模块应该在被执行之前被导入,这似乎对于子模块导入工作很重要,就像 gencache
模块中发现的那样。 import win32com.client.CLSISToClass
:
import sys, ast
import importlib.util
import win32com
if 'win32com.client' not in sys.modules:
# import ast
_win32com_client_spec = importlib.util.find_spec("win32com.client")
_win32com_client_path = _win32com_client_spec.origin
_win32com_client_spec.origin = '<patched win32com.client>'
_win32com_client_ast = ast.parse(open(_win32com_client_path).read())
# rename Dispatch
(_win32com_client_dispatch_ast,) = (x for x in _win32com_client_ast.body if isinstance(x, ast.FunctionDef) and x.name == 'Dispatch')
_win32com_client_dispatch_ast.name = '_OldDispatch'
# create new Dispatch (disregard line numbers)
_my_dispatch_mod_ast = ast.parse("""
def Dispatch(*args, **kwds):
base_inst = _OldDispatch(*args, **kwds)
spec_inst = _OldDispatch(base_inst)
return spec_inst
""")
(_my_dispatch_ast,) = _my_dispatch_mod_ast.body
# insert new Dispatch in module
_win32com_client_ast.body.insert(_win32com_client_ast.body.index(_win32com_client_dispatch_ast)+1, _my_dispatch_ast)
# compile (original path for debugging), create, import and exec the module
_my_win32com_client_co = compile(_win32com_client_ast, _win32com_client_path, 'exec')
_my_win32com_client_mod = importlib.util.module_from_spec(_win32com_client_spec)
_my_win32com_client_mod.__file__ = _win32com_client_path
win32com.client = sys.modules['win32com.client'] = _my_win32com_client_mod
try:
exec(_my_win32com_client_co, _my_win32com_client_mod.__dict__)
except:
del win32com.client, sys.modules['win32com.client']
raise