Block B

At the risk of repeating myself we need to learn how to run code repeatedly. There are two forms of looping in Python, definite and indefinite. We will do definite looping first.

What is an iterator? An iterator is a rule that governs the manner in which a collection is traversed. If an object has an iterator, it is called an iterable. All Python container types are iterables, as we shall soon see.

Home on the range This is an iterator that walks through an arithmetic integer sequence.

Here is one guise of the range iterator. It iterates staring a 0 and ending before 6.


>>> for item in range(6):
...     print(item)
... 
0
1
2
3
4
5

When you specify two arguments, you get a start and an end_before.



>>> for item in range(2,5):
...     print(item)
... 
2
3
4

Not Jif, but Skippy.


>>> for item in range(2, 105, 7):
...     print(item)
... 
2
9
16
23
30
37
44
51
58
65
72
79
86
93
100

Uh oh, Integers only, please.


>>> for item in range(0, 1, .1):
...     print(item)
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer

Here is a typical use of the range iterator.


>>> for item in range(5):
...     print("Bart is a brat.")
... 
Bart is a brat.
Bart is a brat.
Bart is a brat.
Bart is a brat.
Bart is a brat.

We bend over backwards to please. But.... this is kinda oogly.


>>> for item in range(10, -1, -1):
...     print(item)
... 
10
9
8
7
6
5
4
3
2
1
0

This is better. The reversed function causes an iterator to walk backwards.


>>> for item in reversed(range(0, 11)):
...     print(item)
... 
10
9
8
7
6
5
4
3
2
1
0

It's no surprise that for lists and tuples, the iterator presents the items in index order.


>>> x = ["a", "b", "c", "d", "e"]
>>> for k in x:
...     print(k)
... 
a
b
c
d
e
>>> t = ("a", "b", "c", "d", "e")
>>> for k in t:
...     print(k)
... 
a
b
c
d
e

A dictionary's iterator walks through its keys in no particular order.


>>> d = {"cat":"meow", "dog":"woof", "pig":"reeeet"}
>>> 
>>> for k in d:
...     print(k)
... 
cat
dog
pig

Here is how to walk through the values.


>>> for k in d.values():
...     print(k)
... 
meow
woof
reeeet
>>> d.values()
dict_values(['meow', 'woof', 'reeeet'])

You learned in sets education that hashing determines order in a set. However, it's a rock-solid guarantee that every item will be visited exactly once.


>>> s = {5,2,9,0,"cow", "pig", "horse"}
>>> for k in s:
...     print(k)
... 
0
cow
2
pig
5
horse
9

Now make this file and name it sampler.txt.

abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
!@#$%^&*()

We now open this file for reading.


>>> fp = open("sampler.txt", "r")

The file pipe object fp has an iterator which walks through a file one line at a time.


>>> for k in fp:
...     print(k, end="")
... 
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
!@#$%^&*()

Another feature is that when it reads a file, it has a bookmark that moves ahead past the items that are read.


>>> fp.tell()
76

We are at byte 76. We now seek to go back to the start.


>>> fp.seek(0)
0

We can suck the entire contents of the file out like so.


>>> guts = fp.read()
>>> guts
'abcdefghijklmnopqrstuvwxyz\nABCDEFGHIJKLMNOPQRSTUVWXYZ\n1234567890\n!@#$%^&*()\n'

We can search the contents for a substring.


>>> "def" in guts
True

Remember what Mr. Rogers says about things you opened.


>>> fp.close()

Here is another cool use of a loop.


>>> import os
>>> files = os.listdir()
>>> for k in files:
...     print(k)
... 
sampler.txt


##  copy file donor to recipient
from sys import argv
if len(argv) < 3:
    print("Usage:  python copy.py donor recipient")
    quit()

donor = argv[1]
recipient = argv[2]

##  open the donor to read it.
fp_in = open(donor, "r")
##  open the recipient for writing
fp_out = open(recipient, "w")

##  copy each line from the donor to the recipient
for line in fp_in:
    fp_out.write(line)

##  ensure both files are closed at the end.
fp_in.close()
fp_out.close()

Here is a better version.


from sys import argv
import os
## warn user about a usage problem.
if len(argv) < 3:
    print("Usage:  python copy.py donor recipient")
    quit()
donor = argv[1]
recipient = argv[2]
#  check to see if the donor exists. If not, bail
if not os.path.exists(donor):
    print("You are attempting to copy from a nonexistent file.")
    quit()
    
##This context manager automatically closes the file when 
##its block ends.
with open(donor, "r") as fp_in:
    with open(recipient, "w") as fp_out:
        for line in fp_in:
            fp_out.write(line)

Here we do stupid stuff.


MAC:Mon Feb 28:09:01:0228> python copy_better.py heffalump.imagainary foo.txt
You are attempting to copy from a nonexistent file.
MAC:Mon Feb 28:09:02:0228> python copy_better.py 
Usage:  python copy.py donor recipient

Challenge Open the scrabble dictionary and print all words containing 3 or more Zs.


words = open("scrabble.txt", "r")
for word in words:
    word = word.lower()
    if word.count("z") >= 3:
        print(word)

Now run it.

$ python zz.py
bezazz

bezazzes

pazazz

pazazzes

pizazz

pizazzes

pizazzy

pizzaz

pizzazes

pizzazz

pizzazzes

pizzazzy

razzamatazz

razzamatazzes

razzmatazz

razzmatazzes

zizzle

zizzled

zizzles

zizzling

zyzzyva

zyzzyvas

zzz

Get rid of the double spacing with end.


words = open("scrabble.txt", "r")
for word in words:
    word = word.lower()
    if word.count("z") >= 3:
        print(word)

Ooh, pretty!

$ python zz.py
bezazz
bezazzes
pazazz
pazazzes
pizazz
pizazzes
pizazzy
pizzaz
pizzazes
pizzazz
pizzazzes
pizzazzy
razzamatazz
razzamatazzes
razzmatazz
razzmatazzes
zizzle
zizzled
zizzles
zizzling
zyzzyva
zyzzyvas
zzz