This is a quick tour of Python syntax and features that students can use as a reference. It is a written version of live coding examples from Lecture 2 of the course. It isn't completely systematic, and it doesn't include every detail of the language that is usually covered in prerequisite courses of MCS 275, but is meant to provide a way to refresh your memory of some of the most important Python features. The optional course texts or the course materials from MCS 260 provide more detailed information.
If the links above don't work, it might mean you are viewing the notebook at GitHub and you can fix it by opening this same document at nbviewer.jupyter.org.
You can run the python interpreter using the interpreter name, usually python
or python3
. It waits for commands to run, and prints the result of each one. This is the REPL.
You can run a whole file of Python code using python FILENAME.py
or python3 FILENAME.py
(depending on the interpreter name).
This document is a notebook, which is more like the REPL. Pieces of code appear in cells, and the value of the last expression in the cell is printed in the output. We'll talk about notebooks more in a future MCS 275 lecture. At the beginning, you can just treat this as a nicely formatted mix of text, code, and output.
# Create a variable by assignment. No separate declaration needed.
x = 5
# What's the value of x now?
x
# Change the value of x a few times; it can change types at any time
x = 10 # an integer
x = 3.25 # a float
x = True # a boolean
x = None # None (the null value)
x = "hello" # a string
# Some integers
1234
1_000_000 # one million (_ is a separator that is ignored, like a comma)
0x1e # hexadecimal
0b1001 # binary
# only the last value in a cell appears in the output, so you should see 9 = 0b1001 below
# Some floats
0.01
-1.3
3e6 # three million. "e" is for exponent, means 3 * 10**6
# The only possible values of a boolean. Note capitalization
True
False # Output will only show this one; it's the last value in this cell
# The only value of type `nonetype` is None. It is used as a "null", a signal of the absence of something.
# Displaying a None in the REPL doesn't display anything at all, so this cell has no output
None
# Some strings. Can use " or ' for single-line strings, or """ or ''' for multi-line strings
"2021" # This isn't a number! It is in quotes, so it is a string
"It was the best of times"
'It was the worst of times'
"""It was time for MCS 275 lecture
but I didn't zoom installed on my
laptop."""
# addition
2+2
# multiplication
7*8
# division yields a float, even if the result is an integer
6/2
# exponentiation is **, and not ^ which is used for this in some other languages
2**5
The order of operations follows the mnemonic PEMDAS:
10 / 2 / 5 # The leftmost division is done first, so this becomes (10/2) / 5 which is 1
# modulus (remainder upon division) is %
1271845 % 7 # This number is one more than a multiple of 7
# Join two strings
"hello" + " world"
# Repeat a string many times using multiplication
"n" + ( "o"*30 )
# Compare numbers with <, >, =, <=, >=
1 < 5
27 >= 27
27 > 27
# Equality is tested with ==
8 == 9
# Strings compare by dictionary order, with < meaning earlier in dictionary
"aardvark" < "abalone"
"skunk" < "shark"
# Capital letters come before lower case letters in this dictionary
"Zebra" < "airplane"
# Convert a string to an integer
int("1500")
# Convert string to integer, using a base other than 10
int("1001",2) # base 2 = binary, so we get 0b1001 = 9
# Convert anything to a string with str()
# Not often needed. But for example you could use this to
# get the digits of an integer, since a string lets you
# access individual characters
str(239.1)
str(False)
str([2,7,5]) # Lists are formatted with spaces after the commas
s = "Hello"
# Convert to upper case (returns the converted value, doesn't change s)
s.upper()
# Convert to lower case
s.lower()
# Test for prefix
s.startswith("He")
# Test for suffix
s.endswith("llo")
# "string in string" tests for the presence of a substring
"car" in "racecar"
"aaa" in "banana"
# String formatting: .format(value, value, ...) formats the values and puts them into the string where
# there are placeholders
# Basic usage
"I wish I had a pet {} so they could eat {} {}".format("mouse",16,"seeds")
# Placeholders can have numbers to indicate which of the arguments they should take
# They are 0-based indices of the list of arguments to .format(...)
"First you put on your {1}, and THEN you put on your {0}".format("shoes","socks")
# Floating point placeholders can have a formatting directive in the format :X.Yf
# where X,Y are integers. The number will be formatted to have a width of at least X characters,
# Y of them after the decimal point. X is optional.
"I am {:3.2f} percent sure of it".format(75.12345)
"Rounding to tenths we find that pi is approximately {:.1f}".format(3.14159265359)
In a script, you won't see any output if your program just contains expressions, because in script mode, Python doesn't print the results of evaluating each line. You need to explicitly request output.
# Display a message in the terminal
print("Hello world")
# Note: This statement doesn't return a value!
# Can print multiple values. They'll be separated by spaces, by default
print("What if I told you that MCS",275,"was fun?")
# print() can handle every built-in type
print("Here is a list:",[5,4,3,1])
# print() puts a newline at the end by default
# This can be disabled
print("ba",end="") # no newline after this one
print("nana")
Lists are mutable ordered collections of elements accessible by integer index. They are similar to arrays in other languages.
# Lists are written with square brackets and commas
[2,7,5]
# Elements of a list can have different types.
# Let's set up an example to work with in the next few cells.
L = [ 1, "fish", 2, "fish", False, True, False, None, 5.25, "last"]
# Retrieve an element of a list using brackets with 0-based index.
L[0] # first element
L[3] # fourth element
# Negative index means count from the end of the list, with -1 meaning last element
L[-1]
# len(listname) gives the length of the list `listname`
len(L)
# Let's set up a sample list to work with (same one used in a previous section)
L = [ 1, "fish", 2, "fish", False, True, False, None, 5.25, "last"]
# Select a contiguous sublist using [start_idx:end_idx]
# Includes the element at start_idx, but not end_idx
# This is an example of a "slice"
L[2:5] # Has 5-2 = 3 elements
# Slices that extend past the end of the list don't produce an error
L[5:100000] # Elements 5 to 99999 requested, but we get 5 to 9
# Slices start at the beginning if the start index is missing
L[:3] # first three elements
# Slices end at the end if the end index is missing
L[3:] # everything but the first three elements
# You can specify that a slice should use a step size other than 1
L[::2] # Every other element, starting at index 0
L[1::2] # Every other element, starting at index 1
# Convert a string to a list to get a list of its characters
list("abcdef")
# Let's set up a sample list to work with (same one used in a previous section)
L = [ 1, "fish", 2, "fish", False, True, False, None, 5.25, "last"]
# Checking whether an item is an element of a list
5.25 in L
2 in L
"ball" in L
# set up a list to work with
courses_taken = [ 260, 275 ]
# Add an element at the end with append
courses_taken.append(320) # doesn't return anything, it just modifies the list
# What's in the list now?
courses_taken
# An element of a list can be replaced using item assignment
courses_taken[0] = 261 # change first element to 261
courses_taken # Now, the first element should be 261
# But you can't add new elements to the list with item assignment
# Only existing elements can be modified
courses_taken[3] = 501 # no item 3 right now, so this gives an error.
# .pop() will remove and return the last element of a list
courses_taken.pop()
# Notice that 320 is no longer in the list
courses_taken
# .index(elt) will find elt and return its position
# or raise an exception if it is not found
courses_taken.index(275) # should return 1 since courses_taken[1] is 275
# .insert(where,what) adds an element at a specific position
# Afterward, listname[where] will be equal to `what`
# Existing items in the list remain, but move to higher indices
# to make room (if needed).
courses_taken.insert(1,401) # Put 401 at index 1, i.e. make it the second element.
courses_taken # Show the resulting list
# Sort a list in place
courses_taken.sort() # note lack of any return value
# But after the previous command, the list is now sorted
courses_taken
# Strings also support integer indices and slices to get characters or substrings
"banana"[0]
"banana"[2:4]
"banana"[1::2] # every other letter
# Reverse a string
"banana"[::-1]
# Similar to lists, you can get the total number of characters in a string with len(...)
len("If you have a garden and a library, you have everything you need.")
Map keys to values. Keys can be of various types (but some restrictions, e.g. lists cannot be keys). Values can be arbitrary types. Written with {} and : to separate key from value.
# Create a dictionary and bind the name "d" to it
d = { "department": "MSCS", "building": 631, "phd_granting": True}
# Access the value associated with a key
d["department"]
# Add a new key-value pair to the dictionary
d["college"] = "LAS"
# Test if a **key** is present
"building" in d # True because "building" is a key of this dict
# You can't use `in` to directly check if a value is present
631 in d # False because 631 is not a key of this dict
if 5 > 7:
print("5 is larger")
else:
print("7 is larger")
x = 15 # adjust this number and run the code to test other branches
if x % 6 == 0:
print(x,"is divisible by 6")
elif x % 3 == 0:
print(x,"is divixible by 3, but is NOT divisible by 6")
else:
print(x,"is not divisible by 3 (and so not by 6 either)")
s = "carpeting" # change this word and run the code to test conditional
if "pet" in s:
print(s,"contains pet as a substring")
print("In fact, it appears at index",s.find("pet"))
# Find a power of 2 that has 7 as a digit
n = 1
while "7" not in str(2**n):
n = n + 1
# The next line is not indented, so it only runs when the loop is finished
print("Found it: The number 2**{} = {} has 7 as a digit".format(n,2**n))
# Take each element of a list and print it
available_drinks = ["coffee","tea","juice"]
for drink in available_drinks:
print("Available drink:",drink)
# Take each key of a dictionary and do something with it
dept_data = { "department": "MSCS", "building": 631, "phd_granting": True}
for field in dept_data:
print("{} = {}".format(field,dept_data[field]))
# Dictionaries support retrieving key,value pairs
# which can be unpacked into two variables in a loop
for field,value in dept_data.items():
print("{} = {}".format(field,value))
# Break out of a loop early with `break`
L = [ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 ]
for x in L:
if "5" in str(x):
# if we hit an integer containing digit 5, we stop the loop
break
print(x)
print("The loop ended, so the next element of L must have contained the digit 5")
Be careful: It can be dangerous to run file-related commands, because opening a file for writing will delete any existing file with that name. It is important to make sure that any time you open a file for writing, the filename does not refer to an existing file whose contents you want to keep!
# Open a new file (delete if it exists already) for writing
output_file = open("output.txt","wt") # wt = write, text (not binary) file
# Write some text to it. Note need to add our own newlines.
output_file.write("Hello.\n")
output_file.write("This is a text file.\n")
output_file.write("That is all.\n")
# Close the file (writes may not actually happen until now)
output_file.close()
# Open the file created above, for reading
input_file = open("output.txt","rt")
# Get everything in the file as a single string
s = input_file.read()
input_file.close()
# Now print what we read from the file
print(s)
# Process lines of that file one by one
input_file = open("output.txt","rt")
for line in input_file:
print("I just read one more line from the file. The contents are:")
print(line,end="") # line will already have a newline, most likely
print("This line of the file has {} characters\n".format(len(line)))
input_file.close()
# Reading is a one-time operation. Trying to read file contents twice
# gives no data the second time.
input_file = open("output.txt","rt")
s1 = input_file.read() # Read everything, after which we're at the end of the file
s2 = input_file.read() # Reads nothing, already at the end of the file
input_file.close()
print("The first time I read from the file I got:")
print(s1)
print("-"*70)
print("The second time I read from the file I got:")
print(s2) # will print nothing, because we read nothing
print("(Expect nothing shown above, since we're already at the end of this file.)")
# In Python, any text after the symbol # on a line is ignored. This is a comment.
# There is no multi-line comment syntax in Python; you just put # in front of each.
x = 1 # You can also put comments on the same line as code
# And # it is ok # for comments to contain # any number of # characters.
Use comments to explain code, making it easier for humans to understand.
Usually, a good comment doesn't just say what a line of code is doing, but explains why.
A complete lack of comments is bad. Excessive comments are also bad, especially when they state the obvious. (This review document, which is expository and written for students relatively new to Python, has a number of comments that would be considered excessive/obvious in other contexts.)
# EXAMPLE OF EXCESSIVE COMMENTS - they describe code that is already clear
for i in range(10): # i from 0 to 9
x = i*i # compute the square of i and store in x
print("Square:",x) # display the square just computed
# EXAMPLE OF INSUFFICIENT COMMENTS
import math
L = []
for i in range(2,30):
L.append(i)
for j in range(2,math.floor(math.sqrt(i))+1):
if i % j == 0:
L.pop()
break
print(L)
# FIXING THE PREVIOUS EXAMPLE
"""Find the primes up to 29 by the naive (inefficient) method"""
import math # For sqrt
L = [] # to hold the primes
for i in range(2,30):
L.append(i) # Add i to the list of primes for now; will remove later if composite
# If i is composite, then one of its divisors is less than or equal to the
# square root of i, so we need only check up to that point for divisors.
for j in range(2,math.floor(math.sqrt(i))+1):
if i % j == 0:
# Now we know i is composite (divisible by j), so remove it from L and
# exit the inner loop, moving on to the next prime candidate.
L.pop()
break
print(L)
In Python, the first statement in a file, function body, or class definition should be a string literal on a line by itself. It should contain a description of the file, function, or class. This is called a docstring.
While having a string on a line by itself does nothing in Python, in these cases (first statement of file, function, class), Python will store the string and make it available as part of the built-in help.
Most explanatory text in a Python program should be in comments. Docstrings go in just a few special places, and describe a file, class, or function.
# Suppose this cell contains the full contents of hello.py
# Print a greeting <--- this describes the file, but it isn't a docstring! Not a string literal
# "Print a greeting" <--- also not a docstring. It's a comment, not a string literal
"""Greet the user""" # <--- THIS IS A DOCSTRING
"""by saying hello""" # BAD! Not a docstring, as it isn't the first statement.
# It should be a commend instead
# Comment line in the middle of the file. That's fine.
print("Hello user!")
# This example involves functions. See the next section for details about functions.
def f(x):
"""Compute the 'smoothstep' polynomial at x and return.""" # <--- DOCSTRING (even without this comment)
return 3*x**2 - 2*x**3
print("f({}) = {}".format(0.1,f(0.1)))
# Retrieve help about f(...), which includes the docstring.
help(f)
Functions provide reusable, named (usually) code blocks that can be called (run) at any point in a program, can accept data from the program, and can return data.
# Basic example
def clamp(x):
"""Clamp x between 0 and 1, i.e. if x is less than 0, return 0.
If x is greater than 1, return 1. Otherwise return x."""
if x<0:
return 0.0
if x>1:
return 1.0
return x
for i in range(17): # iterate over 0...16
t = (i / 10) - 0.3 # so t ranges from -0.3 to 1.3, with steps of size 0.1
print("clamp({:.2f}) = {:.2f}".format(t,clamp(t)))
# Functions can take multiple arguments, which are required if no default value is given
def clamp2(x,minval,maxval):
"""Clamp x between minval and maxval"""
if x<minval:
return minval
if x>maxval:
return maxval
return x
for i in range(11): # iterate over 0...10
t = (i / 10) # so t ranges from 0 to 1, with steps of size 0.1
print("clamp2({:.2f},0.2,0.8) = {:.2f}".format(t,clamp2(t,0.2,0.8)))
# Default values can be specified, and if they are, the argument is optional.
def clamp3(x,minval=0.0,maxval=1.0):
"""Clamp x between minval (default 0) and maxval (default 1)"""
if x<minval:
return minval
if x>maxval:
return maxval
return x
print("clamp3({:.2f}) = {:.2f}".format(t,clamp3(-0.2))) # minval, maxval get default values
print("clamp3({:.2f},-1,1) = {:.2f}".format(t,clamp3(-0.2,-1,1))) # minval, maxval specified
# When calling a function, you can specify arguments by name using name=value syntax
# These *keyword arguments* or *kwargs* can appear in any order, whereas arguments
# given as values alone must appear in the same order as the definition.
print(clamp3(minval=0.4,x=0.3)) # Using kwargs to choose the argument order
# kwargs are really useful for functions with many arguments, most of which have default
# values. Often you want to call them and change just a few arguments from defaults.
# Functions don't need to return anything. If the `return` keyword is not used,
# the function returns None
def hello():
"""Print a friendly greeting"""
print("Hello there!")
hello() == None # True, because no return means return None
Modules allow you to put a bunch of code in a separate file so it can be reused and is contained (allowing it to be used without the user worrying about all the details). Splitting code for a large program between multiple files also makes it easier to browse the source and to maintain the program.
#%%writefile polynomials.py
# Example module
# To actually use this, either:
# 1) Save the entire cell contents in a file "polynomials.py"
# or 2) Remove the # in front of the first line of this cell and run in a notebook
"""Some useful polynomials and other functions"""
USEFUL_CONST = 1234.0 # all caps is *not* required
def nozeros(x):
"""A quadratic polynomial with no real zeros"""
return x**2 + 1
def smoothstep(x):
"""Smoothstep polynomial (clamped between 0 and 1)"""
if x<0:
return 0.0
if x>1:
return 1.0
return 3*x**2 - 2*x**3
"""Example of using the polynomials module"""
# (Won't work unless you've saved the cell above to polynomials.py in the same directory where
# you are running this code.)
import polynomials
print(polynomials.USEFUL_CONST)
print(polynomials.nozeros(0.2)) # should be 1.004
print(polynomials.smoothstep(0.45)) # should be 0.42525
# There are many useful modules in the Python standard library
# A few examples follow
import sys
print(sys.version) # Python version
print(sys.argv[0]) # Name of the currently running script
import time
print("Working...")
time.sleep(1.25) # Wait for 3.1 seconds
print("Done!")
print("It is now {} seconds since 0:00 on Jan 1 1970 GMT".format(time.time()))
import os
print("Files in the current directory:")
for fn in os.listdir("."):
print(fn)
# Check for a file that exists (for the instructor developing this worksheet)
if os.path.exists("output.txt"):
print("A file with name '{}' exists in the current directory.".format("output.txt"))
# Check for a file that does not exist
if os.path.exists("worksheet1soln.ipynb"):
print("A file with name '{}' exists in the current directory.".format("worksheet1soln.ipynb"))
In Python, classes provide a way to define custom types that combine attributes (data) and methods (behavior).
They are the basis of object-oriented programming in Python.
Classes can extend other classes, allowing customization of certain properties while reverting to the parent class behavior on others.
# Basic 2D point class
import math # for sqrt
class Point:
"""A point in the xy-plane""" # DOCSTRING
def __init__(self,x,y):
"""Initialize new point instance"""
self.x = x
self.y = y
def translate(self,dx,dy):
"""Move the point by a vector (dx,dy)"""
self.x += dx
self.y += dy
def distance_to(self,other):
"""Distance from this point to another one"""
deltax = other.x - self.x
deltay = other.y - self.y
return math.sqrt(deltax**2 + deltay**2)
P = Point(3,4) # Creates instance of Point, which calls __init__(...)
print("Distance from (3,4) to the origin: {:.2f}".format(P.distance_to(Point(0,0))))
print("Before moving: P.x={}, P.y={}".format(P.x,P.y))
print("Moving by (5,1)")
P.translate(5,1) # Modifies P, returns nothing!
print("After moving: P.x={}, P.y={}".format(P.x,P.y))
# Class help includes docstrings of the class itself and its methods
help(Point)