Search code examples
pythonparsingargumentsparameter-passingintrospection

Python: Modify variable bindings in calling scope


Is it possible to replace the function foo below with real code such that

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

prints

1
2

?


To avoid an XY problem, what I really want is to have foo be part of an external module that summarizes common parsing code. At the moment, I solve the parsing as follows: I have a module parse_args that contains a bunch of functions such as

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  

and I do the following in bar:

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

What I would like to be able to do instead is:

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

where the pseudocode of parse_args is

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

I know this is only marginally less wordy, and I understand that there are probably reasons to favor my current approach over what I aim for, but as someone still learning Python, I am really mainly interested in the feasibility here.


Solution

  • No, it's not possible. You can't alter the local namespace of a function from further down the stack, because the CPython implementation has highly optimised the local namespace in such a way that makes any manipulation impossible.

    You are going about your problem the wrong way. Rather than rely on the standard local namespace, create your own in the form of a dictionary. You can then just pass that dictionary around:

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