We're going to start writing some Python code soon, but first we need to set up our development environment.
Python is such a widely-used language that it is installed by default on many operating systems, including Linux. This class will use Python 3.12 (or above), which includes some nice language features that aren't present on older versions. While we could just upgrade our system's Python interpreter version, it's not usually the best approach because the OS needs Python and we might create some side effects.
module load python/3.12Now when you run python --version, you should see it print something like 3.12.12. Any version above 3.12 is the right version for this class! If you want to install this version on your local machine, you probably don't have the module command available, but you can install Python 3.12 with pyenv (on Windows), apt|yum|dnf on Linux, or Homebrew on a Mac.
We're going to install some Python development packages (specifically Mypy, and perhaps some others throughout the term). Trouble is, we don't have the necessary permissions to install software packages on the engineering servers.
But there's a simple workaround. Python ships with a module called venv, which stands for Virtual Environment. Packages installed in a Python virtual environment are isolated from the rest of the system, and you have full access to every Python virtual environment that you create. Hence, Python virtual environments will allow us to install and use whatever software packages we need throughout the term. (See the official docs about Python virtual environments for more info).
Login to the engineering servers in a terminal via an SSH session. Use cd to navigate to your cs-162 directory (if you don't already have one, you should create one via mkdir). Then execute the following shell command:
python -m venv envAfter a few minutes, the command will complete, and you'll have a new directory named env inside your cs-162 directory (you can see it with ls). That directory contains the Python virtual environment that you'll use for all your work in this course.
But you're not done yet. Next, you need to activate your Python virtual environment. The simplest way to do this is by sourcing the activation script, but that will only activate it until you log out of the engineering servers—next time you log in, it will be deactivated again. To make your life easier, I strongly suggest configuring Bash to automatically activate it immediately whenever you log into the engineering servers. To do this, use Vim to open up the .bashrc file in your home directory:
vim ~/.bashrc(You should already have a .bashrc file, and it should already have some important stuff in it. If it looks empty, then you probably typed its name wrong. It should be exactly ~/.bashrc). Scroll to the bottom of the file. Enter insert mode (press the i key) and paste the following command into its own line:
source ~/cs-162/env/bin/activateIf your cs-162 directory is not stored directly inside your home directory (represented by the tilde, ~), that's fine—just replace ~/cs-162/env with the path to the env directory created in the previous step (recall that you can use pwd to see the path of your current working directory). Save and quit Vim (press escape to enter Normal Mode, and then type :wq and press enter).
Finally, you need to get Bash to run the updated .bashrc script. It automatically runs this script every time you log into the engineering servers, so simply logging out (e.g., via the exit command, or by exiting out of your terminal) and logging back in will do the trick. Alternatively, you can just source the .bashrc file by running the following shell command in your terminal: source ~/.bashrc.
The text (env) should now appear at the very left of your terminal prompt. For example, it might look something like this:
(env) guyera@flip2:cs-162$(You might see double parenthesis around your venv, which is okay.) This means that your virtual environment has been activated. You can now install Python packages.
(If you'd ever like to undo this configuration, simply remove the line from ~/.bashrc that you just added). You can also deactivate an active virtual environment by running:
(env) guyera@flip2:cs-162$ deactivate
guyera@flip2:cs-162$The first thing you need to know about Python is that it's (sometimes infamously) known for its "duck typing" type system. This means that an object can be used in an expression so long as the object has all the methods and attributes referenced by that expression. For example, someVariable.print() is a valid Python expression so long as someVariable is an object with a method (member function) named print that has no required arguments (this will make more sense when we've covered classes, objects, and methods later on in the course). It's called "duck typing" because of the common idiom, "if it quack()'s like a Duck, then it must be a Duck". Moreover, in Python, the type of object referenced by a variable can change throughout the duration of the scope. For example, one line of code can assign x = 5, and then the next line of code can reassign x = 'Hello', thereby changing the type of x from int to str.
This is in stark contrast to many other programming languages that have strict static type systems. For example, in C++, every object (variable, constant, etc) must be (explicitly or implicitly) given a static type at declaration, and its static type cannot be changed from that point on—it may not be assigned a value of an incompatible type, and it may only be used in expressions that are specifically crafted to accept its static type.
Although Python's duck typing might sound convenient, it's often really a nightmare disguised as convenience. Because variables' types can change at any time, knowing what type a variable is requires analyzing every single line of code in the entire scope since its type might change several times throughout that scope. In contrast, in C++, an object's (static) type cannot change once declared, so to know its (static) type, you only need to look at its declaration (C++ objects can also have dynamic types, which can change at runtime, but you should never need to know an object's dynamic type anyways).
Static type systems also support powerful static analysis of type-related errors. That's to say, in C++, your compiler can catch type errors before you even run the program. In Python, type errors are inherently runtime errors because of its duck typing type system. Runtime errors are the worst kind of errors because 1) they aren't detected until you actually run the program, 2) even when you do run the program, depending on the nature of the error, it may or may not occur (e.g., if the type error is in an if statement body, it will only occur if that if statement body is actually triggered during the execution of the program), and 3) in the worst case, runtime errors can propagate to other runtime errors, which propagate further, and further, until a fault eventually occurs (e.g., the program crashes, or it produces nonsensical outputs) somewhere far away from the root cause of the problem. That's all to say, runtime errors can be hard to detect and diagnose. Hence, Python's duck typing makes type errors, which are one of the most common kinds of errors, hard to detect and diagnose.
In case you still aren't sold, static types also serve as a clear form of documentation. For example, imagine a function named areaOfCircle() that accepts a parameter radius of type Meters and returns a value of type SquareMeters. You don't even need to look at the function's documentation to know exactly what this function does: you give it the radius of a circle in meters, and it returns the circle's area in square meters. In a duck typing type system with no static type hints, all you'd know is that the function accepts a radius of some sort and maybe returns something; you'd have to read the function's documentation or, worse, its implementation, if you want to know more. (A static type system would also prevent you from accidentally messing up your units here, like passing in a radius expressed in millimeters).
So, there are lots of disadvantages to Python's duck typing, and lots of advantages of a static type system. The only real advantage of duck typing is that you never have to explicitly write out the names and definitions of types (i.e., it saves you keystrokes). Perhaps that tradeoff is worth it for extremely small, internal projects (e.g., when using Python as a scripting language), but it's often not worth it for larger codebases.
Luckily, there is a way to leverage the power of static typing in a Python codebase. Although Python has a duck typing type system, it supports optional static type hints, and it can be supplemented with certain static analysis tools that get you pretty close to a C++-like static type system. Mypy is one such tool, and it's essentially industry-standard at this point.
So, we're going to install Mypy in our Python virtual environment. Every virtual environment comes with a copy of pip, which is a recursive acronym that stands for "pip installs packages". pip is the standard Python package manager, meaning it's used to install Python packages. We will use pip to intall Mypy. Make sure your virtual environment is activated, and then execute the following shell command:
pip install mypyMypy is not very strict by default. However, it can be configured to be strict, and when the TAs use Mypy to verify that your code has no type errors, they will, indeed, configure it to be strict. Hence, you should do so as well so that it works the same way for you as it does for the TAs when they grade your work. Use vim to open your .bashrc file again:
vim ~/.bashrcCopy and paste the following command in its own line at the bottom of your .bashrc file:
alias mypy='mypy --strict'Save and quit vim. Log out of the engineering servers and log back in (or simply execute source ~/.bashrc). From now on, whenever you run mypy in your shell on the engineering servers, it will automatically run it under a stricter configuration.
Our development environment is now configured, so we're ready to start writing code.
Use vim to create and open a file called hello.py. You can create this file wherever you'd like, but I recommend creating it in a directory where you plan to follow along with my lecture demonstrations.
Enter insert mode (by pressing the i key) and paste the following code into the file (e.g., via Ctrl+Shift+V):
hello.pydef main() -> None:
print('Hello, World!')
if __name__ == '__main__':
main()
Whenever you make changes to a Python program in this course, you should always use mypy to verify that there are no type errors in your code. Let's do that now. Save and quit vim (enter Normal Mode via the escape key, and then save and quit by typing :wq and pressing the enter key). Then run the following shell command:
mypy hello.pyThis will run mypy over the code in hello.py, notifying you of any type errors (or other Mypy-discoverable errors) present in your source code. In this case, there's nothing wrong with the code, and a message should be printed to your terminal indicating as such. The very first time you run mypy, you may find it takes a while, but it will be faster in subsequent runs.
Gradescope will always run mypy over your code when grading your programming submissions, so if you forget to do it yourself you might be surprised when you see test failures after submitting your code. Even if the program works, you'll still be penalized for having type errors.
Now let's run the program itself. The standard way of running Python programs is through the python interpreter; simply execute the following shell command:
python hello.py