John
John

Reputation: 497

Python scope of variables in and out of a function

Consider the following code:

def square(x):
    return x**2.

def cube(x):
    return x**3.

def some_func():
    foo['a'] = 1.
    foo['b'] = 3.

    if foo['b'] > foo['a']:
        bar = square
    else:
        bar = cube

foo = dict()

bar = None

some_func()

print(foo['a'])

print(bar(2))

Two different variables are initialised in the beginning, foo, and bar. Then I run a function which doesn't take arguments or return anything, but amends these two variables.

The result of these two is the following:

1.0
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-7e5e5ac79215> in <module>
     22 print(foo['a'])
     23 
---> 24 print(bar(2))

TypeError: 'NoneType' object is not callable

So, the dict is happily amended by some_func(), but I cannot assign a function name. Why? How can I go around this?

Edit: I have tried giving bar other values than None, similar behaviour occurs.

Upvotes: 0

Views: 405

Answers (4)

Jim Stewart
Jim Stewart

Reputation: 17323

In order to assign to the global bar from some_func(), you need to declare it global, like so:

def some_func():
    foo['a'] = 1.
    foo['b'] = 3.

    global bar

    if foo['b'] > foo['a']:
        bar = square
    else:
        bar = cube

See the documentation for global, specifically this section:

The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals. While using global names is automatic if they are not defined in the local scope, assigning to global names would be impossible without global.

Your program clearly demonstrates the difference specified here: your access to foo is using the global name, while your access to bar is assigning to the global name.

Upvotes: 1

Jon
Jon

Reputation: 1850

Inside of some_func() you are adding key-value pairs to the dictionary object referenced by foo. You are editing the object directly. bar is referencing a None object. When you attempt to re-assign the value of the label bar inside of some_func() it does so, but only for the scope of the function it is in. When it leaves the function whatever the bar variable had reference to, None in this case, is what it still references.

The difference is that you are manipulating the object foo points to, but completely changing which object that bar points to. If you could do anything with a None object you would be able to manipulate it inside of some_func() too.

Some here have offered the global option, but unless you are really sure, that is probably not what you want. To truly assign a new value to bar you should return it as a result of some_func(). Also, you may consider passing in foo as a parameter into some_func() so that it is clear what variable you are referencing.

def square(x):
    return x**2.

def cube(x):
    return x**3.

def some_func(foo):
    foo['a'] = 1.
    foo['b'] = 3.

    if foo['b'] > foo['a']:
        bar = square
    else:
        bar = cube
    return bar

foo = dict()
bar = some_func(foo)

Upvotes: 2

Timo Frionnet
Timo Frionnet

Reputation: 484

This has to do with the scope in python. You can read more about it in this article: https://www.datacamp.com/community/tutorials/scope-of-variables-python.

The given solution for your code would be to add: global bar inside the function before the if statements to point it to the global variable that you are trying to set.

Upvotes: 1

dstromberg
dstromberg

Reputation: 7167

Try with a global statement for the thing you need to replace (and not just modify):

#!/usr/bin/python3

def square(x):
    return x**2.

def cube(x):
    return x**3.

def some_func():
    global bar

    foo['a'] = 1.
    foo['b'] = 3.

    if foo['b'] > foo['a']:
        bar = square
    else:
        bar = cube

foo = dict()

bar = None

some_func()

print(foo['a'])

print(bar(2))

HTH

Upvotes: 1

Related Questions