SmithLogo

CSC 111

Introduction to Computer Science Through Programming

Smith Computer Science



Lecture Notes 22: More About Functions





Review of Execution Flow

Possible lingering confusion about execution flow:

  1. where can the functions be defined?
  2. order of execution when there is function composition
  3. Where can variables be used?




Standalone or Module code

We've seen how to use functions defined inside our programs:

def factorial ( a ):
    # iterative approach
    mult = 1
    for i in range (1,a+1):
        mult*=i
    return mult
    
def summation ( a ):
    # iterative approach
    summat = 0
    for i in range (a+1):
        summat+=i
    return summat    
    
def main ():
    num = 12
    fac = factorial(num)
    summ = summation(num)
    print(f"the factorial of {num} is {fac};\n" \
    f"the summation of {num} is {summ}" )
    

main()


Activity 1 [2 minutes]:
  1. Open a scratchpad project in Replit (if you don;t aready have one)
  2. Create a new file called my_operations.py
  3. copy the code shown above inside my_operations.py
  4. Open the tab (on the right pane) called Shell
  5. Run your file using the instruction: python3 my_operations.py


you should get the following output:
the factiorial of 12 is 479001600;
the summation of 12 is 78


As you can you imagine, these two operations might be useful in other contexts... and it would be wasteful to have to write them all over again, soooo....

What if we wanted to make use of this code from another program?



Imagine we want to import!

Say we do something like this:

import my_operations

def main ():
  num = 7
  fac = my_operations.factorial(num)
  summ = my_operations.summation(num)
  print(f"the factorial of {num} is {fac};\n" \
  f"the summation of {num} is {summ}" )
  

main()


What could go wrong?

Activity 2 [2 minutes]:
  1. In the scratchpad project
  2. open the file called main.py
  3. copy the code shown below inside main.py
  4. Run your file using the green Run button (or in the shell with the instruction: python3 main.py)


What is ... unexpected?



We've discussed the concept of importing code so it can be used in other programs.
However, we have not seen how python avoids executing the imported program's main function (which it might as well have)!



The __name__ environment variable


When a program is executed, it is done so inside an execution context that, besides keeping track of the program's variables and code, has what are called environment variables.

One such invironment variable is called __name__ variable.

When a program is rune, the __name__ variable has one of two values:
  1. it contains string "__main__" if the progra executed as a standalone program
  2. it contains the name of the script if the progra was imported


Activity 3 [2 minutes]:
  1. In the scratchpad project
  2. Modify the file called my_operations.py as shown below:

    def factorial ( a ):
        # iterative approach
        mult = 1
        for i in range (1,a+1):
            mult*=i
        return mult
        
    def summation ( a ):
        # iterative approach
        summat = 0
        for i in range (a+1):
            summat+=i
        return summat    
        
    def main ():
        num = 12
        fac = factorial(num)
        summ = summation(num)
        print(f"the factorial of {num} is {fac};\n" \
        f"the summation of {num} is {summ}" )
        
    if __name__ == "__main__":
      main()
    


  3. Open the tab (on the right pane) called Shell
  4. Run your file using the instruction: python3 my_operations.py
    You should get the SAME result as before!

  5. However.... Now run the code in main (Green Run button)


What ... changed?




What is happening is this:


For completeness sake, we should actually write the main inside main.py like this:

import my_operations

def main ():
  num = 7
  fac = my_operations.factorial(num)
  summ = my_operations.summation(num)
  print(f"the factorial of {num} is {fac};\n" \
  f"the summation of {num} is {summ}" )
  
if __name__ == "__main__":
  main()




Multiple parameters and default values

Functions can be defined to take in multiple parameters:

def emphasize(word, my_char):
     print(my_char.join(list(word)))
emphasize("Monday", "-")


Here the output would be:
M-o-n-d-a-y

We can include a “default” value for some (or all) of them:

def emphasize(word, my_char = "*"):
     print(my_char.join(list(word)))
emphasize("Monday")


Here the output would be:
M*o*n*d*a*y

In this case we can call emphasize with only one parameter.


Activity 4 [2 minutes]:
  1. open the the scratchpad project
  2. In the my_operations.py file, add the function (shown bellow) called partial_sum that
    takes two input arguments, x, and y, and returns the partial sum from the smaller to the larger of the two.

    def partial_sum ( x , y):
      small = 0
      big = 0
      if x < y:
        small = x
        big  = y
      else:
        small = y
        big  = x
        
      summat = 0      
      for i in range (small, big+1):
        summat+=i
      return summat
    

  3. Now modify this program with default values so that the defaults are:
    • if only one value is sent, the result is the sum from 0 to the number
    • if no values are sent, the result is 0




Function Composition (Chaining)

Return values allow us to call functions inside other function calls:

>>> n = int(input("Enter an integer: "))
>>> n = int("3")


Similarly,

boom = emphasize(input("Enter a word: ")





Readability and Comments



Example:

fahrenheit = 212
celsius = (fahrenheit - 32) * 5 / 9
Fahrenheit to Celsius.
# calculate the area of a triangle
base = 20
height = 12
area = base * height / 2




Scope

Scope refers to the "area of influence" of a variable or name.
It is the block of code in which it is "alive" and where it could be used.

Look at a modified version of the final code used above:

def factorial ( a ):
    # iterative approach
    mult = 1
    for i in range (1,a+1):
        mult*=i
    return mult
    
def summation ( b ):
    # iterative approach
    summat = 0
    for i in range (b+1):
        summat+=i
    return summat    
    
def main ():
    num = 4
    fac = factorial(num)
    summ = summation(num)
    print(f"the factorial of {num} is {fac};\n" \
    f"the summation of {num} is {summ}" )
    
if __name__ == "__main__":
    main()


If you notice the code for our example, a exists for a brief period of time (only while at factorial).

It's "alive" from the moment the function factorial is invoked and the argument (\(12\)) is inserted into the function parameter a.

We say that the variable a is bound to 12.

Lastly, a is no longer saved after the function returns.

Try it in the Python Tutor


Let's see another example:

 1  
 2  
 3  
 4  
 5  
 6  
 7  
 8  
 9  
10  
11  
12  
13  
14  
15  
# This is the Definition Section
def addThree(x):
    print ('the input was: ' + str(x) ) # concatenation
    y = 0       # not necessary 
    y = x + 3
    print ('I will return:', y) # comma notation
    return y

# This is the Action Section
x = 7
print ('The value of x before \'addThree\' is:', x)
x = addThree(x)
print ('The value of x after \'addThree\' is:', x)
# Was it the value you expected?
# Could I print y here?


Answer the following questions before you try it out in the Python Tutor:

Activity 5 [2 minutes]:
  1. What value of x will be printed at the end?
  2. What is happening with x when we invoke addThree(x)?
  3. What is happening with x after we invoke addThree(x)?
  4. What is happening with x when we return from addThree(x)?
  5. Can we print y at the end?


Try it out in the Python Tutor

The Global Scope

As you can see from where some variables are visualized in the Python Tutor, there is something called The Global Scope!

The global scope corresponds to the maximum scope of the python program (the most external one).

creating variabls at this level let's any other part of the program interact with it.

Global variables are frowned upon because of this.

Some reasons for this is that one might have unexpected side effects from functions using a global variable.

if you see the global keyword used inside a function, that is telling Python to look for the variable in the global scope.
This opens up the chance for that function to modify the variable.

ZyBooks mentions and uses them, but we'll try to avoid using them as we learn more about functions and scope.

The general rule is: A variable's scope is that of the innermost function, class or module in which they're assigned.

We will see more details about scope when we see Conditionals (next class).




Arguments, Parameters, and Local Variables

As you saw above, we have several values being saved into variables and use differently depending on their scope.

First, a couple definitions:

Arguments


In the code shown above,
x = addThree(x)

x is present both as an argument (value or variable passed to a function) of the function addThree,
and as the Left-Hand-Side variable in the assignment.

The order of execution is:
  1. First resolve the value of x
  2. then copy that value into the parameter used inside the function
  3. Once the function returns, assign the return value to the Left-Hand-Side variable of the assignment.


Parameters


In the function definition for addThree, we have:
def addThree(x):

Which has a single parameter that will be received as input.

Activity 6 [2 minutes]:
Answer the following questions:
  1. What is this parameter's Scope?
  2. Does it matter how we name this variable as long as it is a valid identifier? (try it)


Local Variables


Once a function has been invoked with arguments, the values of the arguments are copied into the appropriate parameters and used inside the function as local variables (with the scope local to the function only).

You can also declaree additional variables inside the function and use them while inside the function.

Activity 7 [2 minutes]:
What is an example of a local variable inside addThree?




Function Composition

Function calls can be used as the argument of another function.

This might sound odd and complicated, but it is just like a pipeline of transformations that we link up.

We've also been doing it for a while:

print('The next number is: ' + str( 1 + int( input('number?') ) ) )


Activity 8 [2 minutes]:
What is the output?


Try it out

Another example:

# Definition Section
def times3 (num):
    return num*3

def minus1 (num):
    return num - 1

# Action section
x = input ('gimme a num: ') 
x = int(x)                  # this redefines x
y = minus1( times3(x) )
print('the output of 3x+1 is:',y)


Answer the following questions before trying it out:

Activity 9 [2 minutes]:
  1. Which function is called first?
  2. Where does the return value of the first function go?


Try it out







Homework

[Due for everyone]
TBD

[Optional] TODO