From 0bdb1e5d8d758deea93293427b456ec278457f29 Mon Sep 17 00:00:00 2001 From: Michael Dayan Date: Sat, 19 Nov 2022 12:03:14 +0100 Subject: [PATCH] Add part 2 solutions --- python_series_solutions.ipynb | 1225 +++++++++++++++++++++++++++++- solutions_part2/exam1.py | 13 + solutions_part2/grading.py | 37 + solutions_part2/step1/exam1.py | 11 + solutions_part2/step1/grading.py | 24 + solutions_part2/step2/exam1.py | 11 + solutions_part2/step2/grading.py | 24 + solutions_part2/step3/grading.py | 32 + solutions_part2/step4/grading.py | 37 + 9 files changed, 1405 insertions(+), 9 deletions(-) create mode 100644 solutions_part2/exam1.py create mode 100644 solutions_part2/grading.py create mode 100644 solutions_part2/step1/exam1.py create mode 100644 solutions_part2/step1/grading.py create mode 100644 solutions_part2/step2/exam1.py create mode 100644 solutions_part2/step2/grading.py create mode 100644 solutions_part2/step3/grading.py create mode 100644 solutions_part2/step4/grading.py diff --git a/python_series_solutions.ipynb b/python_series_solutions.ipynb index 275c464..ba55500 100644 --- a/python_series_solutions.ipynb +++ b/python_series_solutions.ipynb @@ -5,7 +5,8 @@ "metadata": {}, "source": [ "# Table of contents:\n", - " * [Part 1](#Introduction-to-Python---Part-1)" + " * [Part 1](#Introduction-to-Python---Part-1)\n", + " * [Part 2](#Introduction-to-Python---Part-2)" ] }, { @@ -1855,13 +1856,1224 @@ "source": [ "print(proper_methods)" ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Introduction to Python - Part 2" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "## Functions" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "A function allows you to group *a logical unit set of commands*, which has a specific aim, into **a single entity**. This way you can reuse that function every time you want to achieve that aim. \n", + "\n", + "The aim can be very simple such has finding the maximum of a list (function `max`), its length (function `len`) or more complex (finding each unique element and count how many times it occurs )." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "def comment_grade(grade):\n", + " if grade < 5:\n", + " return('Grade too low')\n", + " else:\n", + " return('Grade high enough')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "A function can have optional arguments, always indicated after the compulsory arguments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def comment_grade(grade, mode='normal'):\n", + " if grade < 5:\n", + " return('Grade too low')\n", + " elif grade > 5:\n", + " if mode == 'normal':\n", + " return('Grade high enough')\n", + " elif mode == 'positive_reinforcement':\n", + " return('Well done, keep going!')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "To call the function, use its name, with arguments inside parentheses" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "comment_grade(6)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "In many cases, the user is interested in a return value that he will use." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "comment = comment_grade(6)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "print(comment)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "A function can also have no arguments at all. And return nothing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def say_hello():\n", + " print('hello')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "say_hello()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "greeting = say_hello()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "print(greeting)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### A special Python keyword: None" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "`None` is a case-sensitive keyword to state that a variable does not have any value (like \"null\" in other languages). `None` is not the same as `0`, `False`, or an empty string. `None` has a datatype of its own." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "a = None\n", + "print(type(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "A function always return something. So if nothing is explicitely returned by the function, the return value is `None`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Function docstring" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "When you write a function, you need to document it. This will be used to automatically generate help and documentation, and also help anyone (including yourself at a later date) better understanding what it does." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "def comment_grade(grade, mode = 'normal'):\n", + " ''' Provide a feedback according to the grade value\n", + " \n", + " Parameters\n", + " ----------\n", + " grade : int\n", + " The grade obtained by the student (out of 10)\n", + " mode : str\n", + " The feedback mode, either \"normal\" (default) or \"positive_reinforcement\"\n", + "\n", + " Returns\n", + " -------\n", + " comment : str\n", + " The grade feedback\n", + " '''\n", + " if grade < 5:\n", + " return('Grade too low')\n", + " elif grade > 5:\n", + " if mode == 'normal':\n", + " return('Grade high enough')\n", + " elif mode == 'positive_reinforcement':\n", + " return('Well done, keep going!')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Best practice is now to use Python \"type hints\" for the function arguments and return values. You can remove this information from the documentation since it is redundant. Type hints provide useful information when using IDEs (linting) and allows to check code logic via third party libraries (e.g. `mypy`)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def comment_grade(grade: int, mode: str = 'normal') -> str :\n", + " ''' Provide a feedback according to the grade value\n", + " \n", + " Parameters\n", + " ----------\n", + " grade\n", + " The grade obtained by the student (out of 10)\n", + " mode\n", + " The feedback mode, either \"normal\" (default) or \"positive_reinforcement\"\n", + "\n", + " Returns\n", + " -------\n", + " comment\n", + " The grade feedback\n", + " '''\n", + " if grade < 5:\n", + " return('Grade too low')\n", + " elif grade > 5:\n", + " if mode == 'normal':\n", + " return('Grade high enough')\n", + " elif mode == 'positive_reinforcement':\n", + " return('Well done, keep going!')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "student_results = {\n", + " 'John': 3,\n", + " 'Mary': 9,\n", + " 'Peter': 5\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true, + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "for s_name, s_grade in student_results.items():\n", + " s_feedback = comment_grade(s_grade, mode='positive_reinforcement')\n", + " print(f'Feedback for {s_name}: {s_feedback}')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Proper Python code development: use an IDE (e.g. Visual Studio Code)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "**Jupyter notebooks do not allow to easily debug, reuse and version-control your code**. As such it is much better practice to develop functions inside an Integrate Development Environment (IDE) to write the main functions there. You can then import them inside your Jupyter Notebook (and reuse them in other notebooks, in other projects, and with other people with e.g. GitHub)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Functions can be (and often are) imported from modules" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "from random import randrange" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "randrange?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "max_grade = 10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "random_grade = randrange(0, max_grade+1)\n", + "print(random_grade)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "random_grade = random.randrange(0, max_grade+1)\n", + "print(random_grade)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "os.getcwd()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Remember `PATH` in the Linux lecture ?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "import sys" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "sys.path" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "os.__file__" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "A module is a python file (ending in `.py`), usually containing python functions" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "To import functions, simply use `import` followed by the name of your python file without the `.py` extension, e.g. `import grading` if your python file is called `grading.py`. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercise (using module in Jupyter notebook)\n", + "1. Open a terminal in VS Code (`Terminal` --> `New Terminal`) and create the directory `autograder` in the `python_lecture` directory\n", + "2. Create a new file in VS Code (`File` --> `New File`) and copy the definition of the function `comment_grade`\n", + "3. Save it in the directory `autograder` with the name `grading.py` (`File` --> `Save As`, then search the `autograder` directory you created, clicking on `..` (parent directory) if required)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "4. Go back into the Jupyter notebook and try to import your `grading` module so that to call the function `comment_grade` within your notebook. Does it work ?\n", + "5. Add the path to your `autograder` directory by appending it to the `sys.path` list. \n", + "Remember that:\n", + " * you can append an element `my_el` to a list `my_list` with `my_list.append(my_el)`\n", + " * a path is a string (i.e. a sequence of characters) and so should be put in between quotes\n", + "6. Try again to import your `grading` module\n", + "7. Print the help of your function (`help(fun)`, or `fun?`, or `Shift + Tab` after having written the function name)\n", + "8. Call the function with a grade of your choice" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solution (cf `solutions_part2/step1` in Github repo)\n", + "1. To do in your own environment\n", + "```bash\n", + "cd\n", + "cd python_lecture\n", + "mkdir autograder\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. and 3. To do in your own environment, cf `grading.py` in `solutions_part2/step1` on Github" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4. It does not work, python cannot find the module" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "5. `sys.path.append(\"/home/brainhacker/python_lecture/autograder\")`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "6. `import grading` should work now" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "7. Self explanatory" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "8. `grading.comment_grade(8)`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercise (using module in VS Code)\n", + "1. Create a new file in VS Code (`File` --> `New File`) and copy:\n", + " * the definition of the dictionary `student_results`\n", + " * the code applying the function `comment_grade` on each item of that dictionary\n", + "2. Make sure your code will use the function `comment_grade` of the module `grading`:\n", + " * Import the module `grading`\n", + " * choose between `import grading` or `from grading import comment_grade` and adapt the code if required\n", + "3. Save the script in the directory `autograder` with the name `exam1.py` (`File` --> `Save As`, then search the `autograder` directory you created, clicking on `..` (parent directory) if required)\n", + "\n", + "Note: your file `exam1.py` will find the module `grading` because `grading.py` is in the same directory (more generally the directory in which resides your script is always on `sys.path`, contrarily to `bash` and `$PATH`)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "4. In the terminal type `python` and then the path to your `exam1.py` file for python to run your code (note: this is the same as clicking on the `run` icon (green triangle) on the top right)\n", + " * e.g. `python /home/brainhacker/python_lecture/autograder/exam1.py`\n", + "5. Can you see something strange in the outputs?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solution (cf `solutions_part2/step1` in Github repo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1., 2. and 3. cf `exam1.py` in `solutions_part2/step1` in Github repo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4. Run `python /home/brainhacker/python_lecture/autograder/exam1.py`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "5. The strange thing is that the output for Peter is `None`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Example of debugging (`Run` --> `Start debugging`)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "### Exercise (using module in VS Code)\n", + "1. Correct the bug and check it works\n", + "2. Save the file\n", + "3. Import `exam1` inside the Jupyter notebook. What do you notice ?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solution (cf `solutions_part2/step2` in Github repo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. and 2. (cf file `grading.py` in solutions_part2/step2 in Github repo)\n", + " 1. Use the VS Code debugging feature (click next to a line number so that to set a red \"Breakpoint\") at the right place, and try to see why the feedback for Peter is None\n", + " 2. Change `grade > 5` to `grade >= 5` in the code of `grading.py`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Your python file can be used in two ways:\n", + "* Called on the command line with `python` (e.g. `python exam1.py`)\n", + "* Imported in another file or in a Jupyter notebook to use all the functions defined inside\n", + "\n", + "Careful: when you import a file, all the code inside it will be executed !" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Python use the keywork `__name__` to understand how your file was used:\n", + "* If it was called from the command line, then `__name__` will be automatically set to `\"__main__\"`\n", + "* If it was imported, then `__name__` will be automatically set to your file name (e.g. `\"exam1\"`) \n", + "\n", + "To have a part of your file automatically run on the command line, use: \n", + "```\n", + "if __name__ == \"__main__\":\n", + " ...\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercise (using module in VS Code)\n", + "1. Modify `exam1` so that it only executes the code when it is run from the command line\n", + "2. Test by saving it and running it from the command line\n", + "3. Test by importing in the Jupyter notebook and observing nothing happened\n", + " * Tip: use the following to re-import a module which has been modified: \n", + "```\n", + "import importlib\n", + "importlib.reload(my_module)```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solution (cf `solutions_part2/step2` in Github repo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. Cf `exam1.py` in `solutions_part2/step2` in Github repo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2. Run `python /home/brainhacker/python_lecture/autograder/exam1.py`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3.\n", + "```python\n", + "import importlib\n", + "importlib.reload(exam1)\n", + "```\n", + "Nothing should happen when running the two lines above (your latest `exam1.py` and `grading.py` should be in the same directory than the one you added to `sys.path`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Exceptions and assert statements" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "Errors are essential to be able to understand why something went wrong. You can throw yourself so called \"Exceptions\" in your code should something unexpected occurs, such as another module or a user not using your function as intended." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "def comment_grade(grade: int, mode: str = 'normal') -> str :\n", + " ''' Provide a feedback according to the grade value\n", + " \n", + " Parameters\n", + " ----------\n", + " grade\n", + " The grade obtained by the student (out of 10)\n", + " mode\n", + " The feedback mode, either \"normal\" (default) or \"positive_reinforcement\"\n", + "\n", + " Returns\n", + " -------\n", + " comment\n", + " The grade feedback\n", + " '''\n", + " if grade >= 0 and grade < 5:\n", + " return('Grade too low')\n", + " elif grade >= 5 and grade <= 10:\n", + " if mode == 'normal':\n", + " return('Grade high enough')\n", + " elif mode == 'positive_reinforcement':\n", + " return('Well done, keep going!')\n", + " else:\n", + " raise ValueError('The mode should be \"normal\" or \"positive_reinforcement\"')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "comment_grade(7, mode=\"tough\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "source": [ + "For a list of Python Exceptions, please see [here](https://docs.python.org/3/library/exceptions.html)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "Assert statements are particularly useful to test the internal logic of your code, to check that impossible events indeed never happen." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "def comment_grade(grade: int, mode: str = 'normal') -> str :\n", + " ''' Provide a feedback according to the grade value\n", + " \n", + " Parameters\n", + " ----------\n", + " grade\n", + " The grade obtained by the student (out of 10)\n", + " mode\n", + " The feedback mode, either \"normal\" (default) or \"positive_reinforcement\"\n", + "\n", + " Returns\n", + " -------\n", + " comment\n", + " The grade feedback\n", + " '''\n", + " if grade >= 0 and grade < 5:\n", + " return('Grade too low')\n", + " elif grade > 5 and grade <= 10:\n", + " if mode == 'normal':\n", + " return('Grade high enough')\n", + " elif mode == 'positive_reinforcement':\n", + " return('Well done, keep going!')\n", + " else:\n", + " raise ValueError('The mode should be \"normal\" or \"positive_reinforcement\"')\n", + " else:\n", + " assert (grade < 0 or grade > 10), 'INTERNAL BUG: grade is not less than 0 or greater than 10'\n", + " raise ValueError('EXTERNAL ERROR: The grade entered should be between 0 and 10')\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "comment_grade(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "It is convenient to define test functions (starting with `test_`) to run a group of assert statements. The `pytest` package is helpful to automatically run this kind of test functions (cf later section)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def test_comments():\n", + " assert comment_grade(0, mode='normal') == 'Grade too low'\n", + " assert comment_grade(2, mode='normal') == 'Grade too low'\n", + " assert comment_grade(5, mode='normal') == 'Grade high enough'\n", + " assert comment_grade(5, mode='positive_reinforcement') == 'Well done, keep going!'\n", + " assert comment_grade(10, mode='normal') == 'Grade high enough'\n", + " assert comment_grade(10, mode='positive_reinforcement') == 'Well done, keep going!'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "test_comments()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "`pytest` can be used not only to run tests from functions, but also to check results of Numpy docstring examples. In this case, use the `--doctest-modules` flag." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [], + "source": [ + "def comment_grade(grade: int, mode: str = 'normal') -> str :\n", + " ''' Provide a feedback according to the grade value\n", + " \n", + " Parameters\n", + " ----------\n", + " grade\n", + " The grade obtained by the student (out of 10)\n", + " mode\n", + " The feedback mode, either \"normal\" (default) or \"positive_reinforcement\"\n", + "\n", + " Returns\n", + " -------\n", + " comment\n", + " The grade feedback\n", + "\n", + " Examples\n", + " --------\n", + " >>> comment_grade(6)\n", + " 'Grade high enough'\n", + "\n", + " '''\n", + " if grade >= 0 and grade < 5:\n", + " return('Grade too low')\n", + " elif grade >= 5 and grade <= 10:\n", + " if mode == 'normal':\n", + " return('Grade high enough')\n", + " elif mode == 'positive_reinforcement':\n", + " return('Well done, keep going!')\n", + " else:\n", + " raise ValueError('The mode should be \"normal\" or \"positive_reinforcement\"')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercise (in VS Code)\n", + "We will test the use of `pytest` on the previous bug we discovered.\n", + "1. Modify the `comment_grade` function in the `grading` module by reintroducing the bug we discovered\n", + "2. Include a series of tests by copy-pasting the definition of the `test_comments` function above at the end of the `grading.py` file\n", + "3. Save the file and run pytest on your module with `pytest `\n", + "4. Correct the bug, save the file, and rerun pytest again\n", + "Note: by default `pytest` run all the functions starting with `test`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solution (cf `solutions_part2/step3` in Github repo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. and 2. Cf `grading.py` in `solutions_part2/step3` in Github repo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3. Run `pytest /home/brainhacker/python_lecture/autograder/grading.py`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "4. Replace `grade > 5` by `grade >= 5`, then run `pytest /home/brainhacker/python_lecture/autograder/grading.py`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "### Exercise (in VS Code)\n", + "We will test the use of `pytest` to test documentation example\n", + "1. Modify the `comment_grade` function in the `grading` module by adding the following example in the docstring:\n", + "```\n", + " Examples\n", + " --------\n", + " >>> comment_grade(6)\n", + " 'Grade high enough!'\n", + "```\n", + "2. Save the file and run pytest on the `grading` module with the option indicating you want to also test examples in your docstring\n", + "\n", + "TIP: as indicated previously, you need to use the `--doctest-modules` option\n", + "\n", + "3. Did you get an error ? If yes can you modify your example and rerun `pytest` so that not to get any error ?\n", + "\n", + "BONUS:\n", + "\n", + "4. In Jupyter, use the `importlib` module to reimport your `grading` module, and then display the help of the `comment_grade` function to check that your example appears" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solution (cf `solutions_part2/step4` in Github repo)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "1. and 2. cf `solutions_part2/step4` in Github repo, and run:\n", + "`pytest --doctest-modules /home/brainhacker/python_lecture/autograder/grading.py`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "3. Replace `'Grade high enough!'` with `'Grade high enough'` in Examples docstring (i.e. remove the exclamation mark so that the output is exactly as expected)" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.10 ('onto38')", + "display_name": "Python [conda env:shared-ds38]", "language": "python", - "name": "python3" + "name": "conda-env-shared-ds38-py" }, "language_info": { "codemirror_mode": { @@ -1873,12 +3085,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" - }, - "vscode": { - "interpreter": { - "hash": "57931505acd4750e838ebc1d499e059bc94705d1e4273af64b49e677195f6d46" - } + "version": "3.8.2" } }, "nbformat": 4, diff --git a/solutions_part2/exam1.py b/solutions_part2/exam1.py new file mode 100644 index 0000000..28bf985 --- /dev/null +++ b/solutions_part2/exam1.py @@ -0,0 +1,13 @@ +import grading + +student_results = { + 'John': 3, + 'Mary': 9, + 'Peter': 5 + } + +if __name__ == "__main__": + print(__name__) + for s_name, s_grade in student_results.items(): + s_feedback = grading.comment_grade(s_grade, mode='negative_reinforcement') + print(f'Feedback for {s_name}: {s_feedback}') \ No newline at end of file diff --git a/solutions_part2/grading.py b/solutions_part2/grading.py new file mode 100644 index 0000000..fe589f4 --- /dev/null +++ b/solutions_part2/grading.py @@ -0,0 +1,37 @@ +def comment_grade(grade: int, mode: str = 'normal') -> str : + ''' Provide a feedbac k according to the grade value + + Parameters + ---------- + grade + The amount of distance traveled + mode + The feedback mode, either "normal" (default) or "positive_reinforcement" + + Returns + ------- + comment + The grade feedback + + Examples + -------- + >>> comment_grade(5) + 'Grade high enough' + ''' + if grade < 5: + return('Grade too low') + elif grade > 5: + if mode == 'normal': + return('Grade high enough') + elif mode == 'positive_reinforcement': + return('Well done, keep going!') + else: + raise ValueError('The mode should be "normal" or "positive_reinforcement"') + +def test_comments(): + assert comment_grade(0, mode='normal') == 'Grade too low' + assert comment_grade(2, mode='normal') == 'Grade too low' + #assert comment_grade(5, mode='normal') == 'Grade high enough' + #assert comment_grade(5, mode='positive_reinforcement') == 'Well done, keep going!' + assert comment_grade(10, mode='normal') == 'Grade high enough' + assert comment_grade(10, mode='positive_reinforcement') == 'Well done, keep going!' \ No newline at end of file diff --git a/solutions_part2/step1/exam1.py b/solutions_part2/step1/exam1.py new file mode 100644 index 0000000..7aa8ca5 --- /dev/null +++ b/solutions_part2/step1/exam1.py @@ -0,0 +1,11 @@ +import grading + +student_results = { + 'John': 3, + 'Mary': 9, + 'Peter': 5 + } + +for s_name, s_grade in student_results.items(): + s_feedback = grading.comment_grade(s_grade, mode='negative_reinforcement') + print(f'Feedback for {s_name}: {s_feedback}') diff --git a/solutions_part2/step1/grading.py b/solutions_part2/step1/grading.py new file mode 100644 index 0000000..01d4d49 --- /dev/null +++ b/solutions_part2/step1/grading.py @@ -0,0 +1,24 @@ +def comment_grade(grade: int, mode: str = 'normal') -> str : + ''' Provide a feedbac k according to the grade value + + Parameters + ---------- + grade + The amount of distance traveled + mode + The feedback mode, either "normal" (default) or "positive_reinforcement" + + Returns + ------- + comment + The grade feedback + ''' + if grade < 5: + return('Grade too low') + elif grade > 5: + if mode == 'normal': + return('Grade high enough') + elif mode == 'positive_reinforcement': + return('Well done, keep going!') + else: + raise ValueError('The mode should be "normal" or "positive_reinforcement"') diff --git a/solutions_part2/step2/exam1.py b/solutions_part2/step2/exam1.py new file mode 100644 index 0000000..7aa8ca5 --- /dev/null +++ b/solutions_part2/step2/exam1.py @@ -0,0 +1,11 @@ +import grading + +student_results = { + 'John': 3, + 'Mary': 9, + 'Peter': 5 + } + +for s_name, s_grade in student_results.items(): + s_feedback = grading.comment_grade(s_grade, mode='negative_reinforcement') + print(f'Feedback for {s_name}: {s_feedback}') diff --git a/solutions_part2/step2/grading.py b/solutions_part2/step2/grading.py new file mode 100644 index 0000000..82fdd0f --- /dev/null +++ b/solutions_part2/step2/grading.py @@ -0,0 +1,24 @@ +def comment_grade(grade: int, mode: str = 'normal') -> str : + ''' Provide a feedbac k according to the grade value + + Parameters + ---------- + grade + The amount of distance traveled + mode + The feedback mode, either "normal" (default) or "positive_reinforcement" + + Returns + ------- + comment + The grade feedback + ''' + if grade < 5: + return('Grade too low') + elif grade >= 5: + if mode == 'normal': + return('Grade high enough') + elif mode == 'positive_reinforcement': + return('Well done, keep going!') + else: + raise ValueError('The mode should be "normal" or "positive_reinforcement"') diff --git a/solutions_part2/step3/grading.py b/solutions_part2/step3/grading.py new file mode 100644 index 0000000..e6e0f01 --- /dev/null +++ b/solutions_part2/step3/grading.py @@ -0,0 +1,32 @@ +def comment_grade(grade: int, mode: str = 'normal') -> str : + ''' Provide a feedbac k according to the grade value + + Parameters + ---------- + grade + The amount of distance traveled + mode + The feedback mode, either "normal" (default) or "positive_reinforcement" + + Returns + ------- + comment + The grade feedback + ''' + if grade < 5: + return('Grade too low') + elif grade > 5: + if mode == 'normal': + return('Grade high enough') + elif mode == 'positive_reinforcement': + return('Well done, keep going!') + else: + raise ValueError('The mode should be "normal" or "positive_reinforcement"') + +def test_comments(): + assert comment_grade(0, mode='normal') == 'Grade too low' + assert comment_grade(2, mode='normal') == 'Grade too low' + assert comment_grade(5, mode='normal') == 'Grade high enough' + assert comment_grade(5, mode='positive_reinforcement') == 'Well done, keep going!' + assert comment_grade(10, mode='normal') == 'Grade high enough' + assert comment_grade(10, mode='positive_reinforcement') == 'Well done, keep going!' \ No newline at end of file diff --git a/solutions_part2/step4/grading.py b/solutions_part2/step4/grading.py new file mode 100644 index 0000000..84c7b44 --- /dev/null +++ b/solutions_part2/step4/grading.py @@ -0,0 +1,37 @@ +def comment_grade(grade: int, mode: str = 'normal') -> str : + ''' Provide a feedbac k according to the grade value + + Parameters + ---------- + grade + The amount of distance traveled + mode + The feedback mode, either "normal" (default) or "positive_reinforcement" + + Returns + ------- + comment + The grade feedback + + Examples + -------- + >>> comment_grade(5) + 'Grade high enough!' + ''' + if grade < 5: + return('Grade too low') + elif grade >= 5: + if mode == 'normal': + return('Grade high enough') + elif mode == 'positive_reinforcement': + return('Well done, keep going!') + else: + raise ValueError('The mode should be "normal" or "positive_reinforcement"') + +def test_comments(): + assert comment_grade(0, mode='normal') == 'Grade too low' + assert comment_grade(2, mode='normal') == 'Grade too low' + assert comment_grade(5, mode='normal') == 'Grade high enough' + assert comment_grade(5, mode='positive_reinforcement') == 'Well done, keep going!' + assert comment_grade(10, mode='normal') == 'Grade high enough' + assert comment_grade(10, mode='positive_reinforcement') == 'Well done, keep going!' \ No newline at end of file