在 python 解释器中挂钩全局名称查找
Hook the global name lookup in a python interpreter
事情是这样的,我有一个代理保存对远程模块的引用,我将其中一些代理放到 sys.modules
这样我就可以像使用本地模块一样使用它。但是其他一些对象放在远程环境的 __builtin__
模块中(例如为了方便调试或引用而设置的魔术变量)。我不想像 conn.__builtin__.var
那样引用这些变量,我必须替换本地 __builtin__
(这似乎不适用于替换 sys.modules['__builtin__']
或挂钩全局名称查找规则。
如何?对于模块,您只需重载 getattr
即可。但是在像 IPython
这样的交互式解释器中,谁是主要模块或者如何做到这一点? 更新: 正如@Nizam Mohamed 所指出的,是的,我可以获得 __main__
模块,但我仍然无法修改它的名称查找角色。
我想把本地环境完全变成远程环境(用于调试控制台)
更新
现在我只是迭代所有 __builtin__.__dict__
,如果有一个名称不在本地 __builtin__
。我将名称添加到本地的 __builtin__
。但与名称查找规则相比,它不是那么动态,如果我在本地找不到名称 __builtin__
试试远程的。
here是类似的讨论。
和this问题通过用sys.modules
中的对象替换模块来模拟模块。但这不适用于 __builtin__
名称查找,我还尝试将 __builtin__.__getattribute__
替换为自定义查找,该自定义查找将首先使用原始查找,然后在失败时使用自定义查找。但是 __builtin__
的全局名称查找从未调用到 __builtin__.__getattribute__
甚至 __builtin__.__getattribute__('name')
returns 所需的值,__builtin__.name
或 name
从来没有 returns 一.
有一种方法可以获取模块将使用的所有名称的列表。虽然没有修改查找机制,但相信可以解决你的问题。
这里有一些代码(希望足够理解)你可以放在一个模块中,我们称之为 magic
:
import sys
def magic():
# Get the caller frame
frame = sys._getframe().f_back
# Unwind all internal Python import-related stuff
while frame.f_code.co_filename.startswith('<'):
frame = frame.f_back
importer = frame
# Iterate through names the module has/will use
for name in importer.f_code.co_names:
# If the module has not yet defined/imported this name
if name not in importer.f_globals and \
name not in importer.f_locals and \
name not in __builtins__:
# Replace the name in the importer's namespace
# You'll have to replace the right-hand side by your specific code
importer.f_globals[name] = 'hello world'
然后您可以导入它来施展魔法:
import magic
magic.magic()
print(hi)
然而,有两个缺点。首先,动态查找会失败:
import magic
magic.magic()
print(globals()['hi']) # KeyError: 'hi'
虽然这种特殊情况可以通过查看 importer.f_code.co_consts
中的字符串来解决,但它不适用于 print(globals()['h'+'i'])
等更高级的动态查找
第二个缺点是它在函数中不起作用:
import magic
magic.magic()
def f():
print(hi) # NameError: name 'hi' is not defined
f()
这是因为在这种情况下,名称 hi
位于 f.__code__.co_names
而不是模块的 co_names
。
一个可能的解决方案是修改 magic
以尝试找到模块的所有函数并更改它们的代码,但它不适用于函数内部定义的函数等。
另一个解决方案是在函数中调用magic
:
import magic
def f():
magic.magic()
print(hi)
f()
使用 IPython shell
的 AST 转换
正如@asmeurer 所说,您可以编写一个简单的 AST 转换器来“挂钩”变量名查找。基础 class ast.NodeTransformer
提供了一个可以操作的 visit_Name 方法。你只需要重载这个方法来重新定义那些存在于远程模块但不存在于本地的变量。
以下模块可以用作 IPython extension:
testAST.py
import ast
modName = "undefined"
modAttr = []
user_ns = {}
class MyTransformer(ast.NodeTransformer):
def visit_Name(self, node):
if node.id in modAttr and not node.id in user_ns:
return self.getName(node)
return node
def getName(self, NameNode):
return ast.Attribute(value=ast.Name(id=modName, ctx=ast.Load()),
attr = NameNode.id,
ctx = NameNode.ctx)
def magic_import(self, line):
global modName, modAttr, user_ns
modName = str(line)
if not self.shell.run_code( compile('import {0}'.format(line), '<string>', 'exec') ):
user_ns = self.shell.user_ns
modAttr = user_ns[line.strip()].__dict__
self.shell.ast_transformers.append(MyTransformer())
print modName, 'imported'
def load_ipython_extension(ip):
ip.define_magic('magic_import', magic_import)
dummyModule.py
robot=" World"
用法:
In [1]: %load_ext testAST
In [2]: %magic_import dummyModule
In [3]: print "Hello" , robot
Hello World
In [4]: dummyModule.robot_II = "Human"
In [5]: print "Hi", robot_II
Hi Human
这种方法的好处是对远程模块的任何修改都会立即生效,因为查找是在语言级别完成的,没有对象被复制和缓存。
此方法的一个缺点是无法处理动态查找。如果这对你很重要,也许 python_line_transforms
挂钩更合适。
事情是这样的,我有一个代理保存对远程模块的引用,我将其中一些代理放到 sys.modules
这样我就可以像使用本地模块一样使用它。但是其他一些对象放在远程环境的 __builtin__
模块中(例如为了方便调试或引用而设置的魔术变量)。我不想像 conn.__builtin__.var
那样引用这些变量,我必须替换本地 __builtin__
(这似乎不适用于替换 sys.modules['__builtin__']
或挂钩全局名称查找规则。
如何?对于模块,您只需重载 getattr
即可。但是在像 IPython
这样的交互式解释器中,谁是主要模块或者如何做到这一点? 更新: 正如@Nizam Mohamed 所指出的,是的,我可以获得 __main__
模块,但我仍然无法修改它的名称查找角色。
我想把本地环境完全变成远程环境(用于调试控制台)
更新
现在我只是迭代所有 __builtin__.__dict__
,如果有一个名称不在本地 __builtin__
。我将名称添加到本地的 __builtin__
。但与名称查找规则相比,它不是那么动态,如果我在本地找不到名称 __builtin__
试试远程的。
here是类似的讨论。
和this问题通过用sys.modules
中的对象替换模块来模拟模块。但这不适用于 __builtin__
名称查找,我还尝试将 __builtin__.__getattribute__
替换为自定义查找,该自定义查找将首先使用原始查找,然后在失败时使用自定义查找。但是 __builtin__
的全局名称查找从未调用到 __builtin__.__getattribute__
甚至 __builtin__.__getattribute__('name')
returns 所需的值,__builtin__.name
或 name
从来没有 returns 一.
有一种方法可以获取模块将使用的所有名称的列表。虽然没有修改查找机制,但相信可以解决你的问题。
这里有一些代码(希望足够理解)你可以放在一个模块中,我们称之为 magic
:
import sys
def magic():
# Get the caller frame
frame = sys._getframe().f_back
# Unwind all internal Python import-related stuff
while frame.f_code.co_filename.startswith('<'):
frame = frame.f_back
importer = frame
# Iterate through names the module has/will use
for name in importer.f_code.co_names:
# If the module has not yet defined/imported this name
if name not in importer.f_globals and \
name not in importer.f_locals and \
name not in __builtins__:
# Replace the name in the importer's namespace
# You'll have to replace the right-hand side by your specific code
importer.f_globals[name] = 'hello world'
然后您可以导入它来施展魔法:
import magic
magic.magic()
print(hi)
然而,有两个缺点。首先,动态查找会失败:
import magic
magic.magic()
print(globals()['hi']) # KeyError: 'hi'
虽然这种特殊情况可以通过查看 importer.f_code.co_consts
中的字符串来解决,但它不适用于 print(globals()['h'+'i'])
第二个缺点是它在函数中不起作用:
import magic
magic.magic()
def f():
print(hi) # NameError: name 'hi' is not defined
f()
这是因为在这种情况下,名称 hi
位于 f.__code__.co_names
而不是模块的 co_names
。
一个可能的解决方案是修改 magic
以尝试找到模块的所有函数并更改它们的代码,但它不适用于函数内部定义的函数等。
另一个解决方案是在函数中调用magic
:
import magic
def f():
magic.magic()
print(hi)
f()
使用 IPython shell
的 AST 转换正如@asmeurer 所说,您可以编写一个简单的 AST 转换器来“挂钩”变量名查找。基础 class ast.NodeTransformer
提供了一个可以操作的 visit_Name 方法。你只需要重载这个方法来重新定义那些存在于远程模块但不存在于本地的变量。
以下模块可以用作 IPython extension:
testAST.py
import ast
modName = "undefined"
modAttr = []
user_ns = {}
class MyTransformer(ast.NodeTransformer):
def visit_Name(self, node):
if node.id in modAttr and not node.id in user_ns:
return self.getName(node)
return node
def getName(self, NameNode):
return ast.Attribute(value=ast.Name(id=modName, ctx=ast.Load()),
attr = NameNode.id,
ctx = NameNode.ctx)
def magic_import(self, line):
global modName, modAttr, user_ns
modName = str(line)
if not self.shell.run_code( compile('import {0}'.format(line), '<string>', 'exec') ):
user_ns = self.shell.user_ns
modAttr = user_ns[line.strip()].__dict__
self.shell.ast_transformers.append(MyTransformer())
print modName, 'imported'
def load_ipython_extension(ip):
ip.define_magic('magic_import', magic_import)
dummyModule.py
robot=" World"
用法:
In [1]: %load_ext testAST
In [2]: %magic_import dummyModule
In [3]: print "Hello" , robot
Hello World
In [4]: dummyModule.robot_II = "Human"
In [5]: print "Hi", robot_II
Hi Human
这种方法的好处是对远程模块的任何修改都会立即生效,因为查找是在语言级别完成的,没有对象被复制和缓存。
此方法的一个缺点是无法处理动态查找。如果这对你很重要,也许 python_line_transforms
挂钩更合适。