This is not an exhaustive tour, but it quickly exhibits some key aspects of the Python language.
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 introduce notebooks properly in a future MCS 275 lecture, so for now you can just treat this as a nice way to present many small programs to you.
# Create a variable by assignment. No separate declaration needed.
x = 275
# 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 # a different integer value
x = 3.25 # a float
x = True # a boolean
x = None # None (the null value)
x = "hello" # a string
# AVOID TYPE CHANGES IN MOST CASES as they make programs harder for humans to understand.
# 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."""
I skip this section in class as it's likely familiar; the content is here for reference or review.
# addition
2+2
# multiplication
7*8
# division yields a float, even if the result is an integer
6/2
# INTEGER DIVISION, with two slashes/ is division of integers that yields and integer
# The remainder is discarded.
6//2
7//2
8//3
# Percent sign means modulus, or "remainder when dividing by"
500 % 7
So 500 days is some number of weeks plus 3 extra days.
# exponentiation is **, and not ^ which is used for this in some other languages
2**10
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")
L = [2, True, "hello", 0.001] # a list, note heterogeneous types
D = { "ddumas": "AE1953D8C00F",
"bkarloff": "41028F0DE021"} # a dict, mapping keys to values
L[2]
D["ddumas"]
Lists are mutable ordered collections of elements accessible by integer index. They are similar to arrays in other languages.
# 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 divisible by 3, but is NOT divisible by 6")
else:
print(x,"is not divisible by 3 (and so not by 6 either)")
The following is a classic example of the same phenomenon in the previous example: When you consider several conditions, and one is implied by another one, you need to check the more specific one first.
This code will print integers 0 to 99, following each integer with "fizz" if the integer is a multiple of 3, "buzz" if it is a multiple of 5, and "fizzbuzz" if it's a multiple of both.
You must check for being a multiple of both first!
for i in range(100):
if i%15==0:
print(i,"fizzbuzz")
elif i%3==0:
print(i,"fizz")
elif i%5==0:
print(i,"buzz")
else:
print(i)
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")
A slightly more involved example: A little program that repeatly rolls dice or flips coins, until you want to quit.
import random
print("Randomizer Pro v1.0")
while True:
s = input("Flip (C)oin, Roll (D)ie, or (Q)uit? ")
s = s.lower()
# Check for invalid input
if s not in "cdq":
print("I don't know that command!")
continue # jump back to the top of this loop
# Note: continue is often used like this to handle error conditions at the top of a loop
# Check the various valid input possibilities
if s == "c":
print(random.choice(["Heads","Tails"]))
elif s == "d":
print(random.randint(1,6))
else:
# s == "q", so we exit
break
print("Thanks for using Randomizer Pro v1.0")
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!
# Write some text to a file, METHOD 1 (all at once)
# 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.\nThis is a text file.\nThat is all.\n")
# Close the file (writes may not actually happen until now)
output_file.close()
Or we could do the same thing but write one line at a time. You can call .write
as many times as you like.
# Write some text to a file, METHOD 2 (line by line)
# 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()
Let's read those lines back. First, read all at once:
# Open the file created above, for reading, METHOD 1 (all at once)
input_file = open("output.txt","rt")
# Get the entire content of the file as a single big string (probably lots of \n in it)
s = input_file.read()
input_file.close()
# Now print what we read from the file
# (end="" means don't add a newline, just print s itself)
print(s,end="")
Note you can't call .read()
more than once because there is notion of the "current position" in the file. Once you call .read()
, your current position is the end of the file. Calling .read()
a second time will return nothing, because there's nothing to read after the end of the file.
Using .read()
is only appropriate when you want the entire content of the file at once.
# 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.)")
If you're reading a file full of text, it's more likely you want to process it incrementally, one line at a time. That's also possible, but not using .read()
. Instead, there's a method .readline()
that returns the next line of text, or an empty string if there's no more.
# Open the file created above, for reading, METHOD 2 (line by line)
input_file = open("output.txt","rt")
while True:
line = input_file.readline()
if line=="":
# We reached the end; exit the loop
break
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()
Or, instead of calling .readline()
repeatedly, you can also just use the file object in a for loop. That will automatically do something for each line. The following is thus a simpler but equivalent form of method 2:
# Open the file created above, for reading, METHOD 2' (line by line)
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()
# 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)