Python: 修改调用范围内的变量绑定

Python: Modify variable bindings in calling scope

是否可以用真正的代码替换下面的函数 foo 使得

def foo():
    #calling_scope()['a']=2
def bar(a):
    print(a)
    foo()
    print(a)
bar(1)

打印

1
2

?


为了避免 XY 问题,我真正想要的是让 foo 成为总结通用解析代码的外部模块的一部分。目前,我按如下方式解决解析:我有一个模块parse_args,其中包含一堆函数,例如

def bool(arg):
  if arg in [True,'True','true']:
     return True
  if arg in [False,'False','false']:
     return False
  raise ValueError('Did not understand boolean value')
def nonnegative(arg):
  if arg<0:
    raise ValueError('Argument cannot be negative')
  return arg  

我在 bar 中执行以下操作:

def bar(arg1,arg2)
    arg1=parse_args.bool(arg1)
    arg2=parse_args.nonnegative(arg2)

我希望能够做的是:

def bar(arg1,arg2)
    parse_args(arg1='bool',arg2='nonnegative')

其中parse_args的伪代码是

def parse_args(**kwargs)
    #cs=calling_scope()
    #for arg in kwargs:
    #    cs[arg]=globals()[kwargs[arg]](cs[arg])

我知道这只是略微不那么冗长,而且我明白可能有理由支持我目前的方法而不是我的目标,但作为一个仍在学习的人 Python,我真的主要对这里的可行性。

不,这不可能。您无法从堆栈的更下方更改函数的本地名称空间,因为 CPython 实现已经高度优化了本地名称空间,使得任何操作都不可能。

你解决问题的方式不对。与其依赖标准的本地名称空间,不如以字典的形式创建自己的名称空间。然后你可以传递那个字典:

def bar(arg1, arg2)
    namespace = {'arg1': arg1, 'arg2': arg2}
    parse_args(namespace, arg1='bool', arg2='nonnegative')

我找到了一种使用函数注释和装饰器以比我希望的更漂亮的方式进行我想要的解析的方法。 (实际上,这实际上修改了函数中我希望修改它们的变量绑定,从技术上讲这是可能的,因为通过使用装饰器,调用者变成了被调用者。)

我现在写

@validated()
def f(a:'nonnegative integer',b:'bool|function', c:'float in_range(0,1)', d:'{float}', e:'{integer:any}',f:'bool'):  
    print(a,b,c,d,e,f)

并得到

>>f(a=2,b=lambda x: x>2, c=0,d=[1.2, 2.3], e={1:'foo',2:'bar'},f='true')
2 <function <lambda> at 0x7f4779ae8400> 0.0 [1.2, 2.3] {1: 'foo', 2: 'bar'} True

请注意,整数参数 c=0 已转换为浮点数 0.0,字符串 'true' 已转换为布尔值 True。注释中的花括号表示任何可迭代对象,冒号表示字典。竖线表示备选方案。可以用空格连接不同的要求,如参数 ac 所做的那样。如果不满足任何规范,例如 a=-1,则会抛出一个有根据的错误:

swutil.validation.ValidationError: Invalid argument for parameter 'a': -1 was rejected by 'nonnegative integer' (-1 was rejected by 'nonnegative')

最后,装饰器采用可用于指定必须传递哪些参数的参数。例如,@validated('a^b')@validated('a|b') 指定(恰好)必须传递 ab 之一。另一个将填充 NotPassed 实例。

如果有人对代码感兴趣,请发表评论,我会分享。