Search code examples
pythonstringfunctionvariableslocals

return variable name from outside of function, as string inside python function


So I have created a function that applies an action (in this case point wise multiplication of an array with a sinusoid, but that does not matter for my question) to an array.

Now I have created another function with which I want to create a string of python code to apply the first function multiple times later-on. The input of the second function can be either a string or an array, so that I can use the second function on its own output as well, if need be. My method of getting the variable name in a string works outside of the function.

Input :

var = np.array([[1,3],[2,4]]) # or sometimes var = 'a string'

if type(var)==str:
    var_name = var
else:
    var_name = [ k for k,v in locals().items() if v is var][0]

var_name

Output :

'var'

So here var is the variable (either array or string) supplied to the function, in this case an array. The if statement nicely returns me its name.

However when I use this inside my function, no matter what input I give it, it actually seems to look for var in locals(). Somehow it does not take var from the function input.

Definition :

def functionTWO(var, listoflistsofargs=None):
    if type(var)==str:
        var_name = var
    else:
        var_name = [ k for k,v in locals().items() if v is var][0]
    if listoflistsofargs==None:
        return var_name
    command = []
    for i in range(len(listoflistsofargs)):
        if i==0:
            command.append('functionONE(')
            command.append(var_name)
            command.append(',%.17f, %.17f)' % tuple(listoflistsofargs[i]))
        else:
            command.insert(0,'functionONE(')
            command.append(',%.17f, %.17f)' % tuple(listoflistsofargs[i]))
    ''.join(command)
    command[0] = var_name + ' + ' + command[0]
    return ''.join(command)

Input :

somearray = np.array([[1,2,3],[1,2,3],[1,2,3]])
args = [[1,3],[6,5]]
command = functionTWO(somearray, args)
command

Output :

NameError: name 'var' is not defined

Wanted output :

'functionONE(functionONE(somearray, 1, 3), 6, 5)'

Why is listoflistsofargs taken from the function input and var not? I specify var in the listcomprehension in the definition of functionTWO. Normally when I use list comprehensions with function inputs it works fine. Does anybody know why this isnt the case here? Thank you in advance!

EDIT : So I guess the answer is dont. The implementation of classes by Marcin looks much cleaner and about the same order of amount of code. Too bad I couldnt get this to work inside a function. For other donts (actually other ideas) about using variable names as strings there is this question, where I got the above list comprehension for variable names.


Solution

  • You cannot pass a variable as a string*, and you should not do so.

    If you want to pass a value between functions, the normal way is to pass it in as a parameter, and out as a return value.

    If that is inconvenient, the usual solution is an object: define a class which carries both the shared variable, and methods which act on the variable.

    If you need to create command objects, it is much better to do so in a structured way. For example, if you want to pass a function, and parameters, you can literally just pass the function object and the parameters in a tuple:

    def foo():
        return (functionONE,somearray,1,3)
    
    command = foo()
    command[0](*command[1:])
    

    If you want to embed such commands within commands, you'll likely want to wrap that up with a class, so you can recursively evaluate the parameters. In fact, here's a little evaluator:

    def evaluator(object):
        def __init__(self,func=None,params=None):
            self.func = func
            self.params = params
    
        def eval(self,alternativeparams=None):
            if alternativeparams is not None:
               params = alternativeparams
            else:
               params = self.params
    
            if params is not None:
               evaluatedparams = (item() if callable(item) else item for item in params)
            else: evaluatedparams = None
    
            if func is not None:
               return self.func(*(evaluatedparams or ()))
            else: return evaluatedparams
        def __call__(self, *params):
            return self.eval(params if params else None)
    

    Although there are hacks by which you can pass references to local variables out of a function, these are not a great idea, because you end up creating your own half-baked object system which is more difficult to understand.

    * This is because a variable has a name (which is a string) and a context, which maps names to strings. So, you need, at least to pass a tuple to truly pass a variable.