From 93ed886d8c0fcc2da23a6160659f74b3c48dd12b Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Mon, 6 May 2024 16:37:26 +0200 Subject: [PATCH] Added intermediate quizes. (#224) * Added intermediate quizes. * Fixed some typos * Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich * Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich * Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich * Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich * Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich * Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich * Removed duplicate question. * Removed unused questions * Fixed typo in context manager quiz. * Removed unnecessary log message * Fixed quizes * Fixed some typos. * Fixed TOC. * Update input_output.ipynb Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> * Update input_output.ipynb Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> * Update input_output.ipynb Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> * Fixed missing title --------- Co-authored-by: Aliaksandr Yakutovich Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> --- input_output.ipynb | 294 ++++++++++++++++++++++---- tutorial/input_output.py | 199 +++++++++++++++++ tutorial/tests/testsuite/testsuite.py | 1 - 3 files changed, 453 insertions(+), 41 deletions(-) create mode 100644 tutorial/input_output.py diff --git a/input_output.ipynb b/input_output.ipynb index 39bc0be..f7b458b 100644 --- a/input_output.ipynb +++ b/input_output.ipynb @@ -13,29 +13,38 @@ "metadata": {}, "source": [ "# Table of Contents\n", - " - [References](#References)\n", - " - [Introduction](#Introduction)\n", - " - [String input and output ](#String-input-and-output)\n", - " - [String output](#String-output)\n", - " - [String input](#String-input)\n", - " - [Warm-up exercises](#Warm-up-exercises)\n", - " - [File I/O](#File-I/O)\n", - " - [Paths](#Paths)\n", - " - [Exercises on Paths](#Exercises-on-Paths)\n", - " - [Reading from a file](#Reading-from-a-file)\n", - " - [Writing to a file](#Writing-to-a-file)\n", - " - [Exercises on file reading and writing](#Exercises-on-file-reading-and-writing)\n", - " - [Context managers](#Context-managers)\n", - " - [Binary I/O](#Binary-I/O)\n", - " - [Bytes and strings](#Bytes-and-strings)\n", - " - [Converting bytes to text ](#Converting-bytes-to-text)\n", - " - [Reading/Writing CSV files](#Reading/Writing-CSV-files)\n", - " - [Exercises](#Exercises)\n", - " - [Exercise 1: CSV to dictionary 🌶️🌶️](#Exercise-1:-CSV-to-dictionary-🌶️🌶️)\n", - " - [Exercise 2: Counting words 🌶️](#Exercise-2:-Counting-words-🌶️)\n", - " - [Exercise 3: Letter statistics 🌶️🌶️](#Exercise-3:-Letter-statistics-🌶️🌶️)\n", - " - [Exercise 4: Translating words 🌶️🌶️](#Exercise-4:-Translating-words-🌶️🌶️)\n", - " - [Exercise 5: Binary format 🌶️🌶️🌶️](#Exercise-5:-Binary-format-🌶️🌶️🌶️)" + " - [Input / Output](#Input-/-Output)\n", + " - [Table of Contents](#Table-of-Contents)\n", + " - [References](#References)\n", + " - [Introduction](#Introduction)\n", + " - [String input and output ](#String-input-and-output)\n", + " - [String output](#String-output)\n", + " - [Quiz on string output:](#Quiz-on-string-output:)\n", + " - [String input](#String-input)\n", + " - [Exercises on string input](#Exercises-on-string-input)\n", + " - [File I/O](#File-I/O)\n", + " - [Paths](#Paths)\n", + " - [Quiz on Paths](#Quiz-on-Paths)\n", + " - [Exercises on paths](#Exercises-on-paths)\n", + " - [Reading from a file](#Reading-from-a-file)\n", + " - [Quiz on file reading](#Quiz-on-file-reading)\n", + " - [Exercises on file reading](#Exercises-on-file-reading)\n", + " - [Writing to a file](#Writing-to-a-file)\n", + " - [Quiz on file writing](#Quiz-on-file-writing)\n", + " - [Exercises on file writing](#Exercises-on-file-writing)\n", + " - [Context managers](#Context-managers)\n", + " - [Quiz on context managers](#Quiz-on-context-managers)\n", + " - [Binary I/O](#Binary-I/O)\n", + " - [Bytes and strings](#Bytes-and-strings)\n", + " - [Converting bytes to text ](#Converting-bytes-to-text)\n", + " - [Reading/Writing CSV files](#Reading/Writing-CSV-files)\n", + " - [Quiz on CSV](#Quiz-on-CSV)\n", + " - [Exercises](#Exercises)\n", + " - [Exercise 1: CSV to dictionary 🌶️🌶️](#Exercise-1:-CSV-to-dictionary-🌶️🌶️)\n", + " - [Exercise 2: Counting words 🌶️](#Exercise-2:-Counting-words-🌶️)\n", + " - [Exercise 3: Letter statistics 🌶️🌶️](#Exercise-3:-Letter-statistics-🌶️🌶️)\n", + " - [Exercise 4: Translating words 🌶️🌶️](#Exercise-4:-Translating-words-🌶️🌶️)\n", + " - [Exercise 5: Binary format 🌶️🌶️🌶️](#Exercise-5:-Binary-format-🌶️🌶️🌶️)" ] }, { @@ -95,6 +104,63 @@ "print(\"I am some text\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to print any other python object using `print`. \n", + "In that case, the `__str__` **magic method** on that object's class is [called](https://docs.python.org/3/reference/datamodel.html#object.__str__)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print([])\n", + "print([1,2,3])\n", + "print({\"key\": \"value\"})\n", + "print(lambda x: x)\n", + "print((\"some\", \"tuple\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we want to display the value of a variable in a string, we can do it most conveniently using **string interpolation**. \n", + "To do so, we prepend `f` to a string, which can contain reference to the variables we want to print enclosed in \"{}\":" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "my_var = 3\n", + "my_string = f\"my_var is {my_var}\"\n", + "print(my_string)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Quiz on string output:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tutorial.input_output as op\n", + "op.StringOutput()" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -131,12 +197,29 @@ "" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Quiz on string input" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tutorial.input_output as op\n", + "op.StringInput()" + ] + }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "### Warm-up exercises" + "#### Exercises on string input" ] }, { @@ -162,7 +245,7 @@ "metadata": {}, "outputs": [], "source": [ - "%%ipytest\n", + "%%ipytest debug\n", "\n", "def solution_print_odd(n: int) -> None: \n", " \"\"\"\n", @@ -344,7 +427,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also list all files matching a certain in a given directory using the `glob` method:" + "We can also list all files matching a certain pattern in a given directory using the `glob` method. \n", + "The method returns an `iterable` of paths that can be turned into a list like this:" ] }, { @@ -367,12 +451,29 @@ "The `*` star means *match everything*. For more information on *glob patterns*, see the documentation of [fnmatch](https://docs.python.org/3/library/fnmatch.html#module-fnmatch)." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Quiz on Paths\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tutorial.input_output as op\n", + "op.Paths()" + ] + }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercises on Paths" + "#### Exercises on paths" ] }, { @@ -389,6 +490,15 @@ "" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%reload_ext tutorial.tests.testsuite" + ] + }, { "cell_type": "code", "execution_count": null, @@ -401,7 +511,7 @@ "\n", "def solution_find_all_files(current_path: Path) -> list[Path]: \n", " \"\"\"Write your solution here\"\"\"\n", - " pass" + " return 1" ] }, { @@ -436,6 +546,7 @@ "\n", "### Reading from a file\n", "\n", + "We now want to learn how to read text from a file. \n", "Let's see how to do this with an example: we want to open the file [hello.txt](./data/hello.txt) and read its contents.\n", "\n", "1. The path is already identified, we know the file is in `./data/hello.txt`. We save this in a variable `path`.\n", @@ -521,6 +632,54 @@ "This is the most *pythonic* way to read a file line-by-line instead of reading the full contents at once." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Quiz on file reading" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tutorial.input_output as op\n", + "op.ReadFiles()" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Exercises on file reading" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Modify the function `solution_read_file` to return the content of the file passed as the `input_file` argument. Return the content as a **list of strings**, one string per line.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%ipytest\n", + "\n", + "from pathlib import Path\n", + "\n", + "def solution_read_file(input_file: Path) -> \"list[str]\": \n", + " \"\"\"Write your solution here\"\"\"\n", + " pass" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -585,12 +744,29 @@ "Notice that for each line, we concatenate the `newline` `\\n` symbol to the string to be written to write the text to a new line.\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Quiz on file writing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tutorial.input_output as op\n", + "op.WriteFiles()" + ] + }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercises on file reading and writing" + "#### Exercises on file writing" ] }, { @@ -655,7 +831,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "3. Modify the function `solution_read_write_file` to read the lines from the file `input_file` and write them in the form `line, length`, to the file `output_file`. Here `line` is the line of text in `input_file` **without the line ending**, `length` is **number of characters** in that line.\n", + "1. Modify the function `solution_read_write_file` to read the lines from the file `input_file` and write them in the form `line, length`, to the file `output_file`. Here `line` is the line of text in `input_file` **without the line ending**, `length` is **number of characters** in that line **without the line separator**.\n", "If `input_file` contains these lines:\n", " ```\n", " first\n", @@ -697,15 +873,15 @@ "If you open many files and you don't close them, the Python interpreter can run out of memory.\n", "On some operating systems, the file contents are only updated after closing, etc ...\n", "\n", - "This pattern is common when dealing with many *resources*: files, connections, threads, servers, etc...\n", + "This pattern is common when using many different types of *resources*: files, connections, threads, servers, etc...\n", "You acquire access to the resource, do some work on it, and, finally, clean up after yourself by closing it again.\n", "Because of this, Python offers a construct called [*context manager*](https://docs.python.org/3/reference/datamodel.html#context-managers) which implements exactly this beahvior:\n", - "- Get access to a resource.\n", - "- Do some work.\n", - "- Release this resource.\n", + "- Get access to a resource, e.g. open a file.\n", + "- Do some work with the resource.\n", + "- Release this resource, e.g close the file or the connection.\n", "\n", "In the case of files, we can replace the open-read-close or open-write-close sequence with a context manager.\n", - "Context managers are used inside the `with` statement:" + "Context managers are used in a `with` statement:" ] }, { @@ -731,7 +907,8 @@ "source": [ "`with open(path) as name` opens the file in `path` and assigns it to the `name` file object.\n", "This object is only valid in the *scope* of the context manager, that is the indented block of code that follows the `:`.\n", - "Once the Python interpreter leaves the context manager, `file_ob.close()` is automatically called, ensuring the file is properly closed no matter what happens.\n", + "Once the Python interpreter leaves the context manager, `file_ob.close()` is automatically called, ensuring the file is properly closed no matter what happens. \n", + "This means that you don't have to manually call `close` after `open` anymore, avoiding many potential bugs.\n", "\n", "This pattern can be extended to any other resource that should be managed in a similar way, for example database connections. \n", "Any object that implements `__enter__` and `__exit__` can be used with the context manager syntax.\n", @@ -739,6 +916,23 @@ "If you want to learn how to implement context managers for other types of objects, please refer to the `contextlib` [documentation](https://docs.python.org/3/library/contextlib.html) in the Python standard library." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Quiz on context managers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tutorial.input_output as op\n", + "op.ContextManagers()" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -997,6 +1191,23 @@ " " ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Quiz on CSV" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tutorial.input_output as op\n", + "op.CSV()" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -1127,10 +1338,13 @@ "
\n", "

Hint

\n", "
    \n", - "
  • The file is available as the parameter input_file of solution_exercise2 function
  • \n", - "
  • A word consists of printable characters without line ends and spaces
  • \n", + "
  • \n", + " The file is available as the parameter input_file of solution_exercise2 function\n", + "
  • \n", + "
  • \n", + " A word consists of printable characters without whitespace, line breaks etc.\n", + "
  • \n", "
\n", - " \n", "
\n", "\n", "\n" @@ -1350,7 +1564,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.10.12" }, "vscode": { "interpreter": { diff --git a/tutorial/input_output.py b/tutorial/input_output.py new file mode 100644 index 0000000..14e1daa --- /dev/null +++ b/tutorial/input_output.py @@ -0,0 +1,199 @@ +from .common import Question, Quiz + + +class StringOutput(Quiz): + def __init__(self, title=""): + q1 = Question( + question="The function print() can be used only to output strings.", + options={ + "False": "Correct! It can be used on any Python object.", + "True": "Wrong! Try it in a cell below.", + }, + correct_answer="False", + shuffle=True, + ) + + q2 = Question( + question="What does the f before a string do? E.g f'Hello {name}'", + options={ + "It formats the string as a float": "Wrong! Try it in a cell below.", + "It changes the color of the string": "Wrong! Try it in a cell below.", + "It allows inserting variables into a string": "Correct! In the example above, it will print the value of the name variable, if it is defined.", + }, + correct_answer="It allows inserting variables into a string", + shuffle=True, + ) + + super().__init__(questions=[q1, q2]) + + +class StringInput(Quiz): + def __init__(self, title=""): + q1 = Question( + question="What does the function input do?", + options={ + "It asks for user input and returns it as a string": "Correct! It is used to read user input from the console.", + "It shows a list of input devices available on the current computer": "Wrong! It is used to read user input from the console.", + }, + correct_answer="It asks for user input and returns it as a string", + shuffle=True, + ) + + q2 = Question( + question="What happens if you call input() in the middle of a function?", + options={ + "The function execution stops and it waits for the user to type an input": "Correct! Input is a blocking function which waits for user to enter a string in the console and press enter.", + "The function continues its execution": "Wrong!", + }, + correct_answer="The function execution stops and it waits for the user to type an input", + shuffle=True, + ) + + super().__init__(questions=[q1, q2]) + + +class Paths(Quiz): + def __init__(self, title=""): + q1 = Question( + question="What does the operator / do when applied to two Pathlib.Path objects?", + options={ + "It removes the second path from the first": "Wrong, try it in the shell.", + "It concatenates paths": "Correct, it lets you construct a path from different segments", + }, + correct_answer="It concatenates paths", + shuffle=True, + ) + + q2 = Question( + question="If you use Pathlib, do you need to use different path separators on Windows and Linux to combine path segments?", + options={ + "No, you can combine Pathlib.Path objects with /": "Correct! Pathlib will then generate the correct path for your OS.", + "Yes": "Wrong! You can always use /", + }, + correct_answer="No, you can combine Pathlib.Path objects with /", + shuffle=True, + ) + + q3 = Question( + question="""The path Pathlib.Path("./") represent a relative path. What location does it refer to?""", + options={ + "Relative to the current working directory, the location of the current Python script being run": "Correct!", + "Relative to the user's home directory": "Wrong!", + }, + correct_answer="Relative to the current working directory, the location of the current Python script being run", + shuffle=True, + ) + + super().__init__(questions=[q1, q2, q3]) + + +class ReadFiles(Quiz): + def __init__(self, title=""): + q1 = Question( + question="Can you read from a file before calling open?", + options={ + "Yes": "Wrong, if the file is not open, we cannot access its contents.", + "No": "Correct, we need to open the file first.", + }, + correct_answer="No", + shuffle=True, + ) + + q2 = Question( + question="The function readlines reads the entire content of a text file into one. Is this correct?", + options={ + "No": "Correct! It reads the file line by line.", + "Yes": "Wrong! It reads the file line by line and returns a list of str, with one element for each line.", + }, + correct_answer="No", + shuffle=True, + ) + + super().__init__(questions=[q1, q2]) + + +class WriteFiles(Quiz): + def __init__(self, title=""): + q1 = Question( + question="""What does w in the second argument of open do: open(path, "w")?""", + options={ + "It opens the file for writing": "Correct.", + "It writes a w next to each line of the file": "Wrong, it opens the file for reading.", + }, + correct_answer="It opens the file for writing", + shuffle=True, + ) + + q2 = Question( + question="What function do we use on a file object to write a list of strings line-by-line", + options={ + "write": "Wrong, this function only writes a single string.", + "writestrings": "Wrong, this function does not exist.", + "writelines": "Correct.", + }, + correct_answer="writelines", + shuffle=True, + ) + + super().__init__(questions=[q1, q2]) + + +class ContextManagers(Quiz): + def __init__(self, title=""): + q1 = Question( + question="Do you need to call close on a file object when using a context manager?", + options={ + "Yes": "Wrong! The context manager will handle the closing of a file when the context manager scope ends.", + "No": "Correct! The context manager automatically calls close when leaving the scope.", + }, + correct_answer="No", + shuffle=True, + ) + + q2 = Question( + question="What methods should an class implement to be used as a context manager?", + options={ + "__enter__ and __exit__": "Correct! Any class implementing these methods can be used as a context manager.", + "__start__ and __end__": "Wrong.", + "__open__ and __close__": "Wrong.", + }, + correct_answer="__enter__ and __exit__", + shuffle=True, + ) + + super().__init__(questions=[q1, q2]) + + +class CSV(Quiz): + def __init__(self, title=""): + q1 = Question( + question="Does the csv module automatically read the column names of a csv file?", + options={ + "Yes": "Wrong! You are responsible for reading and storing the first line if the file has column names", + "No": "Correct! You are responsible for reading and storing the first line if the file has column names.", + }, + correct_answer="No", + shuffle=True, + ) + + q2 = Question( + question="What argument does the writerow function of a csv object take?", + options={ + "Any iterable of values to write as the current csv row": "Correct!", + "As many arguments as the columns of the csv": "Wrong", + "None: the function does not exist": "Wrong", + }, + correct_answer="Any iterable of values to write as the current csv row", + shuffle=True, + ) + + q3 = Question( + question="Does csv.reader interpret the values of a csv row as number, dates etc?", + options={ + "Yes": "Wrong. By default it reads the current row and returns a list of strings.", + "No": "Correct. By default it reads the current row and returns a list of strings", + }, + correct_answer="No", + shuffle=True, + ) + super().__init__(questions=[q1, q2, q3]) diff --git a/tutorial/tests/testsuite/testsuite.py b/tutorial/tests/testsuite/testsuite.py index 9529099..4cccc26 100644 --- a/tutorial/tests/testsuite/testsuite.py +++ b/tutorial/tests/testsuite/testsuite.py @@ -282,7 +282,6 @@ def ipytest(self, line: str, cell: str): # Parse the AST of the test module to retrieve the solution code ast_parser = AstParser(self.module_file) - # Display the test results and the solution code for result in results: solution = (