Reading

Check out https://docs.python.org/3/tutorial/interpreter.html for more information on the python interpreter.

Interpreted vs. compiled languages and "what is an interpreter?"

C and C++ are compiled languages. As you should now know well, the compiler translates your source code into object code, and links together your object code with standard library code to produce an executable — a file full of instructions that can be executed directly by the CPU. Then you, at your leisure, can tell the OS to launch your program.

In IC210/SI204 you may have done something like this lab. Your solution to this lab is, in fact, a very simple interpreter! You can see that it has a read-eval-print loop as described below. Your program in that lab was the interpreter, and you were interpreting "source code" like "swap 2 5".
With an interpreted language, there is a program called the interpreter that reads in your source code and executes that code as it is read in. This means that your source code does not need to be translated into machine instructions for the CPU. The CPU is executing the interpreter. The interpreter takes whatever actions are specified in the source code. Calculators like the unix bc utility are interpreters. The good old bash shell you know and love is an interpreter. The program python3 is the python interpreter we will be using.

Note: Java is an interesting special case. The Java Compiler (javac) compiles your source code, but it compiles it to bytecode, not CPU instructions. Bytecode is a simple language, and the program java is an interpreter that interprets the Java bytecode language. So if someone asks: "Is Java compiled or interpreted?", you answer "yes".

Interactive vs. non-interactive mode

The interpreter runs in two ways: interactive or non-interactive. In interactive mode it runs in a REPL "Read Eval Print Loop". It outputs a prompt, then reads an expression/statement, evaluates the expression/statement and prints the result of the evaluation. Then, of course, it loops back around and does it all over again!
Important! The interpreter keeps reading input until it hits end-of-file. So in interactive mode, use ctrl-d to simulate end of file and, thus, exit the interpreter.
Note: some things, like x = 2 don't have a result, so nothing is printed. Also, a variable all on its own evaluates to its value, which allows you in interactive mode to inspect the variable's value.
$ python3  ←running python3 with no arguments we get interactive mode
>>> 3 + 5
8
>>> x = 1.455
>>> x
1.455
>>> 2*x + 0.2
3.1100000000000003
When python is launched with standard input tied to a terminal, it automatically goes into interactive mode (like in the previous example). If standard input is coming from somewhere else, or you've directed python to read its code from a file (which we'll see later), it automatically chooses non-interactive mode. In non-interactive mode we don't get a REPL (Read-Eval_Print-Loop). Instead, while the source code is indeed read in, no prompt is printed out. And while after being read the source code is indeed evaluated, the results are not printed (though you can call the print(·) function explicitly to print things).

$ python3 
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 3*4 
12
>>> 
Run this way we get interactive mode because standard in is tied to the terminal.
$ echo "3*4" | python3 
$
Run this way we get non-interactive mode because standard in is coming from a pipe, not a terminal.
3*4 gets evaluated, but because we are in non-interactive mode, the result is not printed.
$ echo "3*4" | python3 -i 
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 12
>>> 
The -i option forces the interpreter into interactive mode.
3*4 gets evaluated and printed because we are in interactive mode.
And ... with the -i option input moves over to the terminal once stdin is exhausted, which is why the interpreter doesn't automatically exit.

Important! While interesting to examine, don't use python like those last two examples! If code you want to use non-interactively is supposed to output something to standard out, we use the print(·) function, which writes its argument to standard out.
$ echo "print(3*4)" | python3 
12
The advantage to using print() to produce output rather than relying on the interactive shell to print results is that we can take the code we used in our interpreter session and put it in a file that we can run over and over.
file: ex01.py run python with file arg
rate = 5.85
bal = 10500.00
interest = bal * rate/100 
print(interest)
$ python3 ex01.py 
614.2499999999999
What's crucially important is that the interpreter (i.e. python3 program) is reading and evaluating each line of ex01.py as if you typed it into the interpreter - except that it's not printing prompts or results because it's not in interactive mode. You can see this in the following:
file: ex02.pyrun python with file argNOTE
print("starting ...")
rate = 5.85
bal = 10500.00
interest = bal * rate/100
print("... calculations complete!") 
print(3/asdf)
$ python3 ex04.py 
starting ...
... calculations complete!
Traceback (most recent call last):
  File "/home/scs/wcbrown/courses/F24SI342/pythonPlay/ex04.py", line 6, in <module>
    print(3/asdf)
NameError: name 'asdf' is not defined
There is one way in which non-interactive mode is different from interactive mode without the prompt and printing outputs. It turns out that the interpreter tries to read and parse the whole input at once before executing. So if there is a syntax error, it will be detected and the interpreter will exit because of it before evaluating anything.
Note: an undefined variable error is *not* a syntax error in Python, even though it is a syntax error in C/C++/Java.
Also: note that the interpreter exits on error when in non-interactive mode.

One last thing! You can use the -i option along with a file argument so that, after executing the code in the file, you drop back into interactive mode, and any functions or variables defined in the file are available for you to use.

$ python3 -i ex01.py 
i = 614.2499999999999
>>> rate
5.85
>>> 
This is really nice if you are defining functions and you want to test your function definitions as you go. That, in turn, makes bottom up programming really easy. Top-down programming with the technique of "stubbing out" functions (see IC211 notes!) is also easier because of this.

file: ex03.pyrun
def sqr(x):
    return x*x 
$ python3 -i ex05.py 
>>> sqr(7)
49
>>> 

In fact, this ability in interpreted languages to play with your functions and classes in the interpreter as you create them is one of the real benefits of an interpreted langauge. In C/C++ and Java and other compiled languages, you need to write a program that calls your new function in order to play with it. Instead, in an interpreted language we just load the definition and use the function in the interpreter!

ACTIVITY: Bottom up programming in Python

Break up into pairs, with one computer per pair for coding!

The purpose of this activity is to get you thinking about how easy it is to do bottom up programming in an interpreted language like python, because you can easily test each function as you write it by running python3 -i yourcode.py and calling the function on different inputs from within the interpreter. [Important! the interpreter has the up-arrow-for-past-commands feature, where the past command history goes back over past times you've launched the interpreter. This is helpful!]

Your ultimate goal is to define a function "grade" that takes an array G of assignment grades (0-100) and array W of weights, where len(W) = len(G), and returns an overall letter grade from the "normalized weighted sum" of the assignment grades.

  1. the weighted sum of the grades is $\sum_{i=0}^{len(G)-1} A[i]\cdot W[i]$.
  2. the normalized weighted sum is the weighted sum of A and W divided by $\sum_{i=0}^{len(W)-1} W[i]$, i.e. divided by the sum of the elements of $W$.
  3. the grade cutoffs are:
    ABCDF
    $g \geq 90$$90 \gt g \geq 78$$78 \gt g \geq 66$$66 \gt g \geq 50$$50 \gt g$

My gift to you the following starter code ... which does not work!
Below is my bottom up plan for developing this function. Start with a file sol.py and add code to it, following the below plan as you go. When you test, run as python3 -i sol.py so you can easily test your functions. Notice how the interpreter makes it easy to follow our gold standard approach to programming: write a very small amount of code, then stop to test and debug!
  1. Write and test function sum(W) that takes an array/list of numbers and returns the sum of those numbers.
    >>> sum([10.5,6,4.25,5])
    25.75
  2. Write and test function wsum(G,W) that takes an array/list of grades and of weights and returns the weighted sum of those numbers.
    >>> wsum([100,50,50,80],[.5,1,1,.25])
    170.0
  3. Write and test function nwsum(G,W) that takes an array/list of grades and of weights and returns the normalized weighted sum of those numbers.
    >>> nwsum([100,50,50,80],[.5,1,1,.25])
    61.81818181818182
  4. Write and test function grademap(x) that takes number $x$ in the range $[0,100]$ and returns the letter grade (as a string) as described above.
    >>> grademap(95)
    'A'
    >>> grademap(78.5)
    'B'
    >>> grademap(66.0)
    'C'
  5. Write and test function grade(G,W) that takes an array/list of grades and of weights and returns the overall letter grade as described above.
    >>> grade([100,50,50,80],[.5,1,1,.25])
    'D'
    >>> grade([100,50,50,80],[3,2,2,1])
    'C'
    >>> grade([100,50,50,80],[3,1,1,2])
    'B'
    >>> grade([100,50,50,80],[10,1,1,1])
    'A'
    >>> grade([100,50,50,80],[-1,10,10,1])
    'F'
Note: Check out more tests.

Shebangs and module imports

All that said, when we want to write "a program" in python, we don't usually type it into the interpreter or put it in a file and run as python3 -i foo.py or even as python3 foo.py. Instead we use a trick you should know already from systems programming. In unix, if a file foo starts with line
#!<path>
and you chmod +x it, then the shell command ./foo will cause the shell to call <path> with ./foo as a command line argument. Note: the #! line is often called a "shebang". This is the same mechanism you saw in Systems Programming for making an executable script out of a file of bash commands. Bash is, as I hope you now see, and interpreted language. The bash shell is the interpreter, and we use it interactively (as a read-eval-print loop) all the time! But, sometimes we also want to store sequences of commands in scripts. Just like Python.

Often people will use a slightly different shebang
#!/usr/bin/env python3
The program "env" uses the PATH environment variable to find the python3 executable (or whatever its argument is) and executes that with the current file as its argument. Just an FYI, because you may see it.
file: ex04.pyrunning
#!/usr/bin/python3 

def sqr(x):
    return x*x

print(sqr(42))  
$ ./ex04.py  
1764

This is exactly equivalent to running: python3 ex04.py

The import statement

If we want to load code from another file into the interpreter, either to use while in interactive mode or to use within our own scripts, we use the import command. How import works is actually a big topic, and the main reason is that we have to wade into namespaces. To finish off this class, just be aware that if you have a file foo.py you would load that code into the interpret with the command
import foo
... which executes the code in foo.py (i.e. you leave off the .py part in the import statement). The catch is that if a name bar is defined in foo.py, perhaps as a variable or function or class, that binding inside the interpreter will go by the name foo.bar. In other words, all the names defined in foo.py belong to the namespace foo not to the default namespace. Hence, if you use our code from the activity as an example and assume it is in a file dbsol.py:
$ python3
Python 3.10.12 (main, Jul 29 2024, 16:56:48) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import dbsol
>>> grademap(78.5)
Traceback (most recent call last):
  File "<stdin>", line 1, in >module>
NameError: name 'grademap' is not defined
>>> dbsol.grademap(78.5)
'B'
>>> 
If you want names defined in the file you are importing to be loaded into the same namespace you are in, you can load the file and have a specific name defined in your current namespace:
	from dbsol import grademap
	     \___/        \______/
        file dbsol.py     name you want      
... or just "load all the names" with
	from dbsol import *
	     \___/        |
        file dbsol.py     I want everything      
So we end with this example:
$ python3
Python 3.10.12 (main, Jul 29 2024, 16:56:48) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from dbsol import *
>>> grademap(78.5)
'B'
>>> 
There will be more to say later on import.

Christopher W Brown