1

I wrote a test program that looked like this:

#!/usr/bin/python

def incrementc():
    c = c + 1

def main():
    c = 5
    incrementc()

main()

print c

I'd think that since I called incrementc within the body of main, all variables from main would pass to incrementc. But when I run this program I get

Traceback (most recent call last):
  File "test.py", line 10, in <module>
    main()
  File "test.py", line 8, in main
    incrementc()
  File "test.py", line 4, in incrementc
    c = c + 1
UnboundLocalError: local variable 'c' referenced before assignment

Why isn't c passing through? And if I want a variable to be referenced by multiple functions, do I have to declare it globally? I read somewhere that global variables are bad.

Thanks!

i love stackoverflow
  • 1,555
  • 3
  • 12
  • 24
  • Global variables are absolutely not bad, when used properly, and it shouldn't be a great concern on a test program :) – Evpok Aug 12 '11 at 22:54
  • You're not the first person to [ask this question][1]. [1]: http://stackoverflow.com/questions/423379/using-global-variables-in-a-function-other-than-the-one-that-created-them – Keith Thompson Aug 12 '11 at 22:57

4 Answers4

6

You're thinking of dynamic scoping. The problem with dynamic scoping is that the behavior of incrementc would depend on previous function calls, which makes it very difficult to reason about the code. Instead most programming languages (also Python) use static scoping: c is visible only within main.

To accomplish what you want, you'd either use a global variable, or, better, pass c as a parameter. Now, because the primitives in Python are immutable, passing an integer can't be changed (it's effectively passed by value), so you'd have to pack it into a container, like a list. Like this:

def increment(l):
    l[0] = l[0] + 1

def main():
    c = [5]
    increment(c)
    print c[0]

main()

Or, even simpler:

def increment(l):
    return l + 1

def main():
    c = 5
    print increment(c)

main()

Generally, global variables are bad because they make it very easy to write code that's hard to understand. If you only have these two functions, you can go ahead and make c global because it's still obvious what the code does. If you have more code, it's better to pass the variables as a parameter instead; this way you can more easily see who depends on the global variable.

Antti
  • 11,944
  • 2
  • 24
  • 29
  • You forgot the most important way of reusing a variable in different scopes -- use a class / object and manipulate an attribute. – agf Aug 12 '11 at 23:11
  • Thanks :) That is interesting that lists and numbers pass differently. – i love stackoverflow Aug 12 '11 at 23:15
  • 1
    @badatmath They don't pass differently. It's just that you're never modifying `l` here, just `l[0]` so you're referring to the local variable. Since `l` and `c` point to the same object, modifying `l[0]` make `c` reflect the change. see my answer to http://stackoverflow.com/questions/7046971/python-when-to-use-copy-copy for more info (the question right before yours in the `python` tag) – agf Aug 12 '11 at 23:21
  • Didn't even know that dynamic scoping existed (never used perl or a lisp version/dialect that used it) - why would anyone ever want to use it though? Sounds like a horrible mess. – Voo Aug 12 '11 at 23:34
  • First versions of Lisp used dynamic scoping because it's very easy to implement. It was only noticed later what a headache it can be. – Antti Aug 12 '11 at 23:36
3

When a variable is assigned to in a scope, Python assumes it's local for the whole scope unless you tell it otherwise.

So, to get this to work as you think it will, you need to use two global statements:

#!/usr/bin/python
def incrementc():
    global c
    c = c + 1
def main():
    global c
    c = 5
    incrementc()
main()
print c

Otherwise, you're talking about a local variable named c in both situations.

The normal way to solve this, however, does not involve globals.

#!/usr/bin/python
def incrementc(c):
    c = c + 1
    return c
def main():
    c = 5
    c = incrementc(c)
    return c
c = main()
print c

Here, in each function and in the global scope, c refers to a different variable, which you are passing around as an argument and with return values. If you wanted only one c, use a class:

class Foo:
    def __init__(self, c):
        self.c = c
        self.incrementc()
    def incrementc(self):
        self.c = self.c + 1


foo = Foo(5)
print foo.c
agf
  • 171,228
  • 44
  • 289
  • 238
2

The variable c isn't passing through because you do not hand any reference to c to the function incrementc.

What you're looking at here are 3 scopes, the global scope and those within the functions main and incrementc. In main you've properly defined a variable c, but increment c has no knowledge of this - so attempting to increment it is going to fail. Even if those two functions succeeded, trying to print c would fail in the global scope because it has no knowledge of the c you've defined in main.

You have a few options. One way to do this:

def incrementc(c):
    c = c + 1
    return c

def main():
    c = 5
    c = incrementc(c)
    return c

c = main()
print c

Notice how c is being handed around. Of course, the name doesn't have to be preserved, you very well could write it like this:

def increment(z):
    z = z + 1
    return z

def main():
    bar = 5
    bar = increment(bar)
    return bar

foo = main()
print foo

Another option that many would probably dislike (for good reason) is to use globals. In that case:

def incrementc():
    global c # indicate intention to write to this global, not just read it
    c = c + 1 

def main():
    global c # declares c in global space
    c = 5
    incrementc()

main()
print c

Any function in which you hope to MODIFY the GLOBAL instance of c, you need to inform the function. So you state, 'global c'. You can READ from the global without doing so. This would ensure (to some extent) that you don't make a mistake and overwrite a value in the global space unintentionally with a similar name, should you decide to use one in the local space of a function.

Hopefully that's clear enough, but feel free to ask for clarification on any point (I'm also open to being corrected if I've mistakenly described any part of this).

Reno
  • 232
  • 1
  • 8
  • @badatmath You declare it in global space just by using it after a `global` statement. The `c = 0` is unnecessary, and contrary to the way things are done in Python. – agf Aug 12 '11 at 23:12
  • @agf Can I use the global statement within _any function_ and still have it be a global variable? – i love stackoverflow Aug 12 '11 at 23:17
  • Yep. Wherever you do `global c` you'll be using `c` as a global in that scope, regardless whether it's been used as a global in other scopes or not. – agf Aug 12 '11 at 23:19
  • @agf could you expand on how it's contrary to python style? i suppose it seemed good practice if only to make it clear to the user everything that exists within the global scope. i understand you don't preallocate variables in python, but doesn't the style guide suggest declaring globals at the top of your modules? – Reno Aug 12 '11 at 23:41
  • If you want it to be initialized to zero prior to the functions being run (like if the increment function could be run first), then it's necessary to do this _somewhere_ (but it shouldn't be a global), but in this case that's not needed. Therefore, your line of code has no effect on the code, acting only as a comment. It's OK for a line of code to both do something and be self-commenting, but if it does nothing, you should just use a comment instead, i.e. `# Note: 'c' is a global variable!`. It's another example of how Python was designed for global variables only to be used as constants. – agf Aug 12 '11 at 23:48
  • @agf I do understand that the line functionally accomplishes nothing - but it communicates intent. I also was under the impression the style guide suggests this practice. For example, a quick search found this blurb in a style guide [here](http://www.python.org/dev/peps/pep-0008/), though it may be dated (2001) - "Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants." – Reno Aug 12 '11 at 23:52
  • PEP8 is definitely the Python style guide. Except that you _aren't using `c` as a normal module-level global_. If you were, it would __need__ to be initialized, so the statement wouldn't have no effect. Really, there _is no right way_ to do it, as this is simply not what globals are for in Python. – agf Aug 12 '11 at 23:56
-1

Global variables are bad.

Just like friends and enemys. Keep your friends close but keep your enemys even closer.

The function main last a local variable c, assignment the value 5 You then call the function inc..C. The c from main is now out of scope so you are trying to use a value of c that is not in scope - hence the error.

Ed Heal
  • 59,252
  • 17
  • 87
  • 127