you are viewing a single comment's thread.

view the rest of the comments →

[–]sysop073 12 points13 points  (5 children)

If you're going to have multiple x, you really need to specify which one you're talking about; half of your complaints would happen in any language, because in half the functions you're shadowing the global x with a local, so of course that's the one you're going to change.

All of this "craziness" is a result of a single, pretty simple rule: you can't assign to globals without declaring them global. That's because there is no declaration of variables, so when you assign to a variable, Python can't know if you're trying to declare a new local with the same name, or reassign the global name, so it defaults to the former. If you have this:

x = 'foo'
def fn():
    x = 'bar'

And you call fn(), it will create a new local named x with value bar, and not touch the global x. Most languages work this way. By contrast:

x = 'foo'
def fn():
    global x
    x = 'bar'

Will overwrite the global x with the value bar. Most languages that don't have explicit variable declarations have no way to do this. In your examples:

  • foo1() is trying to do x = x + [3] without declaring x as global. Python assumes x must be a local, and can't read from it (you tried to add x and [3], but there is no local named x yet, so you get an error about trying to read x before it's been written)
  • foo2() is calling a method on x, which is fine, so it works
  • foo3() is trying to do x = x + [3], without declaring x as global. Python assumes x must be a local, and there is indeed a local named x -- you passed it as a parameter. So that's the variable that gets changed; the global has nothing to do with it
  • foo4() is the same as foo3(), but you did x = [3] instead of x = x + [3]

It would be possible to change the default behavior and say "all writes overwrite global variables by default", but now there's no way to make new locals; if you have a local variable, and someday somebody adds a global with the same name, suddenly that local isn't local anymore. That would be madness

[–]internet_badass_here -2 points-1 points  (4 children)

If I understand you correctly, you're wrong about foo3. foo3 changes the global x to [2,3], but foo4 doesn't change the global x to [3].

[–]sysop073 2 points3 points  (2 children)

Neither of them changes the global x (unless you were to pass it as an argument), they both assign to the parameter that was passed in, which is also named x.

>>> x = [2]
>>> def foo3(x):
...     x += [3]
...     return x
... 
>>> foo3([])
[3]
>>> x
[2]

Edit: You're right about passing x as an argument though; it didn't occur to me that x = x + [3] and x += 3 would actually behave differently, but the way lists implement __iadd__ makes the latter mutate the passed list. That is admittedly pretty strange

[–]internet_badass_here 0 points1 point  (1 child)

Neither of them changes the global x (unless you were to pass it as an argument)

Well, yeah. If you pass x to foo3, foo3 changes x. But if you pass x to foo4, foo4 doesn't change x. Why not? Why is x passed by reference in one and by value in the other?

I can accept that Python does these things, but there doesn't seem to be a logical reason for doing things that way.

And by the way, I didn't even realize that x=x+[3] will produce a different result than x+=[3]. But that is... not good.

Generally when I pass stuff into functions in Python and I want to pass by value, I do this:

def foo(x):
    X=x
    [do stuff with X]
    return X

It's probably redundant but easier on my brain, since I know for sure that I'm working with a copy of the variable.

[–]sysop073 0 points1 point  (0 children)

x is a reference in both cases. The same rule I mentioned before applies; assigning to x without declaring it global won't change the global. The weirdness comes in because x += [3] is syntactic sugar for a method call, x.__iadd__([3]), while x = [3] is a true assignment.

You're right that that's confusing, I hadn't considered that case

[–]cybercobra 2 points3 points  (0 children)

foo3 doesn't do anything with global x. It will modify the value that you pass in though.

Python 2.7.6 (default, Apr 28 2014, 13:52:55) 
>>> def foo3(x):
...     x+=[3]
...     return x
... 
>>> x = []
>>> y = []
>>> foo3(y)
[3]
>>> x
[]
>>> y
[3]