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

3

3

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.

Soupy answered 9/9, 2013 at 16:5 Comment(2)
Why don't you just pass the name of the variable to the function? This seems like much less typing than what you're trying to do now. It also would be more explicit, and less bug-prone.Potty
@sybren Well I wanted to be able to perform the same function an arbitrarily amount of times on the set. It felt silly that I could easily type it in the command console but I did not know an easy way to make a function do that so I just made the function type it. Guess there are 'better' ways. Ill try and learn them :)Soupy
Y
4

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.

Yester answered 9/9, 2013 at 16:15 Comment(4)
cannot is a stronger term than I would have used , but yeah +1Scabrous
@JoranBeasley Fair enough, but as OP's trouble demonstrates, it's not really passing a variable, so much as a name without a context.Yester
@Yester Very nice explanation, thanks! It still bugs me that there is no quicker way to do something that I can type in the console very quickly myself. Your solution is more elegant than mine (eventually I resorted to having a global var reserved for this function). To be able to use your solution I will have to study what classes are and how they work. But Im thinking this will not be the last time Ill need them. So thanks both for your solution and the explanation of why my method is not recommended.Soupy
@Soupy You're welcome. If you're using a global variable, that again is a sign that you should use a class. In short, classes are what you see above: the declaration class <classname>(<baseclass>): followed by a list of functions (methods), which take an instance of the class as their first argument (self). Object instance variables are accessed by self.<variable name>. You create instances by calling the class (with the parameters for __init__ except self). Instances have variables which are distinct from those on other instances (unless defined inside the class body).Yester
D
2

presenter's answer works in python 3

def get_variable_name(*variable):
    return [ k for k,v in globals().items() if v is variable[0]][0]

example:

>> test = 'sample string'
>> get_variable_name(test)
'test'
>>

The only issue is it is a little messy.

Decapitate answered 6/4, 2016 at 2:49 Comment(0)
M
1

The reason you get the error NameError: name 'var' is not defined when you wrap it all into a function is the following: locals().items() refers to the locally defined variables. as a matter of fact, the locally defined variables in your functions are only the variables defined inside the function and those passed as arguments to the function.

As the name says, locals().items() is local, and will consist of different variables depending on where it is called in your script.

On the contrary globals().items() consists of all the global variables, so try using it instead in your functions, it should do the trick.

For more info, look up global and local variables as well as the scope of variables in Python.

Mythos answered 14/1, 2016 at 15:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.