0

is it possible to call a function using a variable as the name for a variable? Example:

def my_method(foo="bar"):
    print(foo)
var = "foo='baz'"
my_method(var)

>>> baz

Now, I can't figure out a way to do this kind of thing (substitute the value in a variable for a variable name).

Is this kind of thing possible?

I know there are things you can do that are analogous to this, for instance:

def my_method(foo, bar, baz):
    print(foo, bar, baz)
var = ['one','two','three']
my_method(*var)

>>> one two three

But I can't find a uniform, generalized solution to any metaprogramming I might need in python. Is there one? Perhaps the language just isn't capable of a generalized metaprogramming solution.

MetaStack
  • 3,266
  • 4
  • 30
  • 67
  • 1
    I would normalize the string into json, and then use the json module to read it in as python structures. That way you'd have all sorts of lists or dicts to unpack out into arguments – solstice333 Mar 14 '18 at 21:51

3 Answers3

1

You can provide exec with a dictionary inside which it will store variables and then unwrap it as your function's keyword arguments.

def my_method(foo="bar"):
    print(foo)

var_a = "foo='baz'"
kwargs = {}

# See safety note at the bottom of this answer.
exec(var_a, {'__builtins__': {}}, kwargs)

my_method(**kwargs )
# prints: 'baz'

You could even use a decorator to give that behaviour to functions.

def kwargs_as_string(f):

    def wrapper(string, **more_kwargs):
        kwargs = {}

        # See safety note at the bottom of this answer.
        exec(string, {'__builtins__': {}}, kwargs)
        return f(**kwargs, **more_kwargs)

    return wrapper

@kwargs_as_string
def my_method(foo="bar"):
    print(foo)

my_method("foo='baz'")
# prints: 'baz'

Safety note

To be safe, we provide exec with an empty global __builtins__, otherwise a reference to the dictionary of the built-in module is inserted under that key. This can lead to trouble.

var_a = '__import__("sys").stdout.write("You are in trouble")'
exec(var_a, {}, {})
# prints: You are in trouble

exec(var_a, {'__builtins__': {}}, {})
# raises a NameError: name '__import__' is not defined
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
  • amazing answer, thank you! is there anything analogous to this for other situations such as if statements, or any other general case, etc? I can't do something like `if **'a==1':` can I? that kind of thing is just impossible right? – MetaStack Mar 14 '18 at 22:49
  • 1
    Not really, this is possible thanks to kwargs unwraping which is specific to functions. What you seem to need might be a script template that you can fill in. – Olivier Melançon Mar 14 '18 at 22:53
1

Assuming you're allowed to have JSON formatted strings...

import json

args = json.loads(
   """
   {
      "kwargs": {
         "a": 2,
         "b": 1
      },
      "args": [3, 4] 
   }
   """)

def foo(a, b):
   print("a: {}".format(a))
   print("b: {}".format(b))

foo(**args["kwargs"])
foo(*args["args"])

# output:
# a: 2
# b: 1
# a: 3
# b: 4
solstice333
  • 3,399
  • 1
  • 31
  • 28
0

can you use getattr to call a function within your scope?

I found these three options, the last one to be the most useful in my case.

def foo():
    def bar(baz):
        print('dynamically called bar method using', baz)

    packages = {'bar': bar}
    getattr(packages['bar'], "__call__")('getattr with map')

    packages['bar']('just the map')

    locals()['bar']('just locals()')

foo()

python test_dynamic_calling.py

dynamically called bar method using getattr with map
dynamically called bar method using just the map
dynamically called bar method using just locals()
MetaStack
  • 3,266
  • 4
  • 30
  • 67