By Simon Bluck
Table of Contents
The sessions now broadly follow the standard tutorial, but in a very condensed way. You are very much advised to refer to the tutorial for a fuller coverage of the language; and indeed, to the full language reference for complete coverage. Some of the examples shown here are taken from the tutorial.
This session adds to what was learnt from Session 2: Syntax and Constructs, and goes on to cover some further language constructs.
Please feel free to seek assistance with understanding Python, particularly with the topics covered in the course sessions.
Note: In the notes for this session, where the description splits into two columns, the right column shows examples. E.g.:
|Expression||a + b * (c + d)|
Where the description splits into three columns, the middle column shows examples and the right column shows results of evaluation of the examples, e.g.:
|Expression evaluation||2 + 3 / (4 + 6)||2.3|
|2 ** 9||512|
More on strings
Strings are a fundamental aspect of programming languages and here we cover them in more detail.
Strings are immutable: you can’t modify a string object. So you have to create a new string object if you want a modified version of an existing string.
|Multiplying||“abc” * 3||‘abcabcabc’|
|“abc” * “abc”||Error|
|“abc” + “abc”||‘abcabc’|
|s = “xyz” ; “abc” + s||“abcxyz”|
|s = “xyz” ; “abc” s||Error|
|Negative indexes||‘abcxyz’ [-2]||‘y’|
|Any object as a string||str(44.4)||‘44.4’|
A string is an object, and has (a lot of) methods (functions) that can be applied to a string. It’s useful to know them! (BTW there is no built-in function to replace a slice of a string.)
A string is a sequence type and so is a list, so there are operations which act equivalently on them. So the table above also works replacing the strings with lists. Except that you can’t concatenate by having adjacent lists.
Unlike strings, lists are mutable. So whereas you cannot modify a string, you can modify a list, e.g.:
|cubes = [1, 8, 27, 65, 125]||# A list of cubes.|
|cubes = 64||# Get it right!|
|cubes.append(6**3)||# Add another cube.|
|cubes.extend([7**3, 8**3])||# Add a couple more cubes.|
None of the three list modifications are allowed for strings. Be aware that if you modify an object then all references to that object will, of course, see that change.
Example loop generating prime numbers. Shows nested loops, break statement and else part. The else part is executed if the break isn’t taken, i.e. when the loop completes all its iterations.
for n in range(2, 10): for x in range(2, n): if n % x == 0: print(n, 'equals', x, '*', n//x) break else: # No "break" occurred. # loop fell through without finding a factor print(n, 'is a prime number')
2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3
The general form of the range() function is: range(start, end, increment)
A continue statement can be used to skip to the next iteration of the loop, e.g.:
for num in range(2, 10): if num % 2 == 0: print("Found an even number", num) continue print("Found a number", num)
A function is a fundamental concept in programming languages, borrowed from mathematics, e.g. the sine function “sin(angle)”. It allows an algorithm to be encapsulated so that it can be executed from different places in a program. A function can have parameters which are passed when it is invoked, e.g. a parameter specifying an angle value. A function can contain any amount of code. A python function always returns a value (which is None if there is no explicit return statement).
def fib(n): """Return a list containing the Fibonacci series up to n.""" result =  # An empty list a, b = 0, 1 while a < n: result.append(a) # Append to the list a, b = b, a+b return result # Return the list # Now call the function we just defined: print(*fib(2000)) # Call it and print the list returned. [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]
Or, use the
* expansion operator to expand out (“unpack”) the list:
print(*fib(2000)) # Call it and print the contents of the list returned. [0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597]
• Function parameters can have default values.
• You can identify parameters by name when calling a function.
• You don’t have to pass values for every parameter.
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while True: ok = input(prompt) if ok in ('y', 'ye', 'yes'): return True if ok in ('n', 'no', 'nop', 'nope'): return False retries = retries - 1 if retries < 0: raise OSError('uncooperative user') print(complaint)
This function can be called in several ways:
ask_ok('Really quit?', 2) # Rely on default for last parameter. ask_ok('OK to overwrite the file?', complaint='Come on, only yes or no!') # Rely on default for last parameter. ask_ok() # Error because "prompt" has no value.
A typical, and good, layout for a Python module has essentially all the code contained within functions, even resulting in some functions that are only called once. It helps to encapsulate distinct functionality and clarify the operation of the overall module.
A disk file consists simply of a sequence of bytes. The file is automatically extended as you write more bytes.
You can input data (text or bytes) from a file or from stdin (the text window you are running your program in).
You can output data (text or bytes) to a file or to stdout (the text window you are running your program in).
You have to open a file for reading, or for writing, before you read or write data from or t that file. And you have to close it afterwards.
There are general functions available to format the data you write, and to help interpret the data you read.
You can do line-oriented reading/writing, or just read/write text/bytes without regard for a line structure.
There are facilities for writing and reading Python objects to a dedicated file, using json. Those facilities serialise and deserialise the objects. Serialisation records the object data, and its structure, in a way that deserialisation can reconstruct the original object.
Operations on files should always be checked for success, and failures handled and reported appropriately. File operations can so easily fail – e.g. you don’t have permission to read/write the file, or you’ve run out of disk space.
Create a file called Fred in the current directory. The file is opened for writing. If the file already exists, it will simply be overwritten. The open call returns a “file object” :-
fout = open('Fred', 'w')
Write some text to the file (
w opens it for writing):
fout.write('This is some text\n') fout.write('with a number: ' + str(42) + '\n')
Close the file:
Open the file for reading; read the data; and close the file:
fin = open('Fred') # Second parameter defaults to 'r'. fredsdata = fin.read() # Read all the data. fin.close()
You can instead read successive lines from a file using the readline() method. When readline() ultimately returns an empty string, that indicates you’ve reached the end of the file. E.g.:
while 1: line = fin.readline() if line == '': break print(line, end='')
Or, nicely, you can regard the file object as an iterator and do:
for line in fin: print(line, end='')
File as a Database
You can use a file as a database, updating parts of it:
# Create an empty file called dbs, failing if it already exists: fdbs = open('dbs', 'x') # 'x' means exclusive creation. fdbs.close() ... fdbs = open('dbs', 'r+b') # binary (bytes) read/write. fdbs.seek(0) # Position at start of file. fdbs.write(b'just sum text') fdbs.seek(5) # Position after the first 5 bytes fdbs.write(b'some text') fdbs.seek(0) print(fdbs.read())
And that outputs:
b'just some text'
Note that print and write are fairly similar in what they achieve. And print has a file argument so you can write:
print (b'some text', file=fdbs)
But print doesn’t print data as bytes: it prints them as Unicode characters. So it doesn’t work for writing to a binary file.
Next -> Part Six: Functions and More