From 43b6eb641400e1434d7cb6dfc9822992aedfe4ca Mon Sep 17 00:00:00 2001 From: edoardob90 Date: Tue, 22 Oct 2024 17:08:02 +0200 Subject: [PATCH 01/12] Just use assertions --- tutorial/tests/test_functions.py | 76 +++++++++++++++----------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index 1f49708a..2cf25a63 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -69,6 +69,13 @@ def test_greet( # +class UnsupportedUnitError(Exception): + def __init__(self) -> None: + super().__init__( + "The function should return an error string for unsupported units." + ) + + def reference_calculate_area(length: float, width: float, unit: str = "cm") -> str: """Reference solution for the calculate_area exercise""" # Conversion factors from supported units to centimeters @@ -88,36 +95,29 @@ def reference_calculate_area(length: float, width: float, unit: str = "cm") -> s return f"{area} cm^2" -def test_calculate_area_signature(function_to_test) -> None: - errors = [] - +def validate_calculate_area_signature(function_to_test) -> None: signature = inspect.signature(function_to_test) params = signature.parameters return_annotation = signature.return_annotation - if function_to_test.__doc__ is None: - errors.append("The function is missing a docstring.") - if len(params) != 3: - errors.append("The function should take three arguments.") - if ( - "length" not in params.keys() - or "width" not in params.keys() - or "unit" not in params.keys() - ): - errors.append( - "The function's parameters should be 'length', 'width' and 'unit'." - ) - if "unit" in params.keys() and not ( - params["unit"].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD + assert function_to_test.__doc__ is not None, "The function is missing a docstring." + assert len(params) == 3, "The function should take three arguments." + assert ( + "length" in params.keys() + and "width" in params.keys() + and "unit" in params.keys() + ), "The function's parameters should be 'length', 'width' and 'unit'." + assert ( + "unit" in params.keys() + and params["unit"].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and params["unit"].default == "cm" - ): - errors.append("Argument 'unit' should have a default value 'cm'.") - if any(p.annotation == inspect.Parameter.empty for p in params.values()): - errors.append("The function's parameters should have type hints.") - if return_annotation == inspect.Signature.empty: - errors.append("The function's return value is missing the type hint.") - - assert not errors, errors_to_list(errors) + ), "Argument 'unit' should have a default value 'cm'." + assert all( + p.annotation != inspect.Parameter.empty for p in params.values() + ), "The function's parameters should have type hints." + assert ( + return_annotation != inspect.Signature.empty + ), "The function's return value is missing the type hint." @pytest.mark.parametrize( @@ -137,29 +137,25 @@ def test_calculate_area_result( unit: str, function_to_test, ) -> None: - errors = [] + validate_calculate_area_signature(function_to_test) if unit in ("cm", "m", "mm", "yd", "ft"): result = function_to_test(length, width, unit) - if not isinstance(result, str): - errors.append("The function should return a string.") - if "cm^2" not in result: - errors.append("The result should be in squared centimeters (cm^2).") - if result != reference_calculate_area(length, width, unit): - errors.append("The solution is incorrect.") + assert isinstance(result, str), "The function should return a string." + assert "cm^2" in result, "The result should be in squared centimeters (cm^2)." + assert result == reference_calculate_area( + length, width, unit + ), "The solution is incorrect." else: try: result = function_to_test(length, width, unit) - except KeyError: - errors.append( - "The function should return an error string for unsupported units." - ) + except KeyError as err: + raise UnsupportedUnitError from err else: - if result != f"Invalid unit: {unit}": - errors.append("The error message is incorrectly formatted.") - - assert not errors + assert ( + result == f"Invalid unit: {unit}" + ), "The error message is incorrectly formatted." # From 233f1102a62063f38efd7220da84c8f3fc384791 Mon Sep 17 00:00:00 2001 From: edoardob90 Date: Tue, 26 Nov 2024 12:13:53 +0100 Subject: [PATCH 02/12] Fix exercise 1 --- functions.ipynb | 14 ++++++-- tutorial/tests/test_functions.py | 59 +++++++++++++------------------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/functions.ipynb b/functions.ipynb index d442a36e..e71c44f0 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -443,7 +443,8 @@ "id": "28", "metadata": {}, "source": [ - "Write a Python function called `greet` that takes two parameters: `name` (a string) and `age` (an integer).\n", + "Complete the function below `solution_greet` that should accept two parameters: `name` (a string) and `age` (an integer).\n", + "\n", "The function should return a greeting message in the following format: `\"Hello, ! You are years old.\"`\n", "\n", "
\n", @@ -462,6 +463,15 @@ "%%ipytest\n", "\n", "def solution_greet():\n", + " \"\"\"Creates a personalized greeting message using name and age.\n", + "\n", + " Args:\n", + " name: The person's name to include in the greeting\n", + " age: The person's age in years\n", + "\n", + " Returns:\n", + " - A string in the format \"Hello, ! You are years old.\"\n", + " \"\"\"\n", " return" ] }, @@ -1487,7 +1497,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.15" } }, "nbformat": 4, diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index 3ee36813..de9a2da3 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -12,55 +12,44 @@ def read_data(name: str, data_dir: str = "data") -> pathlib.Path: return (pathlib.Path(__file__).parent / f"{data_dir}/{name}").resolve() -def errors_to_list(errors): - result = "
    " - for error in errors: - result += "
  • " + error + "
  • " - result += "
" - return result - - # # Exercise 1: a `greet` function # def reference_greet(name: str, age: int) -> str: - """Reference solution for the greet exercise""" + """Reference solution for `solution_greet` exercise""" return f"Hello, {name}! You are {age} years old." -@pytest.mark.parametrize( - "name,age", - [ - ("John", 30), - ], -) def test_greet( - name: str, - age: int, function_to_test, ) -> None: - errors = [] + name, age = "Alice", 30 - signature = inspect.signature(function_to_test) - params = signature.parameters - return_annotation = signature.return_annotation + params = inspect.signature(function_to_test).parameters + return_annotation = inspect.signature(function_to_test).return_annotation + + # Check docstring + assert function_to_test.__doc__ is not None, "The function is missing a docstring." + + # Check number and names of parameters + assert len(params) == 2, "The function should take two arguments." + assert ( + "name" in params and "age" in params + ), "The function's parameters should be 'name' and 'age'." + + # Check type hints for parameters + assert all( + p.annotation != inspect.Parameter.empty for p in params.values() + ), "The function's parameters should have type hints." + + # Check return type hint + assert ( + return_annotation != inspect.Signature.empty + ), "The function's return value is missing the type hint." - if function_to_test.__doc__ is None: - errors.append("The function is missing a docstring.") - if len(params) != 2: - errors.append("The function should take two arguments.") - if "name" not in params.keys() or "age" not in params.keys(): - errors.append("The function's parameters should be 'name' and 'age'.") - if any(p.annotation == inspect.Parameter.empty for p in params.values()): - errors.append("The function's parameters should have type hints.") - if return_annotation == inspect.Signature.empty: - errors.append("The function's return value is missing the type hint.") - - # test signature - assert not errors, errors_to_list(errors) - # test result + # Test the return value assert function_to_test(name, age) == reference_greet(name, age) From 343c789f6273df53ab6fe1078028fd4daedf1f65 Mon Sep 17 00:00:00 2001 From: edoardob90 Date: Tue, 26 Nov 2024 12:39:44 +0100 Subject: [PATCH 03/12] Fix exercise 2 --- functions.ipynb | 31 ++++++++++--- tutorial/tests/test_functions.py | 76 ++++++++++++++++++-------------- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/functions.ipynb b/functions.ipynb index e71c44f0..24efebb7 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -482,13 +482,19 @@ "source": [ "## Exercise 2\n", "\n", - "Write a Python function called `calculate_area` that takes three parameters: `length` (a float), `width` (a float), and `unit` (a string with a **default** value of `\"cm\"`).\n", + "Complete the function below `solution_calculate_area` that takes three parameters:\n", + "1. `length` (a float)\n", + "2. `width` (a float)\n", + "3. `unit` (a string with a **default** value of `\"cm\"`)\n", + "\n", "The function should calculate the area of a rectangle based on the given length and width, and return the result **as a string** including the correct, default unit (i.e., `cm^2`).\n", - "If the unit parameter is \"m\", the function should convert the length and width from meters to centimeters before calculating the area.\n", + "The numeric value of the area should be rounded to have **two significant decimal digits**.\n", + "\n", + "If the unit parameter is **not** \"cm\", the function should convert the length and width to centimeters before calculating the area.\n", "\n", "Your solution function **must** handle the following input units (the output unit is **always** `cm^2`):\n", "\n", - "- centimeters (`cm`)\n", + "- centimeters (`cm`, default)\n", "- meters (`m`)\n", "- millimeters (`mm`)\n", "- yards (`yd`)\n", @@ -518,8 +524,23 @@ "source": [ "%%ipytest\n", "\n", - "def solution_calculate_area():\n", - " pass" + "def solution_calculate_area(length: float, width: float, unit: str = \"cm\") -> str:\n", + " \"\"\"Calculates the area of a rectangle and returns it in square centimeters, rounded at two decimal places.\n", + "\n", + " Converts input measurements to centimeters if necessary, based on the unit parameter.\n", + " Supported units are: cm (centimeters), m (meters), mm (millimeters), yd (yards), and ft (feet).\n", + "\n", + " Args:\n", + " length: The length of the rectangle\n", + " width: The width of the rectangle\n", + " unit: The unit of measurement for length and width. Defaults to \"cm\".\n", + " Must be one of: \"cm\", \"m\", \"mm\", \"yd\", \"ft\"\n", + "\n", + " Returns:\n", + " - A string representing the area in square centimeters (cm^2) if the unit is valid.\n", + " If the unit is invalid, returns \"Invalid unit: \".\n", + " \"\"\"\n", + " return \"cm^2\"" ] }, { diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index de9a2da3..a071e8d9 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -58,26 +58,19 @@ def test_greet( # -class UnsupportedUnitError(Exception): - def __init__(self) -> None: - super().__init__( - "The function should return an error string for unsupported units." - ) - - def reference_calculate_area(length: float, width: float, unit: str = "cm") -> str: """Reference solution for the calculate_area exercise""" # Conversion factors from supported units to centimeters units = { "cm": 1.0, "m": 100.0, - "mm": 10.0, + "mm": 0.1, "yd": 91.44, "ft": 30.48, } try: - area = length * width * units[unit] ** 2 + area = round(length * width * units[unit] ** 2, 2) except KeyError: return f"Invalid unit: {unit}" else: @@ -91,15 +84,11 @@ def validate_calculate_area_signature(function_to_test) -> None: assert function_to_test.__doc__ is not None, "The function is missing a docstring." assert len(params) == 3, "The function should take three arguments." - assert ( - "length" in params.keys() - and "width" in params.keys() - and "unit" in params.keys() + assert all( + p in params.keys() for p in ["length", "width", "unit"] ), "The function's parameters should be 'length', 'width' and 'unit'." assert ( - "unit" in params.keys() - and params["unit"].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD - and params["unit"].default == "cm" + params["unit"].default == "cm" ), "Argument 'unit' should have a default value 'cm'." assert all( p.annotation != inspect.Parameter.empty for p in params.values() @@ -117,10 +106,9 @@ def validate_calculate_area_signature(function_to_test) -> None: (10.0, 2.0, "mm"), (2.0, 8.0, "yd"), (5.0, 4.0, "ft"), - (3.0, 5.0, "in"), ], ) -def test_calculate_area_result( +def test_calculate_area_valid_units( length: float, width: float, unit: str, @@ -128,23 +116,43 @@ def test_calculate_area_result( ) -> None: validate_calculate_area_signature(function_to_test) - if unit in ("cm", "m", "mm", "yd", "ft"): - result = function_to_test(length, width, unit) + result = function_to_test(length, width, unit) + expected = reference_calculate_area(length, width, unit) - assert isinstance(result, str), "The function should return a string." - assert "cm^2" in result, "The result should be in squared centimeters (cm^2)." - assert result == reference_calculate_area( - length, width, unit - ), "The solution is incorrect." - else: - try: - result = function_to_test(length, width, unit) - except KeyError as err: - raise UnsupportedUnitError from err - else: - assert ( - result == f"Invalid unit: {unit}" - ), "The error message is incorrectly formatted." + assert isinstance(result, str), "The function should return a string." + assert "cm^2" in result, "The result should be in squared centimeters (cm^2)." + assert result == expected, "The calculated area is incorrect." + + +@pytest.mark.parametrize( + "length,width,unit", + [ + (2.0, 3.0, "in"), + (4.0, 5.0, "km"), + (3.0, 2.0, "mi"), + (1.0, 1.0, ""), + ], +) +def test_calculate_area_invalid_units( + length: float, + width: float, + unit: str, + function_to_test, +) -> None: + """Test the function with invalid units.""" + validate_calculate_area_signature(function_to_test) + result = function_to_test(length, width, unit) + + assert ( + result == f"Invalid unit: {unit}" + ), f"Expected 'Invalid unit: {unit}' for invalid unit" + + +def test_calculate_area_default_unit(function_to_test): + """Test the function with default unit parameter.""" + validate_calculate_area_signature(function_to_test) + result = function_to_test(2.0, 3.0) # default unit must be "cm" + assert result == "6.00 cm^2", "Default unit (cm) calculation is incorrect." # From 93b99c8cd8d7211f9875f83944850925936b18b3 Mon Sep 17 00:00:00 2001 From: edoardob90 Date: Tue, 26 Nov 2024 13:09:03 +0100 Subject: [PATCH 04/12] Fix exercise 3 --- functions.ipynb | 35 +++++++++++++++++++++++++++----- tutorial/tests/test_functions.py | 17 ++++++++-------- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/functions.ipynb b/functions.ipynb index 24efebb7..c4518ac9 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -1088,9 +1088,10 @@ "source": [ "## Exercise 3\n", "\n", - "Write a Python function called `summing_anything` that is able to sum **any** kind of arguments.\n", - "It therefore must accept a *variable* number of arguments.\n", - "You must **not** use the built-in function `sum()` to solve this exercise: you should write your own (generalized) implementation.\n", + "Complete function below `solution_summing_anything` that is able to sum **any** kind of arguments.\n", + "It must accept a *variable* number of arguments.\n", + "\n", + "You must **not** use the built-in function `sum()`: you should write your own (generalized) implementation.\n", "\n", "A few examples of how the function should work:\n", "\n", @@ -1109,8 +1110,32 @@ "source": [ "%%ipytest\n", "\n", - "def solution_summing_anything():\n", - " pass" + "def solution_summing_anything(*args):\n", + " \"\"\"Combines any number of arguments using the appropriate addition operation.\n", + "\n", + " The function determines the appropriate way to combine arguments based on their type:\n", + " - Strings are concatenated\n", + " - Lists are extended\n", + " - Numbers are added\n", + "\n", + " Args:\n", + " *args: Variable number of arguments to combine. Can be of any type\n", + " that supports the + operator (like str, list, int, etc.)\n", + "\n", + " Returns:\n", + " - The combined result using the appropriate operation for the input types.\n", + " Returns the first argument's type's empty value if no arguments are provided\n", + " (e.g., '' for strings, [] for lists, 0 for numbers).\n", + " \"\"\"\n", + " if not args:\n", + " return args\n", + "\n", + " result = args[0]\n", + "\n", + " for item in args[1:]:\n", + " result += item\n", + "\n", + " return result " ] }, { diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index a071e8d9..686a1db6 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -162,7 +162,7 @@ def test_calculate_area_default_unit(function_to_test): def reference_summing_anything(*args: Any) -> Any: """Reference solution for the summing_anything exercise""" - if not args: + if not args or None in args: return args result = args[0] @@ -174,16 +174,17 @@ def reference_summing_anything(*args: Any) -> Any: @pytest.mark.parametrize( - "args,expected", + "args", [ - ((), ()), - ((1, 2, 3), 6), - (([1, 2, 3], [4, 5, 6]), [1, 2, 3, 4, 5, 6]), - (("hello", "world"), "helloworld"), + (()), + ((None, None)), + ((1, 2, 3)), + (([1, 2, 3], [4, 5, 6])), + (("hello", "world")), ], ) -def test_summing_anything(args: Any, expected: Any, function_to_test) -> None: - assert function_to_test(*args) == expected +def test_summing_anything(args: Any, function_to_test) -> None: + assert function_to_test(*args) == reference_summing_anything(*args) # From 6d31c172319145f2201127f67b0c039ef51eb42f Mon Sep 17 00:00:00 2001 From: edoardob90 Date: Wed, 27 Nov 2024 19:22:15 +0100 Subject: [PATCH 05/12] Split Exercise 2 in easier step-by-step --- functions.ipynb | 291 ++++++++++++++++++++----------- tutorial/tests/test_functions.py | 197 ++++++++++++++------- 2 files changed, 323 insertions(+), 165 deletions(-) diff --git a/functions.ipynb b/functions.ipynb index c4518ac9..be97c33f 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -31,6 +31,9 @@ " - [Default values](#Default-values)\n", "- [Exercise 1](#Exercise-1)\n", "- [Exercise 2](#Exercise-2)\n", + " - [Part 1](#Part-1)\n", + " - [Part 2](#Part-2)\n", + " - [Part 3](#Part-3)\n", "- [How Python executes a function](#How-Python-executes-a-function)\n", " - [Calling](#Calling)\n", " - [Executing](#Executing)\n", @@ -421,21 +424,21 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "id": "26", "metadata": {}, - "outputs": [], "source": [ - "%reload_ext tutorial.tests.testsuite" + "## Exercise 1" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "id": "27", "metadata": {}, + "outputs": [], "source": [ - "## Exercise 1" + "%reload_ext tutorial.tests.testsuite" ] }, { @@ -480,72 +483,156 @@ "id": "30", "metadata": {}, "source": [ - "## Exercise 2\n", + "## Exercise 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], + "source": [ + "%reload_ext tutorial.tests.testsuite" + ] + }, + { + "cell_type": "markdown", + "id": "32", + "metadata": {}, + "source": [ + "### Part 1\n", "\n", - "Complete the function below `solution_calculate_area` that takes three parameters:\n", - "1. `length` (a float)\n", - "2. `width` (a float)\n", - "3. `unit` (a string with a **default** value of `\"cm\"`)\n", + "Write a function called `solution_calculate_basic_area` that calculates the area of a rectangle given its length and width (in centimeters).\n", + "The result should be returned as a string with **two** decimal digits, followed by \"cm^2\".\n", "\n", - "The function should calculate the area of a rectangle based on the given length and width, and return the result **as a string** including the correct, default unit (i.e., `cm^2`).\n", - "The numeric value of the area should be rounded to have **two significant decimal digits**.\n", + "Example: `length=2`, `width=3` should return `\"6.00 cm^2\"`\n", "\n", - "If the unit parameter is **not** \"cm\", the function should convert the length and width to centimeters before calculating the area.\n", + "
\n", + "

Note

\n", + " Do not forget to add the correct type hints to the parameters and the return value.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "%%ipytest\n", + "\n", + "def solution_calculate_basic_area(length, width):\n", + " \"\"\"Calculates the area of a rectangle in square centimeters.\n", "\n", - "Your solution function **must** handle the following input units (the output unit is **always** `cm^2`):\n", + " Args:\n", + " length: The length of the rectangle in centimeters\n", + " width: The width of the rectangle in centimeters\n", + "\n", + " Returns:\n", + " - A string representing the area with 2 decimal places,\n", + " followed by \"cm^2\" (e.g., \"6.00 cm^2\")\n", + " \"\"\"\n", + " return" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "### Part 2\n", "\n", - "- centimeters (`cm`, default)\n", - "- meters (`m`)\n", - "- millimeters (`mm`)\n", - "- yards (`yd`)\n", - "- feet (`ft`)\n", + "Extend the previous function, now called `solution_calculate_metric_area`, by adding a `unit` parameter that can be either \"cm\" or \"m\".\n", + "If the unit is \"m\", convert the measurements to centimeters before calculating the area.\n", + "The result should still be in cm^2.\n", "\n", - "If you pass an unsupported unit, the function should **return** a string with the error message: `Invalid unit: `, where `` is the unsupported unit.\n", + "Example: `length=2`, `width=3`, `unit=\"m\"` should return `\"60000.00 cm^2\"` (because 2m × 3m = 6m² = 60000cm²)\n", "\n", "
\n", "

Note

\n", - " Do not forget to write a proper docstring and add the correct type hints to the parameters and the return value.\n", - "
\n", - "
\n", - "

Hints

\n", - "
    \n", - "
  • 1 yd = 91.44 cm
  • \n", - "
  • 1 ft = 30.48 cm
  • \n", - "
\n", + " Do not forget to add the correct type hints to the parameters and the return value.\n", "
" ] }, { "cell_type": "code", "execution_count": null, - "id": "31", + "id": "35", "metadata": {}, "outputs": [], "source": [ "%%ipytest\n", "\n", - "def solution_calculate_area(length: float, width: float, unit: str = \"cm\") -> str:\n", - " \"\"\"Calculates the area of a rectangle and returns it in square centimeters, rounded at two decimal places.\n", + "def solution_calculate_metric_area(length, width, unit):\n", + " \"\"\"Calculates the area of a rectangle, converting from meters if necessary.\n", + "\n", + " Args:\n", + " length: The length of the rectangle\n", + " width: The width of the rectangle\n", + " unit: The unit of measurement (\"cm\" or \"m\", defaults to \"cm\")\n", + "\n", + " Returns:\n", + " - A string representing the area in cm^2 with 2 decimal places.\n", + " If unit is invalid, returns \"Invalid unit: \"\n", + " \"\"\"\n", + " return" + ] + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "### Part 3\n", + "\n", + "Extend the previous function, now called `solution_calculate_area`, to support the following units: cm, m, mm, yd, and ft.\n", + "The calculated area should always be in centimeters, so you need to take care of the appropriate conversions (when needed).\n", + "Keep the same string format for the result.\n", + "\n", + "Use the following conversion factors:\n", + "- $\\text{yd} / \\text{cm}= 91.44$\n", + "- $\\text{ft} / \\text{cm}= 30.48$\n", + "\n", + "Examples:\n", + "- `length=2`, `width=3`, `unit=\"mm\"` should return `\"0.06 cm^2\"`\n", + "- `length=2`, `width=3`, `unit=\"yd\"` should return `\"50167.64 cm^2\"`\n", + "\n", + "
\n", + "

Note

\n", + " Do not forget to add the correct type hints to the parameters and the return value.\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], + "source": [ + "%%ipytest\n", "\n", - " Converts input measurements to centimeters if necessary, based on the unit parameter.\n", - " Supported units are: cm (centimeters), m (meters), mm (millimeters), yd (yards), and ft (feet).\n", + "def solution_calculate_area():\n", + " \"\"\"Calculates the area of a rectangle with support for multiple units.\n", "\n", " Args:\n", " length: The length of the rectangle\n", " width: The width of the rectangle\n", - " unit: The unit of measurement for length and width. Defaults to \"cm\".\n", - " Must be one of: \"cm\", \"m\", \"mm\", \"yd\", \"ft\"\n", + " unit: The unit of measurement, one of:\n", + " \"cm\" (default), \"m\", \"mm\", \"yd\", \"ft\"\n", "\n", " Returns:\n", - " - A string representing the area in square centimeters (cm^2) if the unit is valid.\n", - " If the unit is invalid, returns \"Invalid unit: \".\n", + " - A string representing the area in cm^2 with 2 decimal places.\n", + " If unit is invalid, returns \"Invalid unit: \"\n", " \"\"\"\n", - " return \"cm^2\"" + " return" ] }, { "cell_type": "markdown", - "id": "32", + "id": "38", "metadata": {}, "source": [ "---" @@ -553,7 +640,7 @@ }, { "cell_type": "markdown", - "id": "33", + "id": "39", "metadata": { "tags": [] }, @@ -563,7 +650,7 @@ }, { "cell_type": "markdown", - "id": "34", + "id": "40", "metadata": { "tags": [] }, @@ -608,7 +695,7 @@ }, { "cell_type": "markdown", - "id": "35", + "id": "41", "metadata": { "tags": [] }, @@ -618,7 +705,7 @@ }, { "cell_type": "markdown", - "id": "36", + "id": "42", "metadata": {}, "source": [ "The following might seem trivial: could we assign the same variable name to two different values?\n", @@ -671,7 +758,7 @@ { "cell_type": "code", "execution_count": null, - "id": "37", + "id": "43", "metadata": { "tags": [] }, @@ -691,7 +778,7 @@ }, { "cell_type": "markdown", - "id": "38", + "id": "44", "metadata": {}, "source": [ "In this example, `x` is a **global** variable and is accessible from anywhere in the program. `x` is also a **local** variable to the `function`, and is accessible from within the function's body.\n", @@ -701,7 +788,7 @@ }, { "cell_type": "markdown", - "id": "39", + "id": "45", "metadata": {}, "source": [ "The `nonlocal` keyword is used to access a variable in the **nearest enclosing scope** that is **not** global.\n", @@ -713,7 +800,7 @@ }, { "cell_type": "markdown", - "id": "40", + "id": "46", "metadata": {}, "source": [ "Look at the following example:" @@ -721,7 +808,7 @@ }, { "cell_type": "markdown", - "id": "41", + "id": "47", "metadata": {}, "source": [ "```python\n", @@ -749,7 +836,7 @@ }, { "cell_type": "markdown", - "id": "42", + "id": "48", "metadata": {}, "source": [ "The `global` keyword is used to access a global variable and modify its value from within a function.\n", @@ -760,7 +847,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43", + "id": "49", "metadata": { "tags": [] }, @@ -782,7 +869,7 @@ }, { "cell_type": "markdown", - "id": "44", + "id": "50", "metadata": {}, "source": [ "Even though you can access and modify variables from different scope, it's not considered a good practice. When a function makes use of `global` or `nonlocal`, or when modifying a mutable type in-place, it's like when a function modifies its own arguments. It's a **side-effect** that should be generally avoided.\n", @@ -792,7 +879,7 @@ }, { "cell_type": "markdown", - "id": "45", + "id": "51", "metadata": {}, "source": [ "# `*args` and `**kwargs`" @@ -800,7 +887,7 @@ }, { "cell_type": "markdown", - "id": "46", + "id": "52", "metadata": {}, "source": [ "In the [Basic datatypes](./basic_datatypes.ipynb#Unpacking) section we saw that iterables (tuples and lists) support **unpacking**. You can exploit unpacking to make a **parallel assignment**.\n", @@ -820,7 +907,7 @@ { "cell_type": "code", "execution_count": null, - "id": "47", + "id": "53", "metadata": {}, "outputs": [], "source": [ @@ -835,7 +922,7 @@ }, { "cell_type": "markdown", - "id": "48", + "id": "54", "metadata": {}, "source": [ "The only difference with unpacking is: all the optional positional arguments are collected by `*args` **in a tuple** and not in a list.\n", @@ -848,7 +935,7 @@ }, { "cell_type": "markdown", - "id": "49", + "id": "55", "metadata": {}, "source": [ "One importat rule:\n", @@ -876,7 +963,7 @@ { "cell_type": "code", "execution_count": null, - "id": "50", + "id": "56", "metadata": {}, "outputs": [], "source": [ @@ -888,7 +975,7 @@ }, { "cell_type": "markdown", - "id": "51", + "id": "57", "metadata": {}, "source": [ "The following **cannot** work\n", @@ -906,7 +993,7 @@ }, { "cell_type": "markdown", - "id": "52", + "id": "58", "metadata": {}, "source": [ "Remember that functions' arguments can be either **positional** or **keyword** arguments:" @@ -915,7 +1002,7 @@ { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "59", "metadata": { "tags": [] }, @@ -928,7 +1015,7 @@ }, { "cell_type": "markdown", - "id": "54", + "id": "60", "metadata": {}, "source": [ "We can call the function with" @@ -937,7 +1024,7 @@ { "cell_type": "code", "execution_count": null, - "id": "55", + "id": "61", "metadata": { "tags": [] }, @@ -952,7 +1039,7 @@ }, { "cell_type": "markdown", - "id": "56", + "id": "62", "metadata": {}, "source": [ "After `*args` there can be **no additional positional arguments** but we might have some (or no) keyword arguments.\n", @@ -963,7 +1050,7 @@ { "cell_type": "code", "execution_count": null, - "id": "57", + "id": "63", "metadata": { "tags": [] }, @@ -983,7 +1070,7 @@ }, { "cell_type": "markdown", - "id": "58", + "id": "64", "metadata": {}, "source": [ "Remember: `d` is a **required** keyword argument because we didn't supply a default value in the function definition.\n", @@ -994,7 +1081,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "65", "metadata": { "tags": [] }, @@ -1006,7 +1093,7 @@ { "cell_type": "code", "execution_count": null, - "id": "60", + "id": "66", "metadata": { "tags": [] }, @@ -1018,7 +1105,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "67", "metadata": { "tags": [] }, @@ -1029,7 +1116,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "68", "metadata": {}, "source": [ "We can even **omit** mandatory positional arguments\n", @@ -1046,7 +1133,7 @@ }, { "cell_type": "markdown", - "id": "63", + "id": "69", "metadata": {}, "source": [ "Or we can force **no positional arguments at all**:\n", @@ -1061,7 +1148,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "70", "metadata": {}, "source": [ "You can see that with iterables unpacking and the two `*`/`**` operators, Python is showing all its versatility when it comes to writing your own function.\n", @@ -1074,7 +1161,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "71", "metadata": {}, "outputs": [], "source": [ @@ -1083,7 +1170,7 @@ }, { "cell_type": "markdown", - "id": "66", + "id": "72", "metadata": {}, "source": [ "## Exercise 3\n", @@ -1104,7 +1191,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "73", "metadata": {}, "outputs": [], "source": [ @@ -1140,7 +1227,7 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "74", "metadata": {}, "source": [ "---" @@ -1148,7 +1235,7 @@ }, { "cell_type": "markdown", - "id": "69", + "id": "75", "metadata": {}, "source": [ "## Quiz on functions" @@ -1157,7 +1244,7 @@ { "cell_type": "code", "execution_count": null, - "id": "70", + "id": "76", "metadata": { "tags": [] }, @@ -1169,7 +1256,7 @@ }, { "cell_type": "markdown", - "id": "71", + "id": "77", "metadata": { "tags": [] }, @@ -1185,7 +1272,7 @@ }, { "cell_type": "markdown", - "id": "72", + "id": "78", "metadata": { "jp-MarkdownHeadingCollapsed": true }, @@ -1195,7 +1282,7 @@ }, { "cell_type": "markdown", - "id": "73", + "id": "79", "metadata": { "tags": [] }, @@ -1212,7 +1299,7 @@ { "cell_type": "code", "execution_count": null, - "id": "74", + "id": "80", "metadata": { "tags": [] }, @@ -1229,7 +1316,7 @@ }, { "cell_type": "markdown", - "id": "75", + "id": "81", "metadata": { "jp-MarkdownHeadingCollapsed": true }, @@ -1247,7 +1334,7 @@ { "cell_type": "code", "execution_count": null, - "id": "76", + "id": "82", "metadata": { "tags": [] }, @@ -1264,7 +1351,7 @@ }, { "cell_type": "markdown", - "id": "77", + "id": "83", "metadata": { "jp-MarkdownHeadingCollapsed": true, "tags": [] @@ -1275,7 +1362,7 @@ }, { "cell_type": "markdown", - "id": "78", + "id": "84", "metadata": { "tags": [] }, @@ -1285,7 +1372,7 @@ }, { "cell_type": "markdown", - "id": "79", + "id": "85", "metadata": {}, "source": [ "You have a range of numbers `136760-595730` and need to count how many valid password it contains. A valid password must meet **all** the following criteria:\n", @@ -1306,7 +1393,7 @@ }, { "cell_type": "markdown", - "id": "80", + "id": "86", "metadata": {}, "source": [ "\n", @@ -1317,7 +1404,7 @@ }, { "cell_type": "markdown", - "id": "81", + "id": "87", "metadata": {}, "source": [ "\n", @@ -1329,7 +1416,7 @@ { "cell_type": "code", "execution_count": null, - "id": "82", + "id": "88", "metadata": {}, "outputs": [], "source": [ @@ -1344,7 +1431,7 @@ }, { "cell_type": "markdown", - "id": "83", + "id": "89", "metadata": { "tags": [] }, @@ -1354,7 +1441,7 @@ }, { "cell_type": "markdown", - "id": "84", + "id": "90", "metadata": {}, "source": [ "You have a new rule: **at least** two adjacent matching digits **must not be part of a larger group of matching digits**.\n", @@ -1376,7 +1463,7 @@ { "cell_type": "code", "execution_count": null, - "id": "85", + "id": "91", "metadata": {}, "outputs": [], "source": [ @@ -1391,7 +1478,7 @@ }, { "cell_type": "markdown", - "id": "86", + "id": "92", "metadata": { "jp-MarkdownHeadingCollapsed": true, "tags": [] @@ -1402,7 +1489,7 @@ }, { "cell_type": "markdown", - "id": "87", + "id": "93", "metadata": {}, "source": [ "#### Part 1" @@ -1410,7 +1497,7 @@ }, { "cell_type": "markdown", - "id": "88", + "id": "94", "metadata": {}, "source": [ "You have a list of buckets, each containing some items labeled from `a` to `z`, or from `A` to `Z`. Each bucket is split into **two** equally sized compartments.\n", @@ -1441,7 +1528,7 @@ }, { "cell_type": "markdown", - "id": "89", + "id": "95", "metadata": {}, "source": [ "
\n", @@ -1451,7 +1538,7 @@ }, { "cell_type": "markdown", - "id": "90", + "id": "96", "metadata": {}, "source": [ "
\n", @@ -1462,7 +1549,7 @@ { "cell_type": "code", "execution_count": null, - "id": "91", + "id": "97", "metadata": { "tags": [] }, @@ -1478,7 +1565,7 @@ }, { "cell_type": "markdown", - "id": "92", + "id": "98", "metadata": { "jp-MarkdownHeadingCollapsed": true }, @@ -1488,7 +1575,7 @@ }, { "cell_type": "markdown", - "id": "93", + "id": "99", "metadata": {}, "source": [ "You are told that you should not care about the priority of **every item**, but only of a \"special item\" that is common to groups of **three buckets**.\n", @@ -1514,7 +1601,7 @@ { "cell_type": "code", "execution_count": null, - "id": "94", + "id": "100", "metadata": {}, "outputs": [], "source": [ diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index 686a1db6..29fc84f9 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -58,101 +58,172 @@ def test_greet( # -def reference_calculate_area(length: float, width: float, unit: str = "cm") -> str: - """Reference solution for the calculate_area exercise""" - # Conversion factors from supported units to centimeters - units = { - "cm": 1.0, - "m": 100.0, - "mm": 0.1, - "yd": 91.44, - "ft": 30.48, - } +# Part 1 +def reference_calculate_basic_area(length: float, width: float) -> str: + """Reference solution for Part 1: basic area calculation.""" + area = length * width + return f"{area:.2f} cm^2" - try: - area = round(length * width * units[unit] ** 2, 2) - except KeyError: + +def validate_basic_area_signature(function_to_test) -> None: + """Validate signature of the basic area calculation function.""" + signature = inspect.signature(function_to_test) + params = signature.parameters + return_annotation = signature.return_annotation + + assert function_to_test.__doc__ is not None, "The function is missing a docstring." + assert ( + len(params) == 2 + ), "The function should take exactly two arguments (length and width)." + assert all( + p in params.keys() for p in ["length", "width"] + ), "The function's parameters should be 'length' and 'width'." + assert all( + p.annotation is float for p in params.values() + ), "Both parameters should be annotated as float." + assert return_annotation is str, "The return type should be annotated as str." + + +@pytest.mark.parametrize( + "length,width", + [ + (2.0, 3.0), + (5.0, 4.0), + (1.5, 2.5), + (0.1, 0.1), + ], +) +def test_calculate_basic_area(length: float, width: float, function_to_test): + validate_basic_area_signature(function_to_test) + expected = reference_calculate_basic_area(length, width) + result = function_to_test(length, width) + assert isinstance(result, str), "Result should be a string" + assert expected == result, "Incorrect area calculation or formatting" + + +# Part 2 + + +def reference_calculate_metric_area( + length: float, width: float, unit: str = "cm" +) -> str: + """Reference solution for Part 2: metric units only.""" + if unit not in ("cm", "m"): return f"Invalid unit: {unit}" - else: - return f"{area} cm^2" + if unit == "m": + length *= 100 + width *= 100 + + area = length * width + return f"{area:.2f} cm^2" -def validate_calculate_area_signature(function_to_test) -> None: + +def validate_metric_area_signature(function_to_test) -> None: + """Validate signature of the metric area calculation function.""" signature = inspect.signature(function_to_test) params = signature.parameters return_annotation = signature.return_annotation assert function_to_test.__doc__ is not None, "The function is missing a docstring." - assert len(params) == 3, "The function should take three arguments." + assert ( + len(params) == 3 + ), "The function should take three arguments (length, width, and unit)." assert all( p in params.keys() for p in ["length", "width", "unit"] ), "The function's parameters should be 'length', 'width' and 'unit'." assert ( - params["unit"].default == "cm" - ), "Argument 'unit' should have a default value 'cm'." - assert all( - p.annotation != inspect.Parameter.empty for p in params.values() - ), "The function's parameters should have type hints." + params["length"].annotation is float + ), "Parameter 'length' should be annotated as float." assert ( - return_annotation != inspect.Signature.empty - ), "The function's return value is missing the type hint." + params["width"].annotation is float + ), "Parameter 'width' should be annotated as float." + assert ( + params["unit"].annotation is str + ), "Parameter 'unit' should be annotated as str." + assert ( + params["unit"].default == "cm" + ), "Parameter 'unit' should have a default value of 'cm'." + assert return_annotation is str, "The return type should be annotated as str." @pytest.mark.parametrize( "length,width,unit", [ (2.0, 3.0, "cm"), - (4.0, 5.0, "m"), - (10.0, 2.0, "mm"), - (2.0, 8.0, "yd"), - (5.0, 4.0, "ft"), + (2.0, 3.0, "m"), + (1.5, 2.0, "cm"), + (1.5, 2.0, "m"), ], ) -def test_calculate_area_valid_units( - length: float, - width: float, - unit: str, - function_to_test, -) -> None: - validate_calculate_area_signature(function_to_test) - +def test_calculate_metric_area(length, width, unit, expected, function_to_test): + validate_metric_area_signature(function_to_test) + expected = reference_calculate_metric_area(length, width, unit) result = function_to_test(length, width, unit) - expected = reference_calculate_area(length, width, unit) + assert isinstance(result, str), "Result should be a string" + assert expected == result, "Incorrect area calculation or formatting" + + +# Part 3 + + +def reference_calculate_area(length: float, width: float, unit: str = "cm") -> str: + """Reference solution for Part 3: all units.""" + conversions = {"cm": 1, "m": 100, "mm": 0.1, "yd": 91.44, "ft": 30.48} + + try: + factor = conversions[unit] + except KeyError: + return f"Invalid unit: {unit}" - assert isinstance(result, str), "The function should return a string." - assert "cm^2" in result, "The result should be in squared centimeters (cm^2)." - assert result == expected, "The calculated area is incorrect." + area = length * width * factor**2 + return f"{area:.2f} cm^2" + + +def validate_area_signature(function_to_test) -> None: + """Validate signature of the full area calculation function.""" + signature = inspect.signature(function_to_test) + params = signature.parameters + return_annotation = signature.return_annotation + + assert function_to_test.__doc__ is not None, "The function is missing a docstring." + assert ( + len(params) == 3 + ), "The function should take three arguments (length, width, and unit)." + assert all( + p in params.keys() for p in ["length", "width", "unit"] + ), "The function's parameters should be 'length', 'width' and 'unit'." + assert ( + params["length"].annotation is float + ), "Parameter 'length' should be annotated as float." + assert ( + params["width"].annotation is float + ), "Parameter 'width' should be annotated as float." + assert ( + params["unit"].annotation is str + ), "Parameter 'unit' should be annotated as str." + assert ( + params["unit"].default == "cm" + ), "Parameter 'unit' should have a default value of 'cm'." + assert return_annotation is str, "The return type should be annotated as str." @pytest.mark.parametrize( "length,width,unit", [ - (2.0, 3.0, "in"), - (4.0, 5.0, "km"), - (3.0, 2.0, "mi"), - (1.0, 1.0, ""), + (2.0, 3.0, "cm"), + (2.0, 3.0, "m"), + (2.0, 3.0, "mm"), + (2.0, 3.0, "yd"), + (2.0, 3.0, "ft"), ], ) -def test_calculate_area_invalid_units( - length: float, - width: float, - unit: str, - function_to_test, -) -> None: - """Test the function with invalid units.""" - validate_calculate_area_signature(function_to_test) +def test_calculate_area(length, width, unit, function_to_test): + validate_area_signature(function_to_test) result = function_to_test(length, width, unit) - - assert ( - result == f"Invalid unit: {unit}" - ), f"Expected 'Invalid unit: {unit}' for invalid unit" - - -def test_calculate_area_default_unit(function_to_test): - """Test the function with default unit parameter.""" - validate_calculate_area_signature(function_to_test) - result = function_to_test(2.0, 3.0) # default unit must be "cm" - assert result == "6.00 cm^2", "Default unit (cm) calculation is incorrect." + expected = reference_calculate_area(length, width, unit) + assert isinstance(result, str), "Result should be a string" + assert expected == result, "Incorrect area calculation or formatting" # From 72e8a655f67c76ad8db40cf48c801ac2b08c4846 Mon Sep 17 00:00:00 2001 From: edoardob90 Date: Wed, 27 Nov 2024 23:17:25 +0100 Subject: [PATCH 06/12] Small fixes and rewordings/cleanup the notebook --- functions.ipynb | 264 +++++++++++++++---------------- tutorial/tests/test_functions.py | 12 +- 2 files changed, 135 insertions(+), 141 deletions(-) diff --git a/functions.ipynb b/functions.ipynb index be97c33f..d4d50cdc 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -13,7 +13,7 @@ "id": "1", "metadata": {}, "source": [ - "# Table of Contents\n", + "## Table of Contents\n", "\n", "- [References](#References)\n", "- [Functions are building blocks](#Functions-are-building-blocks)\n", @@ -40,14 +40,11 @@ " - [Returning](#Returning)\n", "- [The scope of a function](#The-scope-of-a-function)\n", " - [Different types of scope](#Different-types-of-scope)\n", - "- [Global scope](#Global-scope)\n", "- [`*args` and `**kwargs`](#*args-and-**kwargs)\n", "- [Exercise 3](#Exercise-3)\n", "- [Quiz on functions](#Quiz-on-functions)\n", "- [Bonus exercises](#Bonus-exercises)\n", " - [Longest consecutive sequence](#Longest-consecutive-sequence)\n", - " - [Example 1](#Example-1)\n", - " - [Example 2](#Example-2)\n", " - [Part 2](#Part-2)\n", " - [Password validator](#Password-validator)\n", " - [Part 1](#Part-1)\n", @@ -159,8 +156,6 @@ "- The Python keyword to indicate that a name is a function's name is `def`, and it's a **reserved keyword**\n", "- The signature is what allows you to call the function and pass it arguments\n", "\n", - "---\n", - "\n", "For example:\n", "\n", "```python\n", @@ -175,8 +170,6 @@ "id": "11", "metadata": {}, "source": [ - "---\n", - "\n", "### Type hints\n", "\n", "Python doesn't require you to explicitly declare a variable's type. However, when defining functions, you can add **type hints** in the function's signature.\n", @@ -187,8 +180,6 @@ "

Warning

The Python interpreter does not enforce type hints and will not check them during runtime. Type hints are primarily intended for improving code readability, serving as documentation for developers, and making IDEs much more helpful when writing code.\n", "
\n", "\n", - "---\n", - "\n", "They are specified using the `typing` module and are added in the function definition using colons followed by the expected type, right after the parameter name.\n", "\n", "For example:\n", @@ -255,8 +246,6 @@ " return product\n", "```\n", "\n", - "---\n", - "\n", "A few more examples of simple functions:\n", "\n", "```python\n", @@ -452,7 +441,7 @@ "\n", "
\n", "

Note

\n", - " Do not forget to write a proper docstring and add the correct type hints to the parameters and the return value.\n", + " Do not forget to add the correct type hints to the parameters.\n", "
" ] }, @@ -465,7 +454,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_greet():\n", + "def solution_greet() -> str:\n", " \"\"\"Creates a personalized greeting message using name and age.\n", "\n", " Args:\n", @@ -504,8 +493,11 @@ "### Part 1\n", "\n", "Write a function called `solution_calculate_basic_area` that calculates the area of a rectangle given its length and width (in centimeters).\n", + "\n", "The result should be returned as a string with **two** decimal digits, followed by \"cm^2\".\n", "\n", + "*Hint:* you can use the [built-in function](https://docs.python.org/3/library/functions.html#round) `round(number, ndigits)` function to round the floating point `number` to a given number expressed by `ndigits`.\n", + "\n", "Example: `length=2`, `width=3` should return `\"6.00 cm^2\"`\n", "\n", "
\n", @@ -633,14 +625,6 @@ { "cell_type": "markdown", "id": "38", - "metadata": {}, - "source": [ - "---" - ] - }, - { - "cell_type": "markdown", - "id": "39", "metadata": { "tags": [] }, @@ -650,7 +634,7 @@ }, { "cell_type": "markdown", - "id": "40", + "id": "39", "metadata": { "tags": [] }, @@ -663,7 +647,6 @@ "2. **Executing**\n", "3. **Returning**\n", "\n", - "---\n", "\n", "### Calling\n", "\n", @@ -671,7 +654,6 @@ "- A function call frame contains information about the function call, such as the function name, arguments, local variables, and the return address (i.e., where to return control when the function returns)\n", "- The function call frame is pushed onto the top of the call stack\n", "\n", - "---\n", "\n", "### Executing\n", "\n", @@ -681,7 +663,6 @@ "- The function can access its parameters, declare local variables, and perform any other operations specified in the body\n", "- The function can call other functions, which will create their own function call frames and push them onto the call stack\n", "\n", - "---\n", "\n", "### Returning\n", "\n", @@ -695,7 +676,7 @@ }, { "cell_type": "markdown", - "id": "41", + "id": "40", "metadata": { "tags": [] }, @@ -705,22 +686,16 @@ }, { "cell_type": "markdown", - "id": "42", + "id": "41", "metadata": {}, "source": [ - "The following might seem trivial: could we assign the same variable name to two different values?\n", - "\n", - "The obvious (and right) answer is **no**. If `x` is `3` and `2` at the same time, how should Python evaluate an expression containing `x`?\n", - "\n", - "There is, however, a workaround to this and it involves the concept of **scope**.\n", + "The following question might seem a trivial one: *could we assign the **same** variable name to **two** different values?*\n", "\n", - "
\n", - "

Important

You should not the scope to have a variable with two values at the same time, but it's an important concept to understand unexpected behaviours.\n", - "
\n", + "The obvious (and right) answer is **no**. If `x=3` and we can also have `x=2` at the same time, how should Python evaluate an expression like `x + 1`?\n", "\n", - "---\n", + "There is, however, a workaround and it involves the concept of **scope**.\n", "\n", - "Look at the following lines of valid Python code:\n", + "Let's look at the following lines of valid Python code:\n", "\n", "```python\n", "x = \"Hello World\"\n", @@ -733,23 +708,54 @@ "print(f\"Outside 'func', x has the value {x}\")\n", "```\n", "\n", - "What output do you expect?\n", + "What output do you expect? Try it out below:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42", + "metadata": {}, + "outputs": [], + "source": [ + "x = \"Hello World\"\n", + "\n", + "def func():\n", + " x = 2\n", + " return f\"Inside 'func', x has the value {x}\"\n", "\n", + "print(func())\n", + "print(f\"Outside 'func', x has the value {x}\")" + ] + }, + { + "cell_type": "markdown", + "id": "43", + "metadata": {}, + "source": [ "Does `x` really have two simultaneous values?\n", "\n", "**Not really:** the reason is that `x` **within the function's body** and `x` **in the outside code** live in two separates **scopes**. The function body has a **local scope**, while all the code outside belongs to the **global scope**.\n", "\n", - "---\n", - "\n", + "
\n", + "

Important

You should not use scopes to have a variable with two values at the same time, but it's an important concept to understand and prevent unexpected behaviours (or bugs).\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "44", + "metadata": {}, + "source": [ "### Different types of scope\n", "\n", "We can define the scope as **the region of a program where a variable can be accessed**.\n", "\n", - "1. **Global scope**: Variables declared at the top level of a program or module are in the global scope. They are accessible from anywhere in the program or module.\n", + "1. **Global scope**: Variables declared at the top level of a program or module are in the **global** scope. They are accessible from anywhere in the program or module.\n", "\n", - "2. **Local scope**: Variables declared inside a function are in the local scope. They are only accessible from within the function and are discarded when the function returns.\n", + "2. **Local scope**: Variables declared inside a function are in the **local** scope. They are only accessible from within the function and are discarded when the function returns.\n", "\n", - "3. **Non-local scope**: Variables defined in an outer function and declared as `nonlocal` in the inner nested function are in the non-local scope. They are accessible both from the outer function and from within the nested function.\n", + "3. **Non-local scope**: Variables defined in an outer function and declared as `nonlocal` in the *innermost* function are in the **non-local** scope. They are accessible both from the outer function and from within the nested function.\n", "\n", "\n", "Here's an example to illustrate the different scopes:" @@ -758,7 +764,7 @@ { "cell_type": "code", "execution_count": null, - "id": "43", + "id": "45", "metadata": { "tags": [] }, @@ -778,17 +784,15 @@ }, { "cell_type": "markdown", - "id": "44", + "id": "46", "metadata": {}, "source": [ - "In this example, `x` is a **global** variable and is accessible from anywhere in the program. `x` is also a **local** variable to the `function`, and is accessible from within the function's body.\n", - "\n", - "---" + "In this example, `x` is a **global** variable and is accessible from anywhere in the program. `x` is also a **local** variable to the `function`, and is accessible from within the function's body.\n" ] }, { "cell_type": "markdown", - "id": "45", + "id": "47", "metadata": {}, "source": [ "The `nonlocal` keyword is used to access a variable in the **nearest enclosing scope** that is **not** global.\n", @@ -800,7 +804,7 @@ }, { "cell_type": "markdown", - "id": "46", + "id": "48", "metadata": {}, "source": [ "Look at the following example:" @@ -808,7 +812,7 @@ }, { "cell_type": "markdown", - "id": "47", + "id": "49", "metadata": {}, "source": [ "```python\n", @@ -836,7 +840,7 @@ }, { "cell_type": "markdown", - "id": "48", + "id": "50", "metadata": {}, "source": [ "The `global` keyword is used to access a global variable and modify its value from within a function.\n", @@ -847,7 +851,7 @@ { "cell_type": "code", "execution_count": null, - "id": "49", + "id": "51", "metadata": { "tags": [] }, @@ -869,17 +873,17 @@ }, { "cell_type": "markdown", - "id": "50", + "id": "52", "metadata": {}, "source": [ - "Even though you can access and modify variables from different scope, it's not considered a good practice. When a function makes use of `global` or `nonlocal`, or when modifying a mutable type in-place, it's like when a function modifies its own arguments. It's a **side-effect** that should be generally avoided.\n", + "Even though you can access and modify variables from a different scope, it's not considered a good practice. When a function makes use of `global` or `nonlocal`, or when modifying a mutable type in-place, it's like when a function modifies its own arguments. It's a **side-effect** that should be generally avoided.\n", "\n", "It's is considered a much better programming practice to make use of a function's return values instead of resorting to the scoping keywords." ] }, { "cell_type": "markdown", - "id": "51", + "id": "53", "metadata": {}, "source": [ "# `*args` and `**kwargs`" @@ -887,10 +891,10 @@ }, { "cell_type": "markdown", - "id": "52", + "id": "54", "metadata": {}, "source": [ - "In the [Basic datatypes](./basic_datatypes.ipynb#Unpacking) section we saw that iterables (tuples and lists) support **unpacking**. You can exploit unpacking to make a **parallel assignment**.\n", + "In the [Basic datatypes](./basic_datatypes.ipynb#Unpacking) section we saw that iterables (e.g., tuples, lists, strings) support **unpacking**. You can exploit unpacking to make a **parallel assignment**.\n", "\n", "A reminder:\n", "\n", @@ -907,7 +911,7 @@ { "cell_type": "code", "execution_count": null, - "id": "53", + "id": "55", "metadata": {}, "outputs": [], "source": [ @@ -922,7 +926,7 @@ }, { "cell_type": "markdown", - "id": "54", + "id": "56", "metadata": {}, "source": [ "The only difference with unpacking is: all the optional positional arguments are collected by `*args` **in a tuple** and not in a list.\n", @@ -935,7 +939,7 @@ }, { "cell_type": "markdown", - "id": "55", + "id": "57", "metadata": {}, "source": [ "One importat rule:\n", @@ -963,7 +967,7 @@ { "cell_type": "code", "execution_count": null, - "id": "56", + "id": "58", "metadata": {}, "outputs": [], "source": [ @@ -975,7 +979,7 @@ }, { "cell_type": "markdown", - "id": "57", + "id": "59", "metadata": {}, "source": [ "The following **cannot** work\n", @@ -993,7 +997,7 @@ }, { "cell_type": "markdown", - "id": "58", + "id": "60", "metadata": {}, "source": [ "Remember that functions' arguments can be either **positional** or **keyword** arguments:" @@ -1002,7 +1006,7 @@ { "cell_type": "code", "execution_count": null, - "id": "59", + "id": "61", "metadata": { "tags": [] }, @@ -1015,7 +1019,7 @@ }, { "cell_type": "markdown", - "id": "60", + "id": "62", "metadata": {}, "source": [ "We can call the function with" @@ -1024,7 +1028,7 @@ { "cell_type": "code", "execution_count": null, - "id": "61", + "id": "63", "metadata": { "tags": [] }, @@ -1039,7 +1043,7 @@ }, { "cell_type": "markdown", - "id": "62", + "id": "64", "metadata": {}, "source": [ "After `*args` there can be **no additional positional arguments** but we might have some (or no) keyword arguments.\n", @@ -1050,7 +1054,7 @@ { "cell_type": "code", "execution_count": null, - "id": "63", + "id": "65", "metadata": { "tags": [] }, @@ -1070,7 +1074,7 @@ }, { "cell_type": "markdown", - "id": "64", + "id": "66", "metadata": {}, "source": [ "Remember: `d` is a **required** keyword argument because we didn't supply a default value in the function definition.\n", @@ -1081,7 +1085,7 @@ { "cell_type": "code", "execution_count": null, - "id": "65", + "id": "67", "metadata": { "tags": [] }, @@ -1093,7 +1097,7 @@ { "cell_type": "code", "execution_count": null, - "id": "66", + "id": "68", "metadata": { "tags": [] }, @@ -1105,7 +1109,7 @@ { "cell_type": "code", "execution_count": null, - "id": "67", + "id": "69", "metadata": { "tags": [] }, @@ -1116,7 +1120,7 @@ }, { "cell_type": "markdown", - "id": "68", + "id": "70", "metadata": {}, "source": [ "We can even **omit** mandatory positional arguments\n", @@ -1133,7 +1137,7 @@ }, { "cell_type": "markdown", - "id": "69", + "id": "71", "metadata": {}, "source": [ "Or we can force **no positional arguments at all**:\n", @@ -1148,20 +1152,26 @@ }, { "cell_type": "markdown", - "id": "70", + "id": "72", "metadata": {}, "source": [ "You can see that with iterables unpacking and the two `*`/`**` operators, Python is showing all its versatility when it comes to writing your own function.\n", "\n", - "If all this seems confusing, **try to experiment with these concepts** here in the notebook to better understand the behaviour. Create and call all the functions you want and check if their outputs is what you expect.\n", - "\n", - "---" + "If all this seems confusing, **try to experiment with these concepts** here in the notebook to better understand the behaviour. Create and call all the functions you want and check if their outputs is what you expect.\n" + ] + }, + { + "cell_type": "markdown", + "id": "73", + "metadata": {}, + "source": [ + "## Exercise 3" ] }, { "cell_type": "code", "execution_count": null, - "id": "71", + "id": "74", "metadata": {}, "outputs": [], "source": [ @@ -1170,11 +1180,9 @@ }, { "cell_type": "markdown", - "id": "72", + "id": "75", "metadata": {}, "source": [ - "## Exercise 3\n", - "\n", "Complete function below `solution_summing_anything` that is able to sum **any** kind of arguments.\n", "It must accept a *variable* number of arguments.\n", "\n", @@ -1191,7 +1199,7 @@ { "cell_type": "code", "execution_count": null, - "id": "73", + "id": "76", "metadata": {}, "outputs": [], "source": [ @@ -1201,9 +1209,9 @@ " \"\"\"Combines any number of arguments using the appropriate addition operation.\n", "\n", " The function determines the appropriate way to combine arguments based on their type:\n", - " - Strings are concatenated\n", - " - Lists are extended\n", - " - Numbers are added\n", + " - Strings are concatenated\n", + " - Lists are extended\n", + " - Numbers are added\n", "\n", " Args:\n", " *args: Variable number of arguments to combine. Can be of any type\n", @@ -1214,37 +1222,21 @@ " Returns the first argument's type's empty value if no arguments are provided\n", " (e.g., '' for strings, [] for lists, 0 for numbers).\n", " \"\"\"\n", - " if not args:\n", - " return args\n", - "\n", - " result = args[0]\n", - "\n", - " for item in args[1:]:\n", - " result += item\n", - "\n", - " return result " - ] - }, - { - "cell_type": "markdown", - "id": "74", - "metadata": {}, - "source": [ - "---" + " return" ] }, { "cell_type": "markdown", - "id": "75", + "id": "77", "metadata": {}, "source": [ - "## Quiz on functions" + "# Quiz on functions" ] }, { "cell_type": "code", "execution_count": null, - "id": "76", + "id": "78", "metadata": { "tags": [] }, @@ -1256,12 +1248,12 @@ }, { "cell_type": "markdown", - "id": "77", + "id": "79", "metadata": { "tags": [] }, "source": [ - "## Bonus exercises\n", + "# Bonus exercises\n", "\n", "
\n", "

Note

\n", @@ -1272,7 +1264,7 @@ }, { "cell_type": "markdown", - "id": "78", + "id": "80", "metadata": { "jp-MarkdownHeadingCollapsed": true }, @@ -1282,24 +1274,26 @@ }, { "cell_type": "markdown", - "id": "79", + "id": "81", "metadata": { "tags": [] }, "source": [ "Given an **unsorted** set of $N$ random integers, write a function that returns the length of the longest consecutive sequence of integers.\n", "\n", - "#### Example 1\n", + "**Example 1**\n", + "\n", "Given the list `numbers = [100, 4, 200, 1, 3, 2]`, the longest sequence is `[1, 2, 3, 4]` of length *4*.\n", "\n", - "#### Example 2\n", + "**Example 2**\n", + "\n", "Given the list `numbers = [0, 3, 7, 2, 5, 8, 4, 6, 0, 1]`, the longest sequence contains all the numbers from 0 to 8, so its length is **9**.\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "80", + "id": "82", "metadata": { "tags": [] }, @@ -1316,7 +1310,7 @@ }, { "cell_type": "markdown", - "id": "81", + "id": "83", "metadata": { "jp-MarkdownHeadingCollapsed": true }, @@ -1334,7 +1328,7 @@ { "cell_type": "code", "execution_count": null, - "id": "82", + "id": "84", "metadata": { "tags": [] }, @@ -1351,7 +1345,7 @@ }, { "cell_type": "markdown", - "id": "83", + "id": "85", "metadata": { "jp-MarkdownHeadingCollapsed": true, "tags": [] @@ -1362,7 +1356,7 @@ }, { "cell_type": "markdown", - "id": "84", + "id": "86", "metadata": { "tags": [] }, @@ -1372,7 +1366,7 @@ }, { "cell_type": "markdown", - "id": "85", + "id": "87", "metadata": {}, "source": [ "You have a range of numbers `136760-595730` and need to count how many valid password it contains. A valid password must meet **all** the following criteria:\n", @@ -1393,7 +1387,7 @@ }, { "cell_type": "markdown", - "id": "86", + "id": "88", "metadata": {}, "source": [ "\n", @@ -1404,7 +1398,7 @@ }, { "cell_type": "markdown", - "id": "87", + "id": "89", "metadata": {}, "source": [ "\n", @@ -1416,7 +1410,7 @@ { "cell_type": "code", "execution_count": null, - "id": "88", + "id": "90", "metadata": {}, "outputs": [], "source": [ @@ -1431,7 +1425,7 @@ }, { "cell_type": "markdown", - "id": "89", + "id": "91", "metadata": { "tags": [] }, @@ -1441,7 +1435,7 @@ }, { "cell_type": "markdown", - "id": "90", + "id": "92", "metadata": {}, "source": [ "You have a new rule: **at least** two adjacent matching digits **must not be part of a larger group of matching digits**.\n", @@ -1463,7 +1457,7 @@ { "cell_type": "code", "execution_count": null, - "id": "91", + "id": "93", "metadata": {}, "outputs": [], "source": [ @@ -1478,7 +1472,7 @@ }, { "cell_type": "markdown", - "id": "92", + "id": "94", "metadata": { "jp-MarkdownHeadingCollapsed": true, "tags": [] @@ -1489,7 +1483,7 @@ }, { "cell_type": "markdown", - "id": "93", + "id": "95", "metadata": {}, "source": [ "#### Part 1" @@ -1497,7 +1491,7 @@ }, { "cell_type": "markdown", - "id": "94", + "id": "96", "metadata": {}, "source": [ "You have a list of buckets, each containing some items labeled from `a` to `z`, or from `A` to `Z`. Each bucket is split into **two** equally sized compartments.\n", @@ -1528,7 +1522,7 @@ }, { "cell_type": "markdown", - "id": "95", + "id": "97", "metadata": {}, "source": [ "
\n", @@ -1538,7 +1532,7 @@ }, { "cell_type": "markdown", - "id": "96", + "id": "98", "metadata": {}, "source": [ "
\n", @@ -1549,7 +1543,7 @@ { "cell_type": "code", "execution_count": null, - "id": "97", + "id": "99", "metadata": { "tags": [] }, @@ -1565,7 +1559,7 @@ }, { "cell_type": "markdown", - "id": "98", + "id": "100", "metadata": { "jp-MarkdownHeadingCollapsed": true }, @@ -1575,7 +1569,7 @@ }, { "cell_type": "markdown", - "id": "99", + "id": "101", "metadata": {}, "source": [ "You are told that you should not care about the priority of **every item**, but only of a \"special item\" that is common to groups of **three buckets**.\n", @@ -1601,7 +1595,7 @@ { "cell_type": "code", "execution_count": null, - "id": "100", + "id": "102", "metadata": {}, "outputs": [], "source": [ diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index 29fc84f9..e7d0cedf 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -61,8 +61,8 @@ def test_greet( # Part 1 def reference_calculate_basic_area(length: float, width: float) -> str: """Reference solution for Part 1: basic area calculation.""" - area = length * width - return f"{area:.2f} cm^2" + area = round(length * width, 2) + return f"{area} cm^2" def validate_basic_area_signature(function_to_test) -> None: @@ -115,8 +115,8 @@ def reference_calculate_metric_area( length *= 100 width *= 100 - area = length * width - return f"{area:.2f} cm^2" + area = round(length * width, 2) + return f"{area} cm^2" def validate_metric_area_signature(function_to_test) -> None: @@ -176,8 +176,8 @@ def reference_calculate_area(length: float, width: float, unit: str = "cm") -> s except KeyError: return f"Invalid unit: {unit}" - area = length * width * factor**2 - return f"{area:.2f} cm^2" + area = round(length * width * factor**2, 2) + return f"{area} cm^2" def validate_area_signature(function_to_test) -> None: From ec571367dcd105fe03730c1688f522cfabce1f84 Mon Sep 17 00:00:00 2001 From: edoardob90 Date: Wed, 27 Nov 2024 23:19:58 +0100 Subject: [PATCH 07/12] Fix test of Exercise 2.2 --- tutorial/tests/test_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index e7d0cedf..559110f4 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -156,7 +156,7 @@ def validate_metric_area_signature(function_to_test) -> None: (1.5, 2.0, "m"), ], ) -def test_calculate_metric_area(length, width, unit, expected, function_to_test): +def test_calculate_metric_area(length, width, unit, function_to_test): validate_metric_area_signature(function_to_test) expected = reference_calculate_metric_area(length, width, unit) result = function_to_test(length, width, unit) From d57491e28b5f37307ee0645b118cd15914204be2 Mon Sep 17 00:00:00 2001 From: Edoardo Baldi Date: Thu, 28 Nov 2024 08:54:59 +0100 Subject: [PATCH 08/12] Adjust Exercise 1 --- functions.ipynb | 13 ++----------- tutorial/tests/test_functions.py | 10 +++++++++- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/functions.ipynb b/functions.ipynb index d4d50cdc..75e517ca 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -441,7 +441,7 @@ "\n", "
\n", "

Note

\n", - " Do not forget to add the correct type hints to the parameters.\n", + " Do not forget to add a docstring and the correct type hints to the parameters.\n", "
" ] }, @@ -455,15 +455,6 @@ "%%ipytest\n", "\n", "def solution_greet() -> str:\n", - " \"\"\"Creates a personalized greeting message using name and age.\n", - "\n", - " Args:\n", - " name: The person's name to include in the greeting\n", - " age: The person's age in years\n", - "\n", - " Returns:\n", - " - A string in the format \"Hello, ! You are years old.\"\n", - " \"\"\"\n", " return" ] }, @@ -1624,7 +1615,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.15" + "version": "3.11.4" } }, "nbformat": 4, diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index 559110f4..df93be14 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -18,7 +18,15 @@ def read_data(name: str, data_dir: str = "data") -> pathlib.Path: def reference_greet(name: str, age: int) -> str: - """Reference solution for `solution_greet` exercise""" + """Creates a personalized greeting message using name and age. + + Args: + name: The person's name to include in the greeting + age: The person's age in years + + Returns: + - A string in the format "Hello, ! You are years old." + """ return f"Hello, {name}! You are {age} years old." From cd330e34523df289324e9ac37ae0089037b97d62 Mon Sep 17 00:00:00 2001 From: Edoardo Baldi Date: Thu, 28 Nov 2024 09:00:24 +0100 Subject: [PATCH 09/12] Add hints --- functions.ipynb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/functions.ipynb b/functions.ipynb index 75e517ca..d8617f92 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -531,6 +531,8 @@ "If the unit is \"m\", convert the measurements to centimeters before calculating the area.\n", "The result should still be in cm^2.\n", "\n", + "*Hint:* remember the [built-in function](https://docs.python.org/3/library/functions.html#round) `round(number, ndigits)`.\n", + "\n", "Example: `length=2`, `width=3`, `unit=\"m\"` should return `\"60000.00 cm^2\"` (because 2m × 3m = 6m² = 60000cm²)\n", "\n", "
\n", @@ -578,6 +580,8 @@ "- $\\text{yd} / \\text{cm}= 91.44$\n", "- $\\text{ft} / \\text{cm}= 30.48$\n", "\n", + "*Hint:* remember the [built-in function](https://docs.python.org/3/library/functions.html#round) `round(number, ndigits)`.\n", + "\n", "Examples:\n", "- `length=2`, `width=3`, `unit=\"mm\"` should return `\"0.06 cm^2\"`\n", "- `length=2`, `width=3`, `unit=\"yd\"` should return `\"50167.64 cm^2\"`\n", From 3a4c54d93becb5bbdb73f6e19ff99b1e6a1f5772 Mon Sep 17 00:00:00 2001 From: Edoardo Baldi Date: Thu, 28 Nov 2024 09:15:01 +0100 Subject: [PATCH 10/12] Adjust Exercise 3 --- functions.ipynb | 18 ++++++++++-------- tutorial/tests/test_functions.py | 9 ++++----- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/functions.ipynb b/functions.ipynb index d8617f92..61fc8268 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -1178,15 +1178,20 @@ "id": "75", "metadata": {}, "source": [ - "Complete function below `solution_summing_anything` that is able to sum **any** kind of arguments.\n", + "Complete function below `solution_combine_anything` that combines multiple arguments of the same type.\n", "It must accept a *variable* number of arguments.\n", "\n", - "You must **not** use the built-in function `sum()`: you should write your own (generalized) implementation.\n", + "Make sure to consider the case where you have **no arguments** at all.\n", + "\n", + "You must **not** use the built-in function `sum()`.\n", + "\n", + "*Hint:* read the function's docstring since it contains all the details.\n", "\n", "A few examples of how the function should work:\n", "\n", "| Arguments | Expected result |\n", "| --- | --- |\n", + "| `()` | `()` |\n", "| `('abc', 'def')` | `'abcdef'` |\n", "| `([1,2,3], [4,5,6])` | `[1,2,3,4,5,6]` |\n" ] @@ -1200,7 +1205,7 @@ "source": [ "%%ipytest\n", "\n", - "def solution_summing_anything(*args):\n", + "def solution_combine_anything(*args):\n", " \"\"\"Combines any number of arguments using the appropriate addition operation.\n", "\n", " The function determines the appropriate way to combine arguments based on their type:\n", @@ -1214,8 +1219,7 @@ "\n", " Returns:\n", " - The combined result using the appropriate operation for the input types.\n", - " Returns the first argument's type's empty value if no arguments are provided\n", - " (e.g., '' for strings, [] for lists, 0 for numbers).\n", + " If no arguments are provided, you should return an empty tuple.\n", " \"\"\"\n", " return" ] @@ -1260,9 +1264,7 @@ { "cell_type": "markdown", "id": "80", - "metadata": { - "jp-MarkdownHeadingCollapsed": true - }, + "metadata": {}, "source": [ "### Longest consecutive sequence" ] diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index df93be14..8b158bdb 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -239,9 +239,9 @@ def test_calculate_area(length, width, unit, function_to_test): # -def reference_summing_anything(*args: Any) -> Any: +def reference_combine_anything(*args: Any) -> Any: """Reference solution for the summing_anything exercise""" - if not args or None in args: + if not args: return args result = args[0] @@ -256,14 +256,13 @@ def reference_summing_anything(*args: Any) -> Any: "args", [ (()), - ((None, None)), ((1, 2, 3)), (([1, 2, 3], [4, 5, 6])), (("hello", "world")), ], ) -def test_summing_anything(args: Any, function_to_test) -> None: - assert function_to_test(*args) == reference_summing_anything(*args) +def test_combine_anything(args: Any, function_to_test) -> None: + assert function_to_test(*args) == reference_combine_anything(*args) # From d9429ad12ec3cad833f0886d0cc758518949c5f2 Mon Sep 17 00:00:00 2001 From: Edoardo Baldi Date: Thu, 28 Nov 2024 09:17:32 +0100 Subject: [PATCH 11/12] Small fix to notebook --- functions.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/functions.ipynb b/functions.ipynb index 61fc8268..b186477c 100644 --- a/functions.ipynb +++ b/functions.ipynb @@ -1264,7 +1264,9 @@ { "cell_type": "markdown", "id": "80", - "metadata": {}, + "metadata": { + "jp-MarkdownHeadingCollapsed": true + }, "source": [ "### Longest consecutive sequence" ] From 719bcbbfd0fa7036b4ea7ac6f1ef38de6ea43e43 Mon Sep 17 00:00:00 2001 From: Edoardo Baldi Date: Thu, 28 Nov 2024 09:18:26 +0100 Subject: [PATCH 12/12] Small fix to test --- tutorial/tests/test_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorial/tests/test_functions.py b/tutorial/tests/test_functions.py index 8b158bdb..b8a08399 100644 --- a/tutorial/tests/test_functions.py +++ b/tutorial/tests/test_functions.py @@ -240,7 +240,7 @@ def test_calculate_area(length, width, unit, function_to_test): def reference_combine_anything(*args: Any) -> Any: - """Reference solution for the summing_anything exercise""" + """Reference solution for the combine_anything exercise""" if not args: return args