diff --git a/_sources/book/pythontricks/Chapter.ipynb b/_sources/book/pythontricks/Chapter.ipynb index 4e91ec7..2c6ecd2 100644 --- a/_sources/book/pythontricks/Chapter.ipynb +++ b/_sources/book/pythontricks/Chapter.ipynb @@ -829,6 +829,56 @@ "p = Point(x=3.2, y=1.0)\n", "print(p.x, p.y)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mutable Default Values for Function Arguments" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One big mistake in Python:\n", + "\n", + "Using mutable default values for function arguments.\n", + "\n", + "When using mutable objects like a list as a default value, you have to be careful.\n", + "\n", + "See the example below, where the default list is shared among all calls to the function.\n", + "\n", + "To fix this, set the default value to None and create a new list inside the function if no list is provided." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Dangerous behaviour:\n", + "def increment_numbers(numbers=[]):\n", + " for i in range(3):\n", + " numbers.append(i)\n", + " print(f\"Numbers: {numbers}\")\n", + "\n", + "increment_numbers() # Output: Numbers: [0, 1, 2]\n", + "increment_numbers() # Output: Numbers: [0, 1, 2, 0, 1, 2]\n", + "\n", + "\n", + "# Better:\n", + "def increment_numbers(numbers=None):\n", + " if numbers is None:\n", + " numbers = []\n", + " for i in range(3):\n", + " numbers.append(i)\n", + " print(f\"Numbers: {numbers}\")\n", + "\n", + "increment_numbers() # Output: Numbers: [0, 1, 2]\n", + "increment_numbers() # Output: Numbers: [0, 1, 2]" + ] } ], "metadata": { diff --git a/_sources/book/testing/Chapter.ipynb b/_sources/book/testing/Chapter.ipynb index 4674171..7c6470a 100644 --- a/_sources/book/testing/Chapter.ipynb +++ b/_sources/book/testing/Chapter.ipynb @@ -358,6 +358,70 @@ "def test_addition_commutative(a, b):\n", " assert a + b == b + a" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mocking Dependencies with `pytest-mock`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Testing is an essential part of Software projects.\n", + "\n", + "\n", + "\n", + "Especially unit testing, where you test the smallest piece of code that can be isolated.\n", + "\n", + "\n", + "\n", + "They should be independent, and fast & cheap to execute.\n", + "\n", + "\n", + "\n", + "But, what if you have some dependencies like API calls or interactions with databases and systems?\n", + "\n", + "\n", + "\n", + "Here's where mocking comes into play.\n", + "\n", + "\n", + "\n", + "Mocking allows you to replace dependencies and real objects with fake ones which mimic the real behavior.\n", + "\n", + "\n", + "\n", + "So, you don't have to rely on the availability of your API, or ask for permission to interact with a database, but you can test your functions isolated and independently.\n", + "\n", + "\n", + "\n", + "In Python, you can perform mocking with `pytest-mock`, a wrapper around the built-in mock functionality of Python.\n", + "\n", + "\n", + "\n", + "See the example below, where we mock the file removal functionality. We can test it without deleting a file from the disk." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "class UnixFS:\n", + " @staticmethod\n", + " def rm(filename):\n", + " os.remove(filename)\n", + "def test_unix_fs(mocker):\n", + " mocker.patch('os.remove')\n", + " UnixFS.rm('file')\n", + " os.remove.assert_called_once_with('file')\n" + ] } ], "metadata": { diff --git a/book/pythontricks/Chapter.html b/book/pythontricks/Chapter.html index 1c70b5e..bb7543b 100644 --- a/book/pythontricks/Chapter.html +++ b/book/pythontricks/Chapter.html @@ -435,6 +435,7 @@
Counter
defaultdict
namedtuple
One big mistake in Python:
+Using mutable default values for function arguments.
+When using mutable objects like a list as a default value, you have to be careful.
+See the example below, where the default list is shared among all calls to the function.
+To fix this, set the default value to None and create a new list inside the function if no list is provided.
+# Dangerous behaviour:
+def increment_numbers(numbers=[]):
+ for i in range(3):
+ numbers.append(i)
+ print(f"Numbers: {numbers}")
+
+increment_numbers() # Output: Numbers: [0, 1, 2]
+increment_numbers() # Output: Numbers: [0, 1, 2, 0, 1, 2]
+
+
+# Better:
+def increment_numbers(numbers=None):
+ if numbers is None:
+ numbers = []
+ for i in range(3):
+ numbers.append(i)
+ print(f"Numbers: {numbers}")
+
+increment_numbers() # Output: Numbers: [0, 1, 2]
+increment_numbers() # Output: Numbers: [0, 1, 2]
+