Test your knowledge: Part II exercises

1. The basics

Run each of the following expressions, and try to explain what’s happening in each case. Note that the semicolon in some of these is being used as a statement separator, to squeeze multiple statements onto a single line: for example, X=1;X assigns and then prints a variable. Also remember that a comma between expressions usually builds a tuple, even if there are no enclosing parentheses: X,Y,Z is a three-item tuple, which Python prints back to you in parentheses.


In [ ]:
2 ** 16

In [ ]:
2 / 5, 2 / 5.0

In [ ]:
"spam" + "eggs"

In [ ]:
S = "ham"
"eggs " + S

In [ ]:
S * 5

In [ ]:
S[:0]

In [ ]:
"green %s and %s" % ("eggs", S)

In [ ]:
'green {0} and {1}'.format('eggs', S)

In [ ]:
('x',)[0]

In [ ]:
('x', 'y')[1]

In [ ]:
L = [1,2,3] + [4,5,6]
L, L[:], L[:0], L[-2], L[-2:]

In [ ]:
([1,2,3] + [4,5,6])[2:4]

In [ ]:
[L[2], L[3]]

In [ ]:
L.reverse(); L

In [ ]:
L.sort(); L

In [ ]:
L.index(4)

In [ ]:
{'a':1, 'b':2}['b']

In [ ]:
D = {'x':1, 'y':2, 'z':3}
D['w'] = 0
D

In [ ]:
D['x'] + D['w']
D

In [ ]:
D[(1,2,3)] = 4
D

In [ ]:
list(D.keys()), list(D.values()), (1,2,3) in D

In [ ]:
[[]], ["",[],(),{},None]

2. Indexing and slicing

At the interactive prompt, define a list named L that contains four strings or numbers (e.g., L=[0,1,2,3] ). Then, experiment with the following boundary cases. You may never see these cases in real programs (especially not in the bizarre ways they appear here!), but they are intended to make you think about the underlying model, and some may be useful in less artificial forms—slicing out of bounds can help, for example, if a sequence is as long as you expect:

  • What happens when you try to index out of bounds (e.g., L[4] )?
  • What about slicing out of bounds (e.g., L[−1000:100] )?
  • Finally, how does Python handle it if you try to extract a sequence in reverse, with the lower bound greater than the higher bound (e.g., L[3:1] )? Hint: try assigning to this slice ( L[3:1]=['?'] ), and see where the value is put. Do you think this may be the same phenomenon you saw when slicing out of bounds?

In [ ]:

3. Indexing, slicing and del

Define another list L with four items, and assign an empty list to one of its offsets (e.g., L[2]=[] ). What happens? Then, assign an empty list to a slice ( L[2:3]=[] ). What happens now? Recall that slice assignment deletes the slice and inserts the new value where it used to be. The del statement deletes offsets, keys, attributes, and names. Use it on your list to delete an item (e.g., del L[0] ). What happens if you delete an entire slice ( del L[1:] )? What happens when you assign a nonsequence to a slice ( L[1:2]=1 )?


In [ ]:

4. Tuple assignment

What do you think is happening to X and Y when you run following sequence?


In [ ]:
X = 'spam'
Y = 'eggs'
X, Y = Y, X

5. Dictionary keys.

You’ve learned that dictionaries aren’t accessed by offsets, so what’s going on here?


In [ ]:
D = {}
D[1] = 'a'
D[2] = 'b'
D

Does the following shed any light on the subject? (Hint: strings, integers, and tuples share which type category?)


In [ ]:
D[(1, 2, 3)] = 'c'
D

6. Dictionary indexing.

Create a dictionary named D with three entries, for keys 'a' , 'b' , and 'c' . What happens if you try to index a nonexistent key ( D['d'] )? What does Python do if you try to assign to a nonexistent key 'd' (e.g., D['d']='spam' )? How does this compare to out-of-bounds assignments and references for lists? Does this sound like the rule for variable names?


In [ ]:

7. Generic operations.

Run interactive tests to answer the following questions:

  • What happens when you try to use the + operator on different/mixed types (e.g., string + list, list + tuple)?
  • Does + work when one of the operands is a dictionary?
  • Does the append method work for both lists and strings? How about using the keys method on lists? (Hint: what does append assume about its subject object?)
  • Finally, what type of object do you get back when you slice or concatenate two lists or two strings?

In [ ]:

8. String indexing.

Define a string S of four characters: S = "spam" . Then type the following expression: S[0][0][0][0][0] . Any clue as to what’s happening this time? (Hint: recall that a string is a collection of characters, but Python characters are one-character strings.) Does this indexing expression still work if you apply it to a list such as ['s', 'p', 'a', 'm'] ? Why?


In [ ]:

9. Immutable types.

Define a string S of four characters again: S = "spam" . Write an assignment that changes the string to "slam" , using only slicing and concatenation. Could you perform the same operation using just indexing and concatenation? How about index assignment?


In [ ]:

10. Nesting.

Write a data structure that represents your personal information: name (first, middle, last), age, job, address, email address, and phone number. You may build the data structure with any combination of built-in object types you like (lists, tuples, dictionaries, strings, numbers). Then, access the individual components of your data structures by indexing. Do some structures make more sense than others for this object?


In [ ]:

11. Files

Write a script that creates a new output file called myfile.txt and writes the string "Hello file world!" into it. Then write another script that opens my- file.txt and reads and prints its contents. Does the new file show up in the directory where you ran your scripts? What if you add a different directory path to the filename passed to open ? Note: file write methods do not add newline characters to your strings; add an explicit \n at the end of the string if you want to fully terminate the line in the file.


In [ ]:


In [ ]:


In [ ]:
!ls

Test your knowledge: Part III exercises

1. Coding basic loops

  • Write a for loop that prints the ASCII code of each character in a string named S. Use the built-in function ord(character) to convert each character to an ASCII integer. This function technically returns a Unicode code point in Python 3.X, but if you restrict its content to ASCII characters, you’ll get back ASCII codes. (Test it interactively to see how it works.)
  • Next, change your loop to compute the sum of the ASCII codes of all the characters in a string.
  • Finally, modify your code again to return a new list that contains the ASCII codes of each character in the string. Does the expression map(ord, S) have a similar effect? How about [ord(c) for c in S] ? Why? (Hint: see Chapter 14.)

In [ ]:

2. Backslash characters

What happens on your machine when you type the following code interactively?


In [ ]:
for i in range(5):
    print('hello %d\n\a' % i, end="")

3. Sorting dictionaries.

In Chapter 8, we saw that dictionaries are unordered collections. Write a for loop that prints a dictionary’s items in sorted (ascending) order. (Hint: use the dictionary keys and list sort methods, or the newer sorted built-in function.)


In [ ]:

4. Program logic alternatives.

Consider the following code, which uses a while loop and found flag to search a list of powers of 2 for the value of 2 raised to the fifth power (32).

L = [1, 2, 4, 8, 16, 32, 64]
X = 5

found = False
i = 0
while not found and i < len(L):
    if 2 ** X == L[i]:
        found = True
    else:
        i = i+1

if found:
    print('at index', i)
else:
    print(X, 'not found')

As is, the example doesn’t follow normal Python coding techniques. Follow the steps outlined here to improve it:

  • First, rewrite this code with a while loop else clause to eliminate the found flag and final if statement.
  • Next, rewrite the example to use a for loop with an else clause, to eliminate the explicit list-indexing logic. (Hint: to get the index of an item, use the list index method— L.index(X) returns the offset of the first X in list L .)
  • Next, remove the loop completely by rewriting the example with a simple in operator membership expression. (See Chapter 8 for more details, or type this to test: 2 in [1,2,3] .)
  • Finally, use a for loop and the list append method to generate the powers-of-2 list ( L ) instead of hardcoding a list literal.

In [ ]:

Deeper thoughts:

  • Do you think it would improve performance to move the 2 ** X expression outside the loops? How would you code that?
  • As we saw in exercise 1, Python includes a map(function, list) tool that can generate a powers-of-2 list, too: map(lambda x: 2 ** x, range(7)). Try typing this code interactively; we’ll meet lambda more formally in the next part of this book, especially in Chapter 19. Would a list comprehension help here (see Chapter 14)?

In [ ]:

5. Code maintenance.

If you haven’t already done so, experiment with making the code changes suggested in this chapter’s sidebar “Changing PyDoc’s Colors” on page 456. Much of the work of real software development is in changing existing code, so the sooner you begin doing so, the better. For reference, my edited copy of PyDoc is in the book’s examples package, named mypydoc.py; to see how it differs, you can run a file compare (fc on Windows) with the original pydoc.py in 3.3 (also included, lest it change radically in 3.4 as the sidebar describes). If PyDoc is more easily customized by the time you read these words, customize colors per its current convention instead; if this involves changing a CSS file, let’s hope the procedure will be well documented in Python’s manuals.

Test Your Knowledge: Part IV Exercises

1. The basics.

At the Python interactive prompt, write a function that prints its single argument to the screen and call it interactively, passing a variety of object types: string, integer, list, dictionary. Then, try calling it without passing any argument. What happens? What happens when you pass two arguments?


In [ ]:

2. Arguments.

Write a function called adder in a Python module file. The function should accept two arguments and return the sum (or concatenation) of the two. Then, add code at the bottom of the file to call the adder function with a variety of object types (two strings, two lists, two floating points), and run this file as a script from the system command line. Do you have to print the call statement results to see results on your screen?


In [ ]:

3. varargs.

Generalize the adder function you wrote in the last exercise to compute the sum of an arbitrary number of arguments, and change the calls to pass more or fewer than two arguments. What type is the return value sum? (Hints: a slice such as S[:0] returns an empty sequence of the same type as S , and the type built- in function can test types; but see the manually coded min examples in Chapter 18 for a simpler approach.) What happens if you pass in arguments of different types? What about passing in dictionaries?


In [ ]:

4. Keywords.

Change the adder function from exercise 2 to accept and sum/concatenate three arguments: def adder(good, bad, ugly). Now, provide default values for each argument, and experiment with calling the function interactively. Try passing one, two, three, and four arguments. Then, try passing keyword arguments. Does the call adder(ugly=1, good=2) work? Why? Finally, generalize the new adder to accept and sum/concatenate an arbitrary number of keyword arguments. This is similar to what you did in exercise 3, but you’ll need to iterate over a dictionary, not a tuple. (Hint: the dict.keys method returns a list you can step through with a for or while , but be sure to wrap it in a list call to index it in 3.X; dict.values may help here too.)


In [ ]:

5. Dictionary tools.

Write a function called copyDict(dict) that copies its dictionary argument. It should return a new dictionary containing all the items in its argument. Use the dictionary keys method to iterate (or, in Python 2.2 and later, step over a dictionary’s keys without calling keys ). Copying sequences is easy ( X[:] makes a top-level copy); does this work for dictionaries, too? As explained in this exercise’s solution, because dictionaries now come with similar tools, this and the next exercise are just coding exercises but still serve as representative function examples.


In [ ]:

6. Dictionary tools.

Write a function called addDict(dict1, dict2) that computes the union of two dictionaries. It should return a new dictionary containing all the items in both its arguments (which are assumed to be dictionaries). If the same key appears in both arguments, feel free to pick a value from either. Test your function by writing it in a file and running the file as a script. What happens if you pass lists instead of dictionaries? How could you generalize your function to handle this case, too? (Hint: see the type built-in function used earlier.) Does the order of the arguments passed in matter?


In [ ]:

7. More argument-matching examples.


In [ ]:
def f1(a, b): print(a, b)    # Normal args
def f2(a, *b): print(a, b)   # Positional varargs
def f3(a, **b): print(a, b)  # Keyword varargs
def f4(a, *b, **c): print(a, b, c)  # Mixed modes
def f5(a, b=2, c=3): print(a, b, c) # Defaults
def f6(a, b=2, *c): print(a, b, c)  # Defaults and positional varargs

Test the following calls interactively, and try to explain each result; in some cases, you’ll probably need to fall back on the matching algorithm shown in Chapter 18. Do you think mixing matching modes is a good idea in general? Can you think of cases where it would be useful?


In [ ]:
f1(1, 2)

In [ ]:
f1(b=2, a=1)

In [ ]:
f2(1, 2, 3)

In [ ]:
f3(1, x=2, y=3)

In [ ]:
f4(1, 2, 3, x=2, y=3)

In [ ]:
f5(1)

In [ ]:
f5(1, 4)

In [ ]:
f6(1)

In [ ]:
f6(1, 3, 4)

8. Primes revisited.

Recall the following code snippet from Chapter 13, which simplistically determines whether a positive integer is prime:

x = y // 2                         # For some y > 1
while x > 1:
    if y % x == 0:                 # Remainder
        print(y, 'has factor', x)
        break                      # Skip else
    x -= 1
else:                              # Normal exit
    print(y, 'is prime')

Package this code as a reusable function, add some calls to the function. While you’re at it, experiment with replacing the first line’s // operator with / to see how true division changes the / operator in Python 3.X and breaks this code (refer back to Chapter 5 if you need a reminder). What can you do about negatives, and the values 0 and 1 ? How about speeding this up? Your outputs should look something like this:

13 is prime
13.0 is prime
15 has factor 5
15.0 has factor 5.0

In [ ]:

9. Iterations and comprehensions.

Write code to build a new list containing the square roots of all the numbers in this list: [2, 4, 9, 16, 25]. Code this as a for loop first, then as a map call, then as a list comprehension, and finally as a generator expression. Use the sqrt function in the built-in math module to do the calculation (i.e., import math and say math.sqrt(x) ). Of the four, which approach do you like best?


In [ ]:

10. Timing tools.

In Chapter 5, we saw three ways to compute square roots: math.sqrt(X) , X ** .5 , and pow(X, .5) . If your programs run a lot of these, their relative performance might become important. To see which is quickest, repurpose the timerseqs.py script we wrote in this chapter to time each of these three tools. Use the bestof or bestoftotal functions in one of this chapter’s timer modules to test (you can use either the original, the 3.X-only keyword-only variant, or the 2.X/ 3.X version, and may use Python’s timeit module as well). You might also want to repackage the testing code in this script for better reusability—by passing a test functions tuple to a general tester function, for example (for this exercise a copy- and-modify approach is fine). Which of the three square root tools seems to run fastest on your machine and Python in general? Finally, how might you go about interactively timing the speed of dictionary comprehensions versus for loops?


In [ ]:

11. Recursive functions.

Write a simple recursion function named countdown that prints numbers as it counts down to zero. For example, a call countdown(5) will print: 5 4 3 2 1 stop. There’s no obvious reason to code this with an explicit stack or queue, but what about a nonfunction approach? Would a generator make sense here?


In [ ]:

12. Computing factorials.

Finally, a computer science classic (but demonstrative nonetheless). We employed the notion of factorials in Chapter 20’s coverage of permutations: N! , computed as N*(N-1)*(N-2)*...1 . For instance, 6! is 6*5*4\3\2*1 , or 720 . Code and time four functions that, for a call fact(N) , each return N! . Code these four functions (1) as a recursive countdown per Chapter 19; (2) using the functional reduce call per Chapter 19; (3) with a simple iterative counter loop per Chapter 13; and (4) using the math.factorial library tool per Chapter 20. Use Chapter 21’s timeit to time each of your functions. What conclusions can you draw from your results?


In [ ]: