PYW.png 9a: file handling, list comprehensions and OOP1

Table of Contents

1. In-class assignment

1.1. Q1

# In morse code, each letter in the alphabet is represented by a
# combination of short and long signals. For this exercise we use "*"
# for short and "-" for long signals, and we use "." to separate
# characters.
#
# Here is an example message in Morse code:

code = "-*-*.---.-*.--*.*-*.*-.-.**-.*-**.*-.-.**.---.-*.***.*----*.-*--.---.**-.*----*.-*-*.*-*.*-.-*-*.-*-.*.-**.*----*.-.****.*.*----*.-*-*.---.-**.*"

# Complete the definition of the function "morse_split" below, that
# takes a morse code string like the one above as input, and returns
# a list of separate Morse encodings (characters).

def morse_split(s):
    return s.split(".") # Return a list of Morse encodings (characters) instead
print(morse_split(code))

1.2. Q2

# To decode a message, we can use the following Morse symbol table:

code = "-*-*.---.-*.--*.*-*.*-.-.**-.*-**.*-.-.**.---.-*.***.*----*.-*--.---.**-.*----*.-*-*.*-*.*-.-*-*.-*-.*.-**.*----*.-.****.*.*----*.-*-*.---.-**.*"

def morse_split(s):
    return s.split(".") # Return a list of Morse encodings (characters) instead

morse_dict = {'*-': 'a', '-***': 'b', '-*-*': 'c', '-**': 'd',
              '*': 'e', '**-*': 'f', '--*': 'g', '****': 'h',
              '**': 'i', '*---': 'j', '-*-': 'k', '*-**': 'l', 
              '--': 'm', '-*': 'n', '---': 'o', '*--*': 'p', 
              '--*-': 'q', '*-*': 'r', '***': 's', '-': 't',
              '**-': 'u', '***-': 'v', '*--': 'w', '-**-': 'x',
              '-*--': 'y', '--**': 'z', '*----*': '-' }

# You can use this dictionary from within the function morse_translate
# below. Its input will be a message in Morse encoding. The function
# should decode the message (you may use your previous function if you
# want), and then return a string containing the decoded message.

def morse_translate(s):
    new_string = ""
    var1 = morse_split(s)
    for char in var1:
        new_string += morse_dict[char]
    return new_string
print(morse_translate(code))

2. Writing files

Earlier we saw that we can read files like this:

with open("example.txt", "r") as finput: 
    lines = finput.readlines()

for i in lines:
    print(i)

Here finput is called "the file handle".

(By the way, in the past I have used "file" instead of "finput". In principle you can pick any name (to store the so-called "file handle"), as long as it is not already "taken" by Python (like for, in, while, return etc.)).

Let us now look at how we can write to text files as well:

2.1. Write and append to text file

Writing to a file:

with open("example.txt","w") as finput:
    finput.write("this is the output of a python script")

N.B.: The file-handle is only open (and therefore open to edits) within the "with open()"-block. So this does not work:

with open("example.txt","w") as finput:
    finput.write("this is the output of a python script")

finput.write("\nthis is yet another new line")

By the way this also goes for reading files:

with open("example.txt","r") as finput:
    var = finput.read()

print(var)

If you open a file in the "writing" mode ("w") and then use .write you will overwrite whatever was inside the file.

with open("example.txt","w") as file:
    file.write("this is replacing the output of the previous python script")

But within the "with"-block you can use the .write method multiple time without replacing what you have already written…

with open("example.txt","w") as finput:
    finput.write("test1\n")
    finput.write("test2")

… but if you leave the "with"-block and open the file again, any usage of .write will replace whatever was inside the file.

with open("example.txt","w") as file:
    file.write("test1")

with open("example.txt","w") as file:
    file.write("test2")

If you want to add to the content of an existing file (rather than overwrite/replace it), you can use .append

with open("example.txt","w") as file:
    file.write("test1")

with open("example.txt","a") as finput:
    for i in range(5):
        finput.write("\nthis is additional output of a python script")

3. Formatting text output

In class 7b we saw two ways to include variables into a string:

var1 = "Nil"

var2 = [1, 2, 4]

print("Showing %s and %s how to include variables in print statements" % (var1, var2))

print(f"Showing {var1} and {var2} how to include bla bla ...")

The second way of including variables in a string allows for a few extra tricks.

In the example below "width" stands for the minimal width that will be occupied by the value that you want to print. If the value itself does not require that full width, the rest will be filled up with whitespace:

f"{value:width.precision}"
value1 = 10
value2 = 20
print(f"Value 1 is {value1:5} and value 2 is {value2:5}")

You can also change the alignment of the text within that minimal width:

value1 = 10
value2 = 20
print(f"Value 1 is {value1:5} and value 2 is {value2:5}") # By default integers are outlined to the right
print(f"Value 1 is {value1:<5} and value 2 is {value2:<5}") # With the < and > we can change the alignment.

# In both cases, the integer + whitepsace takes up the same width.

When printing floats, there is also this trick:

pi = 3.14159

print(f"{pi:.2f} is an approximation of pi.")
print(f"{pi:10.2f} is an approximation of pi.")
print(f"{pi:<10.3f} is an approximation of pi.")

We can use these tricks to format the text we write to files:

with open("christmastree.txt", "w") as file:
  for i in range(10):
      output1 = i * "a"
      output2 = i * "b"
      file.write(f"{output1:>12}{output2:<12}\n")

4. List comprehensions

You all know what this does:

lst = []
for i in "string":
    lst.append(i)

print(lst)    

There is another way to write (do) this in Python that you might come across:

lst = [i for i in "string"]
print(lst)

"The bracket operators indicate that we are constructing a new list. The expression inside the brackets specifies the elements of the list, and the for clause (for-loop) indicates what sequence we are traversing" (Downey, 2015, p. 184-185).

  • Advantage: simpler code, faster
  • Disadvantage: harder to debug, because you cannot add a print statement to the for-loop

You can make adjustments to the elements of the list:

lst = [i*2 for i in "string example"]
print(lst)

You can also add an if-statement to filter the content of a collection:

lst = [i for i in range(20) if i % 7 == 0]
print(lst)
lst = []
for i in range(20):
    if i % 7 == 0:
      print(i)
      lst.append(i)
print(lst)

5. Object Oriented Programming

"We have used many of Python’s built-in types; now we are going to define a new type."

"As an example, we will create a type called Point that represents a point in two-dimensional space."

"A programmer-defined type is also called a class. A class definition looks like this:"

class Point:
    """Represents a point in 2-D space."""

(By the way 1: the string between """ is called the docstring. It's good practice to briefly describe what class definitions (and functions!) do in such a docstring!)

(By the way 2: variable names for classes are conventionally written as CamelCase).

5.1. Instances of classes

Once you have defined a class, you can create instances of that class:

class Point:
    """Represents a point in 2-D space."""

p1 = Point()
print(p1)

"Every object is an instance of some class, so 'object' and 'instance' are interchangeable" (Downey, 2015: p. 148).

5.2. Attributes

Why go through all this trouble? Because we can assign attributes and methods to classes and their instances. And this can be useful.

We are assigning values to named elements of an object. These elements are called attributes.

def test_mv():
    """this function prints yes"""
    print("yes")

class Point:
    """Represents a point in 2-D space."""

p1 = Point()

p1.x = 2.0
p1.y = 4.0

You can read the value of an attribute like this:

class Point:
    """Represents a point in 2-D space."""

p1 = Point()

p1.x = 2.0
p1.y = 4.0    

print(p1.x)    

With these simple tricks, you could for instance create a list with points, loop through them, and print their x-coordinates:

class Point:
    """Represents a point in 2-D space."""

p1 = Point()
p2 = Point()
p3 = Point()

p1.x = 1.0
p2.x = 2.0
p3.x = 3.0

p1.y = -2.0
p2.y = -3.0
p3.y = 3

lst = [p1, p2, p3]

for i in lst:
    print(i.x)

5.3. Methods

class Point:
    def move_right(self, i):
        self.x += i

p1 = Point()
p1.x = 0
p1.y = 4

p1.move_right(2)

print(p1.x)

5.4. Initialization

class Point:
    def __init__(self):
        self.x = 0
        self.y = 4

p1 = Point()
print(p1.y)
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p1 = Point(2,2)
print(p1.y)

6. In-class exercises

  1. Create a function that shows the content of a file, asks or user input, and adds the user input to the file.
  2. Vertically flip the "christmass tree" written to a text file above.
  3. Create a list comprehension that stores a list of integers as a list of strings.
  4. Create a class for rectangles. Create two instances each with different values for the attributes "width" and "height".

Created: 2025-04-08 di 13:19