1

I'd like to create a function that will modify an initialized global variable based on the argument passed to it, but I get a SyntaxError: name 'arg' is local and global. I have seen other methods to accomplish this, using globals() or creating a simple func inside myFunc to "trick" Python. Another approach would be to create if statements inside myFunc to explicitly assign the corresponding global variables, but that seems overly verbose.

Why does this occur, and what would be the most efficient/elegant/Pythonic way to accomplish this?

Given:

var1 = 1
var2 = 2
var3 = 3

def myFunc(arg):
    global arg
    arg = 10

myFunc(var1) # each of these should print to 10
myFunc(var2)
myFunc(var3)
Chrispy
  • 1,300
  • 3
  • 11
  • 25
  • Correct answer given below, but consider the style issue. There is definitely a more "pythonic" way to achieve this. See the comments in @Holy Mackerel answer – Joop Dec 30 '13 at 09:00
  • It's possible, but the more important question you need to ponder is whether isn't there a better way to accomplish whatever the reason is you want to do this. – martineau Dec 30 '13 at 10:02

3 Answers3

3

You can use globals() to access the variables and assign new values from within myFunc()

var1 = 1
var2 = 2

def myFunc(varname):
    globals()[varname] = 10

print(var1, var2)

myFunc("var1")
myFunc("var2")

print(var1, var2)

Will output:

1, 2
10, 10
Holy Mackerel
  • 3,259
  • 1
  • 25
  • 41
  • I'm aware of the `globals()` method, but since you suggested it, are there any downsides to doing it this way? Would this be considered hacky? – Chrispy Dec 30 '13 at 06:39
  • I suppose a downside would be that the vars need to be passed in as literals. I don't consider it hacky. – Holy Mackerel Dec 30 '13 at 06:40
  • 3
    Why would you ever do this over `var1 = myFunc(var1)`? – Bi Rico Dec 30 '13 at 06:46
  • I agree. But looking at the question the OP wanted to perform the assignment from within the function, not from assigning the returned value. – Holy Mackerel Dec 30 '13 at 06:50
  • Answering the question correctly. Nice way of accessing it. DId not realize before that globals() returns a dict. I would question this on a matter of style though. The original question does not give background to the problem, but I am sure there is a more elegant/simple way of structuring the program so that this workaround is not necesary – Joop Dec 30 '13 at 08:57
  • To type out some quick context as to what I'm trying to accomplish: I have two `dict`s with a start and end state that I'd like to evaluate. For a start state that does not have a corresponding end state, this function's purpose is to find and append the `value` for a specific `key` to a global list using a `set()` evaluation of membership. However, the corresponding global list can change since this function will be called across different segments of data, so that global var is passed in as an argument into the function. Hope that helps clarify? – Chrispy Dec 30 '13 at 18:01
2

In python a variable is a name for an object. When you call a function, and pass it an argument you're passing the object associated with the variable, not the name. So for example when you call wash(mydog), you're saying "wash the object known as mydog". Keep in mind, that the same object could have more than one name, for example spot = mydog = best_dog_ever = new_dog(). The function doesn't know which name was used to pass it the object, and even if it did, what if the name used was not the one in the global scope, you'd have to have some way of saying this function only takes global variables as arguments.

I hope that helps explain why you're getting a syntax error, but you can still accomplish pretty much the same thing at least two ways. The first is to simply assign the return value to the variable you're trying to change, for example:

var1 = 1
var2 = 2

def addone(a):
    return a + 1

def main():
    global var1, var2
    var1 = addone(var1)
    var2 = addone(var2)

print var1, var2
main()
print var1, var2

The second is to use a more object oriented approach, something like this:

class GlobalValue(object):

    def __init__(self, value):
        self.value = value

var1 = GlobalValue(1)
var2 = GlobalValue(2)

def addone(a):
    a.value += 1

print var1.value, var2.value
addone(var1)
addone(var2)
print var1.value, var2.value

Or even better:

class GlobalValue(object):

    def __init__(self, value):
        self.value = value

    def addone(self):
        self.value += 1

var1 = GlobalValue(1)
var2 = GlobalValue(2)

print var1.value, var2.value
var1.addone()
var2.addone()
print var1.value, var2.value
Bi Rico
  • 25,283
  • 3
  • 52
  • 75
  • 1
    I've accepted the answer by @holymackerel because it was the immediate solve to my problem without refactoring. However, Bi Rico, I really appreciate the more detailed answer and also addressing my question on why this is occurring for my own edification. If I could upvote your answer, I'd do that too, but I need more rep ;) I'm also going to give the class oriented approach more thought as well to see if/how best to work that in. – Chrispy Dec 30 '13 at 18:08
0

Why does this occur

Because the global variable that you want to use has the same name as the parameter, arg. In Python, parameters are local variables, and a variable can only be local or global, not both.

It appears as though you expected to be able to use the contents of var to, somehow, specify which existing global variable to modify. It does not work like that. First off, variables don't contain other variables; they contain values. The name of a variable isn't a value. Again, parameters are local variables - and calling a function assigns to those variables. It assigns a value. (Keep in mind that you could just as easily call the function without a variable for the argument: myFunc(3). Should this cause 3 to become equal to 10 somehow?)

Second, even if you passed, for example, a string, you would have to do more work in order to access the corresponding global variable. It can be done, but please do not.

and what would be the most efficient/elegant/Pythonic way to accomplish this?

The Pythonic way is don't. If you want your function to communicate information out when it is called, return a value:

def myFunc():
    return 10

var1 = myFunc()
var2 = myFunc()
var3 = myFunc()

The simplest way to fix the error is to just rename the global variable. However, this does not fix the apparent intent of the code:

var1 = 1
var2 = 2
var3 = 3

def myFunc(arg):
    global g
    g = 10

# var1, var2 and var3 will NOT CHANGE
# Instead, the new global variable g is created, and assigned a value of 10,
# three times in a row.
myFunc(var1)
myFunc(var2)
myFunc(var3)
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153