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 可以挂钩到全局变量的查找中。资料来源: