From 01cb17ba25dada624d7f305c74b3ce0450396c56 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Fri, 19 Apr 2024 14:50:09 +0200 Subject: [PATCH 01/19] Added intermediate quizes. --- input_output.ipynb | 208 +++++++++++++++++++++-- tutorial/input_output.py | 234 ++++++++++++++++++++++++++ tutorial/tests/testsuite/testsuite.py | 3 +- 3 files changed, 428 insertions(+), 17 deletions(-) create mode 100644 tutorial/input_output.py diff --git a/input_output.ipynb b/input_output.ipynb index f0bc31c5..8cef96c1 100644 --- a/input_output.ipynb +++ b/input_output.ipynb @@ -79,6 +79,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", @@ -115,12 +172,27 @@ "" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import tutorial.input_output as op\n", + "op.StringInput()" + ] + }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercises" + "#### Exercises on string input" ] }, { @@ -146,7 +218,7 @@ "metadata": {}, "outputs": [], "source": [ - "%%ipytest\n", + "%%ipytest debug\n", "\n", "def solution_print_odd(n: int) -> None: \n", " \"\"\"\n", @@ -328,7 +400,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 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:" ] }, { @@ -351,12 +424,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" + "#### Exercises on paths" ] }, { @@ -373,6 +463,15 @@ "" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%reload_ext tutorial.tests.testsuite" + ] + }, { "cell_type": "code", "execution_count": null, @@ -385,7 +484,7 @@ "\n", "def solution_find_all_files(current_path: Path) -> list[Path]: \n", " \"\"\"Write your solution here\"\"\"\n", - " pass" + " return 1" ] }, { @@ -420,6 +519,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", @@ -505,12 +605,29 @@ "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" + "#### Exercises on file reading" ] }, { @@ -600,12 +717,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" + "#### Exercises on file writing" ] }, { @@ -647,7 +781,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "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.\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", @@ -689,15 +823,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 *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:" ] }, { @@ -723,7 +857,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, avoding 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", @@ -731,6 +866,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", @@ -989,6 +1141,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", @@ -1118,7 +1287,14 @@ "\n", "
\n", "

Hint

\n", - " The file is available as the parameter input_file of solution_exercise2 function\n", + " \n", "
\n", "\n", "\n" @@ -1338,7 +1514,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.10.12" }, "vscode": { "interpreter": { diff --git a/tutorial/input_output.py b/tutorial/input_output.py new file mode 100644 index 00000000..6472f0b1 --- /dev/null +++ b/tutorial/input_output.py @@ -0,0 +1,234 @@ +from .common import Question, Quiz + + +class StringOutput(Quiz): + def __init__(self, title=""): + q1 = Question( + question="Can the function print() be used on strings only?", + 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 a f before a string do?", + 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 you to insert variables into the string": "Correct!", + }, + correct_answer="It allows you to insert variables into the 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 can be 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": "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, which is 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, which is the location of the current Python script being run.", + shuffle=True, + ) + + super().__init__(questions=[q1, q2, q3]) + + +class TextFiles(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, + ) + + q3 = Question( + question="""The path Pathlib.Path("./") represent a relative path. What location does it refer to?""", + options={ + "Relative to the current working directory, which is 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, which is 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 95290998..acb804f2 100644 --- a/tutorial/tests/testsuite/testsuite.py +++ b/tutorial/tests/testsuite/testsuite.py @@ -237,6 +237,7 @@ def run_cell(self) -> List[IPytestResult]: @cell_magic def ipytest(self, line: str, cell: str): + print("Running tests...") """The `%%ipytest` cell magic""" # Check that the magic is called from a notebook if not self.shell: @@ -282,7 +283,7 @@ 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) - + print("Parsed AST") # Display the test results and the solution code for result in results: solution = ( From 917c2ece88f36ad196fcbc38671425a791c56cc2 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 08:13:55 +0200 Subject: [PATCH 02/19] Fixed some typos --- tutorial/input_output.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index 6472f0b1..4779ebca 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -14,11 +14,11 @@ def __init__(self, title=""): ) q2 = Question( - question="What does a f before a string do?", + question="What does a 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 you to insert variables into the string": "Correct!", + "It allows you to insert variables into the string": "Correct! In the example above, it will print the value of the name variable, if this is defined.", }, correct_answer="It allows you to insert variables into the string", shuffle=True, @@ -43,7 +43,7 @@ def __init__(self, title=""): 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": "Wrong!", + "The function continues its execusion": "Wrong!", }, correct_answer="The function execution stops and it waits for the user to type an input", shuffle=True, @@ -176,7 +176,7 @@ def __init__(self, title=""): 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?", + 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.", From b4efda0c2a95fe71d44afd4317c4fb77fc0bf6f0 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 13:55:18 +0200 Subject: [PATCH 03/19] Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich --- tutorial/input_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index 4779ebca..f443e8bd 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -4,7 +4,7 @@ class StringOutput(Quiz): def __init__(self, title=""): q1 = Question( - question="Can the function print() be used on strings only?", + 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.", From 32587c3eb3df359eb160017faf7cb9286d45928b Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 13:55:25 +0200 Subject: [PATCH 04/19] Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich --- tutorial/input_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index f443e8bd..daa8edc2 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -14,7 +14,7 @@ def __init__(self, title=""): ) q2 = Question( - question="What does a f before a string do? E.g f'Hello {name}'", + 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.", From 5aff42135f07dae7b54fae9fef12b56699fa1593 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 13:55:33 +0200 Subject: [PATCH 05/19] Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich --- tutorial/input_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index daa8edc2..f4b63650 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -18,7 +18,7 @@ def __init__(self, title=""): 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 you to insert variables into the string": "Correct! In the example above, it will print the value of the name variable, if this is defined.", + "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 you to insert variables into the string", shuffle=True, From 0b2156b712ca98056a228313679a261ae465988c Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 13:55:40 +0200 Subject: [PATCH 06/19] Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich --- tutorial/input_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index f4b63650..46c05bb5 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -32,7 +32,7 @@ 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 can be used to read user input from the console.", + "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", From 983ed30cd4df633df67f2cb4e54b4395050805b2 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 13:55:46 +0200 Subject: [PATCH 07/19] Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich --- tutorial/input_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index 46c05bb5..1ab4234d 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -58,7 +58,7 @@ def __init__(self, title=""): 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", + "It concatenates paths": "Correct, it lets you construct a path from different segments", }, correct_answer="It concatenates paths", shuffle=True, From 7f0ca718e9db4deac79eff75e6268bca7d9c7c16 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 13:55:53 +0200 Subject: [PATCH 08/19] Update tutorial/input_output.py Co-authored-by: Aliaksandr Yakutovich --- tutorial/input_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index 1ab4234d..da01f40e 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -20,7 +20,7 @@ def __init__(self, title=""): "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 you to insert variables into the string", + correct_answer="It allows inserting variables into a string", shuffle=True, ) From dba1eb4dceb2af36a464d03c5b7b9a2e272ec092 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 13:58:56 +0200 Subject: [PATCH 09/19] Removed duplicate question. --- tutorial/input_output.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index da01f40e..8e5e870e 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -109,17 +109,7 @@ def __init__(self, title=""): 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, which is 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, which is the location of the current Python script being run.", - shuffle=True, - ) - - super().__init__(questions=[q1, q2, q3]) + super().__init__(questions=[q1, q2]) class ReadFiles(Quiz): From ea0de43351ddab7bb0736e783cc173ee966a971f Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 14:02:12 +0200 Subject: [PATCH 10/19] Removed unused questions --- tutorial/input_output.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index 8e5e870e..a28545d8 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -87,31 +87,6 @@ def __init__(self, title=""): super().__init__(questions=[q1, q2, q3]) -class TextFiles(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 ReadFiles(Quiz): def __init__(self, title=""): q1 = Question( From 4fb29824eb1dc53de8535fc39ae8a15cd199d3ae Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 14:03:16 +0200 Subject: [PATCH 11/19] Fixed typo in context manager quiz. --- tutorial/input_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index a28545d8..d6ffd5d5 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -146,7 +146,7 @@ def __init__(self, title=""): "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.", + correct_answer="No", shuffle=True, ) From c90d185872f95fbfba7d4a260e59075e6805de50 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Thu, 2 May 2024 14:05:08 +0200 Subject: [PATCH 12/19] Removed unnecessary log message --- tutorial/tests/testsuite/testsuite.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tutorial/tests/testsuite/testsuite.py b/tutorial/tests/testsuite/testsuite.py index acb804f2..8cd65489 100644 --- a/tutorial/tests/testsuite/testsuite.py +++ b/tutorial/tests/testsuite/testsuite.py @@ -237,7 +237,6 @@ def run_cell(self) -> List[IPytestResult]: @cell_magic def ipytest(self, line: str, cell: str): - print("Running tests...") """The `%%ipytest` cell magic""" # Check that the magic is called from a notebook if not self.shell: From 5c32aba934e96483799475bbab2673155152f5d2 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Fri, 3 May 2024 11:12:42 +0200 Subject: [PATCH 13/19] Fixed quizes --- tutorial/input_output.py | 24 ++++++++++++------------ tutorial/tests/testsuite/testsuite.py | 1 - 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index d6ffd5d5..a673930b 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -7,7 +7,7 @@ def __init__(self, title=""): 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.", + "True": "Wrong! Try it in a cell below.", }, correct_answer="False", shuffle=True, @@ -33,7 +33,7 @@ def __init__(self, title=""): 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.", + "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, @@ -43,7 +43,7 @@ def __init__(self, title=""): 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 execusion": "Wrong!", + "The function continues its execution": "Wrong!", }, correct_answer="The function execution stops and it waits for the user to type an input", shuffle=True, @@ -67,10 +67,10 @@ def __init__(self, title=""): 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 /", + "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 /", + correct_answer="No, you can combine Pathlib.Path objects with /", shuffle=True, ) @@ -117,21 +117,21 @@ 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 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.", + 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.", + "write": "Wrong, this function only writes a single string.", + "writestrings": "Wrong, this function does not exist.", + "writelines": "Correct.", }, - correct_answer="writelines", + correct_answer="writelines", shuffle=True, ) diff --git a/tutorial/tests/testsuite/testsuite.py b/tutorial/tests/testsuite/testsuite.py index 8cd65489..4cccc262 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) - print("Parsed AST") # Display the test results and the solution code for result in results: solution = ( From 056e526ae1c0908fe31c935ad8e633182e8ea07a Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Fri, 3 May 2024 14:47:54 +0200 Subject: [PATCH 14/19] Fixed some typos. --- tutorial/input_output.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorial/input_output.py b/tutorial/input_output.py index a673930b..14e1daa0 100644 --- a/tutorial/input_output.py +++ b/tutorial/input_output.py @@ -77,10 +77,10 @@ def __init__(self, title=""): q3 = Question( question="""The path Pathlib.Path("./") represent a relative path. What location does it refer to?""", options={ - "Relative to the current working directory, which is the location of the current Python script being run.": "Correct!", + "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, which is the location of the current Python script being run.", + correct_answer="Relative to the current working directory, the location of the current Python script being run", shuffle=True, ) From ce2f7505f937dcbda684b8241338e1fc1ae1e613 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Sat, 4 May 2024 13:57:42 +0200 Subject: [PATCH 15/19] Fixed TOC. --- input_output.ipynb | 55 +++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/input_output.ipynb b/input_output.ipynb index 8421683c..0979470d 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-🌶️🌶️🌶️)" ] }, { From a14c1d68620776ea07785ad6ebf978ba506c7b92 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Mon, 6 May 2024 13:30:27 +0200 Subject: [PATCH 16/19] Update input_output.ipynb Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> --- input_output.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input_output.ipynb b/input_output.ipynb index 0979470d..cd69b942 100644 --- a/input_output.ipynb +++ b/input_output.ipynb @@ -425,7 +425,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also list all files matching a certain in a given directory using the `glob` method. \n", + "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:" ] }, From e90534c06f1c8397c24a38f941b2a08ba4783e37 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Mon, 6 May 2024 13:30:39 +0200 Subject: [PATCH 17/19] Update input_output.ipynb Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> --- input_output.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input_output.ipynb b/input_output.ipynb index cd69b942..b6aefd5f 100644 --- a/input_output.ipynb +++ b/input_output.ipynb @@ -871,7 +871,7 @@ "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 using many different types *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, e.g. open a file.\n", From 2cba5361345032db7854f4bc57894ecd8ae814ad Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Mon, 6 May 2024 13:30:59 +0200 Subject: [PATCH 18/19] Update input_output.ipynb Co-authored-by: Despina Adamopoulou <16343312+despadam@users.noreply.github.com> --- input_output.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input_output.ipynb b/input_output.ipynb index b6aefd5f..be4e95a6 100644 --- a/input_output.ipynb +++ b/input_output.ipynb @@ -906,7 +906,7 @@ "`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", - "This means that you don't have to manually call `close` after `open` anymore, avoding many potential bugs.\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", From ca7e07322f685a5e034abcfa34fc7d9627e1aab0 Mon Sep 17 00:00:00 2001 From: Simone Baffelli Date: Mon, 6 May 2024 13:38:03 +0200 Subject: [PATCH 19/19] Fixed missing title --- input_output.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/input_output.ipynb b/input_output.ipynb index be4e95a6..f7b458b1 100644 --- a/input_output.ipynb +++ b/input_output.ipynb @@ -200,7 +200,9 @@ { "cell_type": "markdown", "metadata": {}, - "source": [] + "source": [ + "### Quiz on string input" + ] }, { "cell_type": "code",