Today: whole program example - pylibs, floating point, Ghost demo, mimic demo
Today we'll do a whole program in class to walk through the whole process.
Download pylibs.zip to get started. We'll work through this together.
First, look at what problem we want to solve - like Madlibs.
Say we have two files. It's handy to invent terminology for the parts of your abstract problem to then use in yours docs, your var names, etc. Here we'll define "terms" and "template":
The "terms" file provides a list of words for each "noun" type category. One category per line, separated by commas, like this:
noun,cat,donut,velociraptor verb,nap,run
The "template" file has text, and within it are markers like "[noun]" where a random substitution should be done.
I had a [noun] and it liked to [verb] all day
We want to run this program giving it the terms and templates files, and get the output like this
$ $ python3 pylibs.py test-terms.txt test-template.txt I had a velociraptor and it liked to nap all day
Let's do it. Look at pylibs.py
Input line from terms file like this:
noun,cat,rabbit,velociraptor
Use line = line.strip() to remove newline. Use parts = line.split(',') to separate on the commas.
Create entry in terms dict like:
'noun': ['cat', 'rabbit', 'velociraptor']
File 'test-terms.txt' - write a Doctest
noun,cat,donut,velociraptor verb,nap,run
Write a Doctest for the file 'test-terms.txt', so we know this code is working before proceeding.
Here is our solution complete with docs and doctest - in class, anything that works is doing pretty well.
def read_terms(filename):
"""
Given the filename of the terms file, read
it into a dict with each 'noun' word as a key
and its value is its list of subs ['apple', 'donut', 'unicorn'].
Return the terms dict.
>>> read_terms('test-terms.txt')
{'noun': ['cat', 'donut', 'velociraptor'], 'verb': ['nap', 'run']}
"""
terms = {}
with open(filename) as f:
for line in f:
# line is: noun,apple,rabbit,velociraptor,balloon
line = line.strip() # remove \n
parts = line.split(',')
term = parts[0]
words = parts[1:]
terms[term] = words
return terms
main() - calls two helpers, just need to write them
# command line: terms-file template-file
if len(args) == 2:
terms = read_terms(args[0])
process_file(terms, args[1])
The beginning of this function is pretty standard, read through lines of filename. Here is the code for the start:
def process_file(terms, filename):
"""
Given terms dict and filename of template.
Process the template file, printing out its lines
with the substitution done.
"""
with open(filename) as f:
for line in f:
words = line.split() # ['had', '[noun]']
# print each word with substitution done
Key trick - use line.split() to get the list of words that make up each line. This also takes care of the \n at the end.
line.split() -> ['I', 'had', 'a', '[noun]']
Q: What is the useful helper we want here?
A: A function that did the substitution for one word, so calling it with '[noun]' returns 'apple' would be handy here - decompose that out.
If word is of the form '[noun]' return a random substitute for it from the terms dict. Otherwise return the word unchanged.
Note 1: s.startswith() / s.endswith() very handy here to look for square brackets
Note 2: random.choice(lst) returns a random element from list.
Here our solution has all the Doctests added, but for in-class anything that works is fine.
This is a nice example of a helper function: (1) isolates some complexity within this function were we can solve and test it. (2) Also makes its caller function more tractable.
def substitute(terms, word):
"""
Given terms dict and a word from the template.
Return the substituted form of that word.
If it is of the form '[noun]' return a random
word from the terms dict. Otherwise
return the word unchanged.
>>> substitute({'noun': ['apple']}, '[noun]')
'apple'
>>> substitute({'noun': ['apple']}, 'donut')
'donut'
"""
if word.startswith('[') and word.endswith(']'):
word = word[1:len(word) - 1] # trim off [ ]
if word in terms:
subs = terms[word] # list of ['apple', 'donut', ..]
return random.choice(subs)
return word
Note: print a word followed by one space and no newline:
print(word + ' ', end='')
...
words = line.split()
for word in words:
sub = substitute(terms, word)
print(sub + ' ', end='')
print()
We have main() process_file() and substitute() wired together. Try it from the command line, with the files 'terms.txt' and 'template.txt'
$ cat terms.txt noun,velociraptor,donut,ray of sunshine verb,run,nap,eat the bad guy adjective,blue,happy,flat,shiny $ $ cat template.txt I had a [noun] and it was very [adjective] when it would [verb] $ $ python3 pylibs.py terms.txt template.txt I had a ray of sunshine and it was very shiny when it would nap $ $ python3 pylibs.py terms.txt template.txt I had a velociraptor and it was very shiny when it would eat the bad guy $
# int 3 100 -2 # float, has a "." 3.14 -26.2 6.022e23
>>> 1 + 1 + 1 3 >>> 1 + 1 + 1.0 # float promotion 3.0 >>> 3.14 * 2 6.28 >>> 3.0 * 3 9.0 >>> 3.14 * 2 + 1 7.28
>>> float('3.14') # str -> float
3.14
>>> int(3.14) # float -> int, truncation
3
>>> int('16')
16
>>> int('3.14')
ValueError: invalid literal for int() with base 10: '3.14'
>>> 0.1 0.1 >>> 0.1 + 0.1 0.2 >>> 0.1 + 0.1 + 0.1 # this is why we can't have nice things 0.30000000000000004 >>> >>> 0.1 + 0.1 + 0.1 + 0.1 0.4 >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 0.5 >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 0.6 >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 0.7 >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 0.7999999999999999 # here the garbage is negative
Another example with 3.14
>>> 3.14 * 3 9.42 >>> 3.14 * 4 12.56 >>> 3.14 * 5 15.700000000000001 # d'oh
Summary: float math is slightly wrong
The short answer, is that with a fixed number of bytes to store a floating point number in memory, there are some unavoidable problems where numbers have these garbage digits on the far right. It is similar to the way the number 1/3 is not possible to write it out precisely as a decimal number.
>>> a = 3.14 * 5 >>> b = 3.14 * 6 - 3.14 >>> a == b # Observe == not working right False >>> b 15.7 >>> a 15.700000000000001
>>> abs(a-b) < 0.00001 True