Python中,在查找一个未定义的全局变量时,是否可以动态生成一个值?
In Python, when looking up an undefined global variable, is it possible to dynamically generate a value?
在Lua中,全局变量存储在名为_G
的table中。您可以将元table添加到_G
,这样当您查找未定义的全局值时,将调用用户定义的函数来提供值。
在下面的示例中,查找任何未定义的变量returns未定义变量的名称。
setmetatable ( _G, { __index = function ( t, k ) return k end } )
print ( foo ) -- prints the string "foo"
foo = 5
print ( foo ) -- prints 5
print ( bar ) -- prints the string "bar"
是否可以在Python3中实现同样的效果?
你的意思是像
print(bar) if 'bar' in globals() else print('bar')
2018 年 12 月 12 日的更新答案:
这是另一种(更好的?)方法。基本上,我们手动 read()
、compile()
,然后 exec()
整个源文件。当我们调用 exec()
时,我们可以传入一个替代的全局字典。整个文件被读取、编译和执行了两次,但我们确实达到了预期的结果。
def baz ():
global foo
print ( foo ) # should print the string 'foo'
foo = 5
print ( foo ) # should print 5
print ( bar ) # should print the string 'bar'
class CustomGlobals ( dict ):
def __getitem__ ( self, k ):
if k in self: return self .get ( k )
if hasattr ( self .get ( '__builtins__' ), k ):
# we raise KeyError to avoid clobbering builtins
raise KeyError
return k
def main ():
with open ( __file__, 'rb' ) as f:
source = f .read() # re-read __file__
code = compile ( source, __file__, 'exec' ) # re-compile __file__
g = CustomGlobals ( globals() )
g [ '__name__' ] = None # prevent infinite recursion
exec ( code, g ) # re-exec __file__ against g
g [ 'baz' ] () # call the re-complied baz function
if __name__ == '__main__': main()
上述方法更优越(IMO),因为我们不需要在字符串中嵌套代码,而且错误消息将包含正确的行号。
2018 年 11 月 29 日的原始答案:
如果有问题的 Python 代码来自字符串(而不是标准 .py
文件),那么您可以 exec()
该字符串并提供自定义全局字典,如下所示:
code = '''
print ( foo ) # should print the string 'foo'
foo = 5
print ( foo ) # should print 5
print ( bar ) # should print the string 'bar'
'''
class CustomDict ( dict ):
def __init__ ( self, other ): super() .__init__ ( other )
def __getitem__ ( self, k ):
if k in self: return self .get ( k )
if hasattr ( self .get ( '__builtins__' ), k ):
# we raise KeyError to avoid clobbering builtins
raise KeyError
return k
exec ( code, None, CustomDict ( globals() ) )
根据需要,以上输出:
foo
5
bar
我还没有找到任何方法 "mutate" 一个模块来为该模块中的代码实现相同的结果。如果有人知道改变模块的方法,我会很高兴看到它。
(也许模块可以将自己的源代码读入字符串,然后在自定义全局 dict
的上下文中编译该字符串,然后将结果注入 sys.modules
?在其他换言之,该模块将用自身的克隆替换自身,但具有不同的全局 dict
。嗯。)
旁白:有一些方法可以在模块上模拟 __getitem__()
和 __getattr__()
。而从 Python 3.7 开始,您可以简单直接地在模块中定义一个 __getattr__()
函数。然而,据我所知,这些技术中的 none 可以挂钩到全局变量的查找中。资料来源:
- Python 3 documentation for
exec()
- Python: subscript a module
- __getattr__ on a module
- PEP 562 -- Module __getattr__ and __dir__
在Lua中,全局变量存储在名为_G
的table中。您可以将元table添加到_G
,这样当您查找未定义的全局值时,将调用用户定义的函数来提供值。
在下面的示例中,查找任何未定义的变量returns未定义变量的名称。
setmetatable ( _G, { __index = function ( t, k ) return k end } )
print ( foo ) -- prints the string "foo"
foo = 5
print ( foo ) -- prints 5
print ( bar ) -- prints the string "bar"
是否可以在Python3中实现同样的效果?
你的意思是像
print(bar) if 'bar' in globals() else print('bar')
2018 年 12 月 12 日的更新答案:
这是另一种(更好的?)方法。基本上,我们手动 read()
、compile()
,然后 exec()
整个源文件。当我们调用 exec()
时,我们可以传入一个替代的全局字典。整个文件被读取、编译和执行了两次,但我们确实达到了预期的结果。
def baz ():
global foo
print ( foo ) # should print the string 'foo'
foo = 5
print ( foo ) # should print 5
print ( bar ) # should print the string 'bar'
class CustomGlobals ( dict ):
def __getitem__ ( self, k ):
if k in self: return self .get ( k )
if hasattr ( self .get ( '__builtins__' ), k ):
# we raise KeyError to avoid clobbering builtins
raise KeyError
return k
def main ():
with open ( __file__, 'rb' ) as f:
source = f .read() # re-read __file__
code = compile ( source, __file__, 'exec' ) # re-compile __file__
g = CustomGlobals ( globals() )
g [ '__name__' ] = None # prevent infinite recursion
exec ( code, g ) # re-exec __file__ against g
g [ 'baz' ] () # call the re-complied baz function
if __name__ == '__main__': main()
上述方法更优越(IMO),因为我们不需要在字符串中嵌套代码,而且错误消息将包含正确的行号。
2018 年 11 月 29 日的原始答案:
如果有问题的 Python 代码来自字符串(而不是标准 .py
文件),那么您可以 exec()
该字符串并提供自定义全局字典,如下所示:
code = '''
print ( foo ) # should print the string 'foo'
foo = 5
print ( foo ) # should print 5
print ( bar ) # should print the string 'bar'
'''
class CustomDict ( dict ):
def __init__ ( self, other ): super() .__init__ ( other )
def __getitem__ ( self, k ):
if k in self: return self .get ( k )
if hasattr ( self .get ( '__builtins__' ), k ):
# we raise KeyError to avoid clobbering builtins
raise KeyError
return k
exec ( code, None, CustomDict ( globals() ) )
根据需要,以上输出:
foo
5
bar
我还没有找到任何方法 "mutate" 一个模块来为该模块中的代码实现相同的结果。如果有人知道改变模块的方法,我会很高兴看到它。
(也许模块可以将自己的源代码读入字符串,然后在自定义全局 dict
的上下文中编译该字符串,然后将结果注入 sys.modules
?在其他换言之,该模块将用自身的克隆替换自身,但具有不同的全局 dict
。嗯。)
旁白:有一些方法可以在模块上模拟 __getitem__()
和 __getattr__()
。而从 Python 3.7 开始,您可以简单直接地在模块中定义一个 __getattr__()
函数。然而,据我所知,这些技术中的 none 可以挂钩到全局变量的查找中。资料来源:
- Python 3 documentation for
exec()
- Python: subscript a module
- __getattr__ on a module
- PEP 562 -- Module __getattr__ and __dir__