{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Mutability of objects in Python\n", "\n", "All objects in Python can be either **mutable** or **immutable**. This is an important notation that newcomers to Python need to be aware of, which otherwise can lead to serious bugs in our codes.\n", "\n", "What do we mean by *mutable*? We leant that everything in Python is an Object and every variable holds an instance of an object. Once its type is set at runtime it can never change. A list is always a list, an integer is always an integer. However its value can be modified if it is mutable.\n", "\n", "A mutable object can be changed/modified after it is created, and an immutable object can’t." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "| Class | Mutable |\n", "| ------- |:-------:|\n", "| `bool` | no |\n", "| `int` | no |\n", "| `float` | no |\n", "| `str` | no |\n", "| `list` | yes |\n", "| `tuple` | no |\n", "| `dict` | yes |" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Mutability has not much practical importance for simple types,\n", "# but it has for container types.\n", "# Let's see this with some examples\n", "\n", "a_str = \"Python\"\n", "a_list = [\"P\", \"y\", \"t\", \"h\", \"o\", \"n\"]\n", "a_tuple = (\"P\", \"y\", \"t\", \"h\", \"o\", \"n\")\n", "a_dict = {0: \"P\", 1: \"y\", 2: \"t\", 3: \"h\", 4: \"o\", 5: \"n\"}" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P\n" ] }, { "ename": "TypeError", "evalue": "'str' object does not support item assignment", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;31m# let's try to change \"P\" into \"p\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0ma_str\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"p\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'str' object does not support item assignment" ] } ], "source": [ "print(a_str[0])\n", "\n", "# let's try to change \"P\" into \"p\"\n", "a_str[0] = \"p\"" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P\n", "['p', 'y', 't', 'h', 'o', 'n']\n" ] } ], "source": [ "# Let's try with list\n", "print(a_list[0])\n", "a_list[0] = \"p\"\n", "print(a_list)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P\n" ] }, { "ename": "TypeError", "evalue": "'tuple' object does not support item assignment", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# However, the 'immutable' cousin of list, the tuple, does not allow assignment\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma_tuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0ma_tuple\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"p\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'tuple' object does not support item assignment" ] } ], "source": [ "# However, the 'immutable' cousin of list, the tuple, does not allow assignment\n", "print(a_tuple[0])\n", "a_tuple[0] = \"p\"" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P\n", "{0: 'p', 1: 'y', 2: 't', 3: 'h', 4: 'o', 5: 'n'}\n" ] } ], "source": [ "# And with dict \n", "print(a_dict[0])\n", "a_dict[0] = \"p\"\n", "print(a_dict)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['p', 'y', 't', 'h', 'o', 'n']\n" ] } ], "source": [ "my_dict = {\"str\": a_str, \"list\": a_list}\n", "another_dict = my_dict\n", "print(another_dict[\"list\"])" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "my_dict: {'str': 'Python', 'list': ['P', 'y', 't', 'h', 'o', 'n']}\n", "another_dict: {'str': 'Python', 'list': ['P', 'y', 't', 'h', 'o', 'n']}\n" ] } ], "source": [ "# Now let's modify my_dict\n", "my_dict[\"list\"][0] = \"P\"\n", "# and see what happens to both dictionaries\n", "print(\"my_dict:\", my_dict)\n", "print(\"another_dict:\", another_dict)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Although we never changed/modified `another_dict`, it was also changed. This is because the key **'list'** in both dictionaries refer to the same `list` object: `a_list`. It is mutable and once it is modified, both dictionaries will reflect this modification. Let's visit this with a final example." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "my_dict: {'str': 'Python', 'list': ['Z', 'y', 't', 'h', 'o', 'n']}\n", "another_dict: {'str': 'Python', 'list': ['Z', 'y', 't', 'h', 'o', 'n']}\n" ] } ], "source": [ "a_list[0] = \"Z\"\n", "print(\"my_dict:\", my_dict)\n", "print(\"another_dict:\", another_dict)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To summarize:\n", "\n", "- An object in Python can either be mutable or immutable\n", "- We can simply check it by trying to modify a variable\n", "- `str` and `tuple` are immutable\n", "- `list` and `dict` are mutable\n", "- We need to pay attention when we modify mutable objects that are referred from multiple objects!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "\n", "## a solution : explicit deep copy\n", "\n", "The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):\n", "\n", "A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.\n", "\n", "A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'my_dict' is not defined", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mcopy\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0ma_third_dict\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcopy\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdeepcopy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmy_dict\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m## <- explicit deep copy\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mmy_dict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"str\"\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"Back to Python\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mmy_dict\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m\"list\"\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"P\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"my_dict:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmy_dict\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mNameError\u001b[0m: name 'my_dict' is not defined" ] } ], "source": [ "import copy\n", "a_third_dict = copy.deepcopy(my_dict) ## <- explicit deep copy\n", "my_dict[\"str\"] = \"Back to Python\"\n", "my_dict[\"list\"][0] = \"P\"\n", "print(\"my_dict:\", my_dict)\n", "print(\"another_dict:\", a_third_dict)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 }