Passing data to and from functions

Below, we demonstrate way to pass data to and from functions in different ways, and with different effects.

In [1]:
def adjust_grade():
    global grade
    grade = grade - 5
    print("New grade inside the function:", grade)

if __name__ == "__main__":
    grade = 95
    adjust_grade()
    print("New grade outside the function:", grade)
New grade inside the function: 90
New grade outside the function: 90

Since the grade inside the function is global, the global variable grade gets modified when the function runs.

In [2]:
def adjust_grade2(grade):
    grade = grade - 5
    print("New grade inside the function:", grade)

if __name__ == "__main__":
    grade = 95
    adjust_grade2(grade)
    print("New grade outside the function:", grade)
New grade inside the function: 90
New grade outside the function: 95

Here, grade is a parameter of the function adjust_grade2() (as well as a global variable.) When grade is modified inside the function, it's the local variable that gets modified. The global variable remains the same.

In [3]:
def adjust_grade3(g):
    g = g - 5
    print("New grade inside the function:", g)

if __name__ == "__main__":
    grade = 95
    adjust_grade3(grade)
    print("New grade outside the function:", grade)
New grade inside the function: 90
New grade outside the function: 95

This is the exact same thing as before: the parameter g just got renamed. Just like in math, f(x)=x and f(y)=y represent the same function.

In [4]:
def adjust_grade4(g):
    global grade
    grade = g - 5
    
if __name__ == "__main__":
    grade = 95
    adjust_grade4(grade)
    print("New grade outside the function:", grade)
New grade outside the function: 90

This works basically like adjust_grade(): the global variable grade gets modified.

In [5]:
def get_adjusted_grade(grade):
    return grade - 5

if __name__ == "__main__":
    grade = 95
    get_adjusted_grade(grade)
    print("New grade outside the function:", grade)
New grade outside the function: 95

This outputs nothing: that's because while get_adjusted_grade(grade) has the right value, the value doesn't get stored anywhere. Here' is how we can fix this:

In [6]:
def get_adjusted_grade(grade):
    return grade - 5

if __name__ == "__main__":
    grade = 95
    grade = get_adjusted_grade(grade)
    print("New grade outside the function:", grade)
New grade outside the function: 90

We assign get_adjusted_grade(grade) to grade, and now everything works.

Errors caused by ambiguity between global and local variabes

Now, here are two variants that produce errors:

In [7]:
def adjust_grade_error():
    grade = grade - 5
    print("New grade inside the func:", grade)

if __name__ == "__main__":
    grade = 95
    grade = adjust_grade_error() 
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-7-a94d5d96d061> in <module>()
      5 if __name__ == "__main__":
      6     grade = 95
----> 7     grade = adjust_grade_error()

<ipython-input-7-a94d5d96d061> in adjust_grade_error()
      1 def adjust_grade_error():
----> 2     grade = grade - 5
      3     print("New grade inside the func:", grade)
      4 
      5 if __name__ == "__main__":

UnboundLocalError: local variable 'grade' referenced before assignment

The problem here is that in adjust_grade_error(), we first evaluate grade - 5, in order to store it back in grade. That implies that the grade we mean must be global -- we don't have a local grade yet. On the other hand, the fact that we assign to grade without going global grade means grade must be local. That is the assumption that Python goes with, resulting in the error message above. The error message says that Python assumed grade was local, but there wasn't a local value of grade that it could use to compute grade - 5. It's the same problem as with

In [8]:
x = x + 2
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-8-bde1f726cb0c> in <module>()
----> 1 x = x + 2

NameError: name 'x' is not defined

Here is another function that would cause an error

In [9]:
def adjust_grade_error(grade):
    global grade
    grade = grade - 5
    print("New grade inside the func:", grade)
  File "<ipython-input-9-137c9cf0f5f6>", line 2
    global grade
SyntaxError: name 'grade' is parameter and global

We don't even need to call the function to get the error -- having grade as a parameter implies it's local, but then we say that it's global. Thsi causes an error.

Passing data back to the main block using global variables

We already did this (kind of), but let's write a function that explicitly uses global variables to pass data out:

In [10]:
def minus5(x):
    global ret_val_minus5
    ret_val_minus5 = x - 5
    
if __name__ == "__main__":
    minus5(10)
    print("Result:", ret_val_minus5)
Result: 5

The right way to do things

The vast majority of the time, you want to pass the data as parameters to the function, and then return the data from the function:

def minus5(x):
    return x - 5
if __name__ == "__main__":
    print(minus5(15)) 

When do global variables make sense? One example is Lab 2 (unless you know about Python classes, which we won't be covering this semester.)

Recall that you need to keep track of the current value, and the previous value, and the memory value. The function add would look something like

def add(current_value, previous_value, memory, to_add):
   #...
   #...
   #...
   return new_cur_value, new_prev_value, new_memory

That is cumbersome