Checks

Variables and expressions

  • Create some variables in the Python shell. What happens if you add spaces, hyphens or other characters to the variable names?

    >>> x = 3
    >>> var fünf = 5
      File "<stdin>", line 1
        var fünf = 5
            ^^^^
    SyntaxError: invalid syntax
    >>> var_fünf = 5
    
  • Do the results change if you use brackets to group numbers in different ways?

    >>> 2 + 3 * 4 - 5 / 6
    13.166666666666666
    >>> (2 + 3) * 4 - 5 / 6
    19.166666666666668
    >>> 2 + (3 * 4) - 5 / 6
    13.166666666666666
    >>> 2 + 3 * (4 - 5) / 6
    1.5
    
  • Which of the following variable and function names do you think are not good Python style, and why?

    var*

    ❌ contains an invalid character (*)

    varname

    ✅ ok, but easier to read with underscore

    func_name()

    varName

    ❌ Mixed upper and lower case letters

    VARNAME

    ❌ Capital letters only, difficult to read

    very_very_long_var_name

    ✅ ok, but very long and therefore only recommended if you want to differentiate between many very similar variables

Numbers

  • Create some number variables (integers, floating point numbers and complex numbers). Experiment a little with what happens when you perform operations with them, even across types.

    >>> x = 3
    >>> import math
    >>> pi = math.pi
    >>> pi
    3.141592653589793
    >>> c = 3j4
      File "<stdin>", line 1
        c = 3j4
             ^
    SyntaxError: invalid imaginary literal
    >>> c = 3 +4j
    >>> c
    (3+4j)
    >>> x * c
    (9+12j)
    >>> x + c
    (6+4j)
    

Complex numbers

  • Load the math module and try out some of the functions. Then load the cmath module and do the same.

    >>> from math import sqrt
    >>> sqrt(3)
    1.7320508075688772
    >>> from cmath import sqrt
    >>> sqrt(3)
    (1.7320508075688772+0j)
    
  • How can you restore the functions of the math module?

    >>> from math import sqrt
    >>> sqrt(3)
    1.7320508075688772
    

Boolean values

  • Decide whether the following statements are true or false:

    • 1 → True

    • 0 → False

    • -1 → True

    • [0] → True (List with one item)

    • 1 and 0 → False

    • 1 > 0 or [] → True

Lists

  • What does len() return for each of the following cases:

    >>> len([3])
    1
    >>> len([])
    0
    >>> len([[1, [2, 3], 4], "5 6"])
    2
    
  • How would you use len() and slices to determine the second half of a list if you don’t know how long it is?

    >>> l = [[1, [2, 3], 4], "5 6"]
    >>> l[len(l) // 2 :]
    ['5 6']
    
  • How could you move the last two entries of a list to the beginning without changing the order of the two?

    >>> l[-2:] + l[:2]
    ['5 6', 7, [1, [2, 3], 4], '5 6']
    
  • Which of the following cases triggers an exception?

    • min(["1", "2", "3"])

    • max([1, 2, "3"])

    • [1,2,3].count("1")

    max([1, 2, "3"]), as strings and integers cannot be compared; it is therefore impossible to obtain a maximum value.

  • If you have a list l, how can you remove a certain value i from it?

    >>> if i in l:
    ...     l.remove(i)
    ...
    

    Note

    This code only removes the first occurrence of i. To remove all occurrences of i from the list, the list could be converted to the set type, for example:

    >>> l = set(l)
    >>> if i in l:
    ...     l.remove(i)
    ...
    >>> l = list(l)
    
  • If you have a nested list ll, how can you get a copy nll of this list in which you can change the elements without changing the contents of ll?

    >>> import copy
    >>> nll = copy.deepcopy(ll)
    
  • Make sure that the object my_collection is a list before you try to append data to it.

    >>> my_collection = []
    >>> if isinstance(my_collection, list):
    ...     print(f"my_collection is a list")
    ...
    my_collection is a list
    
  • What other options could you have besides explicitly checking the type?

Tuples

  • Explain why the following operations cannot be applied to the tuple t:

    • t.append(1)

    • t[2] = 2

    • del t[3]

    All operations attempt to change the tuple t. However, tuples cannot be changed.

  • How can you sort the elements of a tuple?

    >>> sorted(t)
    

Sets

  • How many elements does a set have if it is formed from the following list [4, 2, 3, 2, 1]?

    Four different elements.

Dictionaries

  • Suppose you have the two dictionaries x = {"a":1, "b":2, "c":3, "d":4} and y = {"a":5, "e":6, "f":7}. What would be the content of x after the following code snippets have been executed?

    >>> del x["b"]
    >>> z = x.setdefault("e", 8)
    >>> x.update(y)
    
    >>> x = {"a": 1, "b": 2, "c": 3, "d": 4}
    >>> y = {"a": 5, "e": 6, "f": 7}
    >>> del x["b"]
    >>> z = x.setdefault("e", 8)
    >>> x.update(y)
    >>> x
    {'a': 5, 'c': 3, 'd': 4, 'e': 6, 'f': 7}
    
  • Which of the following expressions can be a key of a dictionary: 1; "Veit"; ("Veit", [1]); [("Veit", [1])]; ["Veit"]; ("Veit", "Tim", "Monique")

    >>> d = {}
    >>> d[1] = None
    >>> d["Veit"] = None
    >>> d[("Veit", [1])]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unhashable type: 'list'
    >>> d[["Veit"]] = None
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unhashable type: 'list'
    >>> d[("Veit", "Tim", "Monique")] = None
    
  • You can use a Dictionary like a spreadsheet table by using Tuples as key row and column values. Write sample code to add and retrieve values.

    >>> sheet = {}
    >>> sheet[("A", 0)] = 1
    >>> sheet[("A", 1)] = 2
    >>> sheet[("B", 0)] = 3
    >>> sheet[("B", 1)] = 4
    >>> print(sheet[("A", 1)])
    2
    

Strings

  • For example, can you add or multiply a string with an integer, a floating point number or a complex number?

    >>> x = 3
    >>> c = 3 + 4j
    >>> snake = "🐍"
    >>> x + snake
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unsupported operand type(s) for +: 'int' and 'str'
    >>> x * snake
    '🐍🐍🐍'
    >>> c + snake
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unsupported operand type(s) for +: 'complex' and 'str'
    >>> c * snake
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can't multiply sequence by non-int of type 'complex'
    

Operators and functions

  • Which of the following strings cannot be converted into numbers and why?

    >>> int("1e2")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: invalid literal for int() with base 10: '1e2'
    >>> int(1e+2)
    100
    >>> int("1+2")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: invalid literal for int() with base 10: '1+2'
    >>> int("+2")
    2
    
  • How can you change a heading such as variables and expressions so that it contains hyphens instead of spaces and can therefore be better used as a file name?

    >>> ve = "variables and expressions"
    >>> "-".join(ve.split())
    'variables-and-expressions'
    
  • If you want to check whether a line begins with .. note::, which method would you use? Are there any other options?

    >>> x.startswith(".. note::")
    True
    >>> x[:9] == ".. note::"
    True
    
  • Suppose you have a string with exclamation marks, quotation marks and line breaks. How can these be removed from the string?

    >>> hipy = "„Hello Pythonistas!“\n"
    >>> hipy.strip("„“!\n")
    'Hello Pythonistas'
    
  • How can you change all spaces and punctuation marks from a string to a hyphen (-)?

    >>> from string import punctuation, whitespace
    >>> chars = punctuation + whitespace
    >>> subs = str.maketrans(chars, len(chars) * "-")
    >>> hipy = "Hello Pythonistas!\n"
    >>> hipy.translate(subs)
    'Hello-Pythonistas--'
    

re

  • Which regular expression would you use to find strings that represent the numbers between -3 and +3?

    r"-?[0-3]" or r"-{0,1}[0-3]"

    ?

    is a quantifier for one or no occurrence.

  • Which regular expression would you use to find hexadecimal values?

    r"0[xX][0-9a-fA-F]+"

    corresponds to an expression starting with 0, followed by a lower or upper case x, followed by one or more characters in the ranges 0-9, a-f or A-F.

input()

  • How can you get string and integer values with the input() function?

    >>> year_birth = input("Geburtsjahr: ")
    Geburtsjahr: 1964
    >>> type(year_birth)
    <class 'str'>
    >>> year_birth = int(input("Geburtsjahr: "))
    Geburtsjahr: 1964
    >>> type(year_birth)
    <class 'int'>
    
  • What is the effect if you do not use int() to call input() for integer inputs?

    >>> import datetime
    >>> current = datetime.datetime.now()
    >>> year = current.year
    >>> year_birth = input("Geburtsjahr? ")
    Geburtsjahr? 1964
    >>> age = year - year_birth
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unsupported operand type(s) for -: 'int' and 'str'
    
  • Can you change the code so that it accepts a floating point number?

    >>> import datetime
    >>> current = datetime.datetime.now()
    >>> year = current.year
    >>> year_birth = float(input("Geburtsjahr: "))
    Geburtsjahr: 1964
    >>> type(year_birth)
    <class 'float'>
    
  • What happens if you enter an incorrect value type?

    >>> import datetime
    >>> current = datetime.datetime.now()
    >>> year = current.year
    >>> year_birth = int(input("Geburtsjahr: "))
    Geburtsjahr: Schaltjahr
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: invalid literal for int() with base 10: 'Schaltjahr'
    
  • Write the code to ask for the names and ages of three users. After the values have been entered, ask for one of the names and output the corresponding age.

    >>> personal_data = {}
    >>> for i in range(3):
    ...     name = input("Name? ")
    ...     age = int(input("Age? "))
    ...     personal_data[name] = age
    ...
    Name? Veit
    Age? 60
    Name? Tim
    Age? 35
    Name? Monique
    Age? 37
    >>> who = input("Who? ")
    Who? Veit
    >>> print(personal_data[who])
    60
    

Loops

  • Removes all negative numbers from the list x = [ -2, -1, 0, 1, 2, 3].

    >>> x = [-2, -1, 0, 1, 2, 3]
    >>> pos = []
    >>> for i in x:
    ...     if i >= 0:
    ...         pos.append(i)
    ...
    >>> pos
    [0, 1, 2, 3]
    
  • Which list comprehension would you use to achieve the same result?

    >>> x = [-2, -1, 0, 1, 2, 3]
    >>> pos = [i for i in x if i >= 0]
    >>> pos
    [0, 1, 2, 3]
    
  • How would you count the total number of negative numbers in the list [-[1, 0, 1], [-1, 1, 3], [-2, 0, 2]]?

    >>> x = [[-1, 0, 1], [-1, 1, 3], [-2, 0, 2]]
    >>> neg = 0
    >>> for row in x:
    ...     for col in row:
    ...         if col < 0:
    ...             neg += 1
    ...
    >>> neg
    3
    
  • Creates a generator that only returns odd numbers from 1 to 10.

    Tip

    A number is odd if there is a remainder when it is divided by 2, in other words if % 2 is true.

    >>> x = (x for x in range(10) if x % 2)
    >>> for i in x:
    ...     print(i)
    ...
    1
    3
    5
    7
    9
    
  • Write a dict with the edge lengths and volumes of cubes.

    >>> {x: x**3 for x in range(1, 5)}
    {1: 1, 2: 8, 3: 27, 4: 64}
    

Exceptions

  • Write code that receives two numbers and divides the first number by the second. Check if the ZeroDivisionError occurs when the second number is 0 and catch it.

    >>> x = int(input("Please enter an integer: "))
    Please enter an integer: 7
    >>> y = int(input("Please enter an integer: "))
    Please enter an integer: 6
    >>> try:
    ...     z = x / y
    ... except ZeroDivisionError as e:
    ...     print("It cannot be divided by 0!")
    ...
    >>> z
    1.1666666666666667
    >>> y = int(input("Please enter an integer: "))
    Please enter an integer: 0
    >>> try:
    ...     print("It cannot be divided by 0!")
    ... except ZeroDivisionError as e:
    ...     print("It cannot be divided by 0!")
    ...
    It cannot be divided by 0!
    
  • If MyError inherits from Exception, what is the difference between except Exception as e and except MyError as e?

    The first catches every exception that inherits from Exception, while the second only catches MyError exceptions.

  • Write a simple program that receives a number and then uses the assert() statement to throw an Exception if the number is 0.

    >>> x = int(input("Please enter an integer that is not zero: "))
    Please enter an integer that is not zero: 0
    >>> assert x != 0, "The integer must not be zero."
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AssertionError: The integer must not be zero.
    
  • Write a user-defined exception outliers that throws an Exception if the variable x is greater or less than 3?

    >>> class Outliers(Exception):
    ...     pass
    ...
    >>> x = -4
    >>> if abs(x) > 3:
    ...     raise Outliers(f"The value {x} is an outlier")
    ...
    Traceback (most recent call last):
      File "<stdin>", line 2, in <module>
    Outliers: The value -4 is an outlier
    
  • Is checking whether an object is a list (Check: Listen) programming in the style of LBYL or EAFP?

    This is LBYL programming. Only when you add append() to a try... except block and catch TypeError exceptions does it become a bit more EAFP.

Parameters

  • Write a function that can take any number of unnamed arguments and output their values in reverse order?

    >> def my_func(*params):
    ...     for i in reversed(params):
    ...         print(i)
    ...
    >>> my_func(1, 2, 3, 4)
    4
    3
    2
    1
    

Variables

  • Assuming x = 1, func() sets the local variable x to 2 and gfunc() sets the global variable x to 3, what value does x assume after func() and gfunc() have been run through?

    >>> x = 1
    >>> def func():
    ...     x = 2
    ...
    >>> def gfunc():
    ...     global x
    ...     x = 3
    ...
    >>> func()
    >>> x
    1
    >>> gfunc()
    >>> x
    3
    

Modules

  • If you have created a my_math module that contains a divide() function, what options are there for importing this function and then using it? What are the advantages and disadvantages of each option?

    >>> import my_math
    >>> my_math.divide(..., ...)
    
    >>> from my_math import divide
    >>> divide(..., ...)
    

    The first solution is often favoured as there will be no conflict between the identifiers in my_math and the importing namespace. However, this solution is a little more complex.

  • A variable min is contained in the scope.py module. In which of the following contexts can min be used?

    1. With the module itself

    2. Within the scope() function of the module

    3. Within a script that has imported the scope.py module

    1. and 2. but not 3.

  • Pack the functions that you created at the end of Decorators as an independent module. The functions should initially only be fully usable from another script.

    example_mod.py
    from functools import wraps
    
    
    def my_decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            """Wrapper docstring"""
            print("Call decorated function")
            return f(*args, **kwargs)
    
        return wrapper
    
    
    @my_decorator
    def example():
        """Example docstring"""
        print("Call example function")
    
    my_script.py
    from example_mod import example
    
    print(example.__name__)
    print(example.__doc__)
    
  • Make your module executable.

    --- /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/appendix/example_mod.py
    +++ /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/appendix/example_mod2.py
    @@ -15,3 +15,8 @@
     def example():
         """Example docstring"""
         print("Call example function")
    +
    +
    +if __name__ == "__main__":
    +    print(example.__name__)
    +    print(example.__doc__)
    
  • Rewrite your version of the wc utility so that it implements both the distinction between bytes and characters and the ability to read from files and from standard input.

    --- /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/modules/wcargv.py
    +++ /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/modules/wcargv_stdin.py
    @@ -1,27 +1,50 @@
    -"""wc module. Contains function: words_occur()"""
    +"""Reads a file or stdin and returns the number of lines, words and characters –
    +   similar to the UNIX wc utility."""
     
     import sys
     
     
    -def words_occur():
    -    """words_occur() - count the occurrences of words in a file."""
    -    # Prompt user for the name of the file to use.
    -    file_name = sys.argv.pop()
    -    # Open the file, read it and store its words in a list.
    -    with open(file_name, "r") as f:
    -        word_list = f.read().split()
    -    # Count the number of occurrences of each word in the file.
    -    occurs_dict = {}
    -    for word in word_list:
    -        # increment the occurrences count for this word
    -        occurs_dict[word] = occurs_dict.get(word, 0) + 1
    -    # Print out the results.
    -    print(
    -        f"File {file_name} has {len(word_list)} words, "
    -        f"{len(occurs_dict)} are unique:"
    -    )
    -    print(occurs_dict)
    +def main():
    +    """Count the occurrences of lines, words and characters in a file or
    +    stdin."""
    +    # initialize counts
    +    line_count = 0
    +    word_count = 0
    +    char_count = 0
    +    filename = None
    +    option = None
    +    if len(sys.argv) > 1:
    +        params = sys.argv[1:]
    +        if params[0].startswith("-"):
    +            # If there are several parameters, the first one is taken as an option
    +            option = params.pop(0).lower().strip()
    +        if params:
    +            filename = params[0]
    +    file_mode = "r"
    +    if option == "-c":
    +        file_mode = "rb"
    +    if filename:
    +        infile = open(filename, file_mode)
    +    else:
    +        infile = sys.stdin
    +    with infile:
    +        for line in infile:
    +            line_count += 1
    +            char_count += len(line)
    +            words = line.split()
    +            word_count += len(words)
    +    if option in ("-c", "-m"):
    +        print(f"{filename} has {char_count} characters.")
    +    elif option == "-w":
    +        print(f"{filename} has {word_count} words.")
    +    elif option == "-l":
    +        print(f"{filename} has {line_count} lines.")
    +    else:
    +        # print the answers using the format() method
    +        print(
    +            f"{filename} has {line_count} lines, {word_count} words and {char_count} characters."
    +        )
     
     
     if __name__ == "__main__":
    -    words_occur()
    +    main()
    

Classes

  • Write a Triangle class that can also calculate the area.

    class Triangle:
        def __init__(self, width, height):
            self.width = width
            self.height = height
    
        def area(self):
            return 0.5 * self.width * self.height
    

Methods

  • Write a class method that is similar to circumferences(), but returns the total area of all circles.

    def area(self):
        return self.diameter**2 / 4 * self.__class__.pi
    
    
    @classmethod
    def areas(cls):
        """Class method to sum all areas."""
        careasum = 0
        for c in cls.circles:
            careasum = careasum + c.area()
        return careasum
    

Classes and inheritance

  • Rewrites the code for a Triangle class so that it inherits from Form.

    >>> class Form:
    ...     def __init__(self, x=0, y=0):
    ...         self.x = x
    ...         self.y = y
    ...
    >>> class Triangle(Form):
    ...     def __init__(self, width=1, height=1, x=0, y=0):
    ...         super().__init__(x, y)
    ...         self.length = length
    ...         self.height = height
    ...
    
  • How would you write the code to add an area() method for the Triangle class? Should the area() method be moved to the Form base class and inherited by Circle, Square and Triangle? What problems would this change cause?

    It makes sense to put the area() method in a Triangle class; but putting it in Form would not be very helpful because different types of Form have their own area calculations. Any derived Form would override the base area() method anyway.

Data types as objects

  • What would be the difference between using type() and isinstance() in Check: Lists?

    With type() you would only get lists, but not instances of lists.

Private variables and methods

  • Modify the code of the Triangle class to make the dimension variables private. What restriction will this change impose on the use of the class?

    >>> class Triangle:
    ...     def __init__(self, x, y):
    ...         self.__x = x
    ...         self.__y = y
    ...
    

    The dimension variables are no longer available outside the class via .x and .y.

  • Update the dimensions of the Triangle class so that they are properties with getters and setters that do not allow negative values.

    >>> class Triangle:
    ...     def __init__(self, x, y):
    ...         self.__x = x
    ...         self.__y = y
    ...     @property
    ...     def x(self):
    ...         return self.__x
    ...     @x.setter
    ...     def x(self, new_x):
    ...         if new_x >= 0:
    ...             self.__x = new_x
    ...     @property
    ...     def y(self):
    ...         return self.__y
    ...     @y.setter
    ...     def y(self, new_y):
    ...         if new_y >= 0:
    ...             self.__y = new_y
    ...
    >>> t1 = Triangle(-2, 2)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 6, in __init__
    ValueError: The number must be greater or equal to zero.
    >>> t1 = Triangle(2, 2)
    >>> t1.x = -2
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 13, in x
    ValueError: The number must be greater or equal to zero.
    >>> t1.x = 3
    >>> t1.x
    3
    

Creating a distribution package

  • If you want to create a task management package that writes the tasks to a database and provides them via a Python API and a command line interface (CLI), how would you structure the files?

    The package performs three types of actions:

    • Accessing the database

    • Providing a Python API

    • Providing a command line interface

    ├── README.rst
    ├── pyproject.toml
    └── src
        └── items
            ├── __init__.py
            ├── api.py
            ├── cli.py
            └── db.py
    
  • Think about how you want to fulfil the above tasks. Which libraries and modules can you think of that could fulfil this task? Sketch the code for the modules of the Python API, the command line interface and the database connection.

    I would create a DB class in src/items/db.py for communication with the database, in the following example for tinydb:

    import tinydb
    
    
    class DB:
        def __init__(self, db_path, db_file_prefix):
            self._db = tinydb.TinyDB(
                db_path / f"{db_file_prefix}.json", create_dirs=True
            )
    
        def create(self, item: dict):
            """Create an item
    
            Returns:
                id: The items id.
            """
    
            return id
    
        def read(self, id: int):
            """Reads an item.
    
            Args:
                id (int): The item id of an item.
            Returns:
                item: The item object."""
            return item
    
        def update(self, id: int, mods):
            """Update an item in the database.
    
            Args:
                id (int): The item id of an item.
                mods (Item): The modifications to be made to this item.
            """
            self._db.update(changes, doc_ids=[id])
    
        def delete(self, id: int):
            """Deletes an item in the database.
    
            Args:
                id (int): The item id of an item.
            """
            self._db.remove(doc_ids=[id])
    
        def close(self):
            """Closes the database connection."""
            self._db.close()
    

    Then I would use dataclass() in src/items/api to create an Item class:

    from dataclasses import dataclass, field
    
    
    @dataclass
    class Item:
        summary: str = None
        owner: str = None
        state: str = "todo"
        id: int = field(default=None, compare=False)
    
    
    class ItemsException(Exception):
        pass
    
    
    class ItemsDB:
        def __init__(self, db_path):
            self._db_path = db_path
            self._db = DB(db_path, ".items_db")
    
        def add_item(self, item: Item):
            return
    
        def get_item(self, item: Item):
            return
    
        def update_item(self, item: Item):
            return
    
        def delete_item(self, item: Item):
            return
    
        def close(self):
            self._db.close()
    
        def path(self):
            return self._db_path
    

    ItemsException Item and ItemsDB are then provided in src/items/__init__.py:

    from .api import ItemsException, Item, ItemsDB
    

    See also

    You can find a complete example at github.com/veit/items.

Files

  • Uses the functions of the os module to take a path to a file named example.log and create a new file path in the same directory for a file named example.log1.

    >>> import os
    >>> path = os.path.abspath("example.log")
    >>> print(path)
    /Users/veit/python-basics-tutorial-de/example.log
    >>> new_path = f"{path}2"
    >>> print(new_path)
    /Users/veit/python-basics-tutorial-de/example.log2
    
  • What is the significance of adding b as a parameter to open()?

    This opens the file in binary mode, which means that bytes and not characters are read and written.

  • Open a file my_file.txt and insert additional text at the end of the file. Which command would you use to open my_file.txt? Which command would you use to reopen the file and read it from the beginning?

    >>> with open("my_file", "a") as f:
    ...     f.write("Hi, Pythinistas!\n")
    ...
    17
    >>> with open("my_file") as f:
    ...     print(f.readlines())
    ...
    ['Hi, Pythinistas!\n', 'Hi, Pythinistas!\n']
    
  • What use cases can you imagine in which the struct module would be useful for reading or writing binary data?

    • when reading and writing a binary file

    • when reading from an external interface, where the data should be stored exactly as it was transmitted

  • Why pickle may or may not be suitable for the following use cases:

    1. Saving some state variables from one run to the next ✅

    2. Storing evaluation results ❌, as pickle is dependent on the respective Python version

    3. Saving user names and passwords ❌, as pickles are not secure

    4. Saving a large dictionary with English terms ❌, as the entire pickle would have to be loaded into memory

  • If you look at the man page for the wc utility, you will see two command line options:

    -c

    counts the bytes in the file

    -m

    counts the characters, which in the case of some Unicode characters can be two or more bytes long

    Also, if a file is specified, our module should read from and process that file, but if no file is specified, it should read from and process stdin.

    See also

    _wcargv_stdin.py

  • If a context manager is used in a script that reads and/or writes multiple files, which of the following approaches do you think would be best?

    1. Put the entire script in a block managed by a with statement.

    2. Use one with statement for all reads and another for all writes.

    3. Use a with statement every time you read or write a file, that is, for every line.

    4. Use a with statement for each file you read or write.

    Probably 4. is the best approach as part of the context manager’s job when accessing files is to ensure that a file is closed.

  • Archive *.txt files from the current directory in the archive directory as *.zip files with the current date as the file name.

    • Which modules do you need for this?

      datetime, pathlib and zipfile.

    • Write a possible solution.

       1>>> import datetime
       2>>> import pathlib
       3>>> import zipfile
       4>>> file_pattern = "*.txt"
       5>>> archive_path = "archive"
       6>>> today = f"{datetime.date.today():%Y-%m-%d}"
       7>>> cur_path = pathlib.Path(".")
       8>>> paths = cur_path.glob(file_pattern)
       9>>> zip_path = cur_path.joinpath(archive_path, today + ".zip")
      10>>> zip_file = zipfile.ZipFile(str(zip_path), "w")
      11>>> for path in paths:
      12...     zip_file.write(str(path))
      13...     path.unlink()
      14...
      
      Line 9

      creates the path to the ZIP file in the archive directory.

      Line 10

      opens the new ZIP file object for writing; str() is required to convert a path into a character string.

      Line 12

      writes the current file to the ZIP file.

      Line 13

      removes the current file from the working directory.