<jupyter_start><jupyter_text>MutabilityYou will encounter the term **"mutable"** and **"immutable"** while reading/discussing Python. Essentially, this means whether an object can be modified/mutated.This concept is important at a fundamental level, particularly with regards to dictionaries. The key-value structure of a dictionary is essentially what is called a **"hash table"** or **"associative array"**. These are common to most programming languages. These data structures work efficiently due to the use of a hash function which generates an **index** for quickly accessing values linked to keys. Think of it like a phone book, where you can quickly flip to the desired pages using alphabetical sorting. For this indexing to work, keys point to a static space in memory and can't be changed, otherwise the index has to be recreated. Thus, **dictionary keys must be immutable objects**.  Which object types are mutable?**"mutable" means that you can change the object**. For example, integers are not mutable: you cannot change the number 1 to mean anything else.Common immutable type:    numbers: integer, float, booleans    immutable sequences: string, tuple, frozenset    Common mutable type (almost everything else):     list, set, dict    StringsAccessing elements and slicing for strings works similarly as in lists. In contrast to lists, Python strings are immutable. However, `a` is not a string: it is a variable with a string value. You can't mutate the string, but can change what value of the variable to a new string<jupyter_code>a = "dog"
a[2]
# mutating a string is not possible
a[2] = "t"
# but we can assign a new string
a = "cat"
print(a)<jupyter_output>cat<jupyter_text>TuplesMutability is also an important concept at a functional level in Python. In some cases, it is safer or otherwise optimal to use data structures which can't be modified. The tuple data container is also like a list, however tuples are immutable.<jupyter_code>nameTuple = ("Curly","Larry","Moe")

print(nameTuple[0])

nameTuple[0] = "Harry"<jupyter_output>Curly<jupyter_text>As a tuple is immutable, it does not support item assignment. In contrast, elements in a list can be altered individually.  Variables as ReferencesIn Python, objects are passed as **references**. This means that a variable is only referring to the object as a particular space in memory. Consider the following:<jupyter_code>varA = list(range(10))

varB = varA

varB[0] = 999

print(varA)<jupyter_output>[999, 1, 2, 3, 4, 5, 6, 7, 8, 9]<jupyter_text>In the above example, varA and varB are different references pointing to the same object in memory space. You can make independent copies of variables, thus creating a new space in memory for the copy:<jupyter_code>import copy

varA = list(range(10))

varB = copy.copy(varA)

varB[0] = 999

print(varA)
print(varB)<jupyter_output>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[999, 1, 2, 3, 4, 5, 6, 7, 8, 9]<jupyter_text>Note that the above copying is **"shallow"** - this won't work for nested structures. To do this, you need to use the deepcopy method:<jupyter_code>import copy

nest1 = { "A":[0,1,2], "B":[3,4,5], "C":[6,7,8] }

nest2 = copy.copy(nest1)

nest3 = copy.deepcopy(nest1)

nest1['A'][0] = 999

print(nest1)
print(nest2)
print(nest3)
# the problem doesn't exist with strings

a = 'SIB'
b = a
a = 'URPP'

print(a)
print(b)<jupyter_output>URPP
SIB
{'fraction_non_alphanumeric': 0.0907742509641056, 'fraction_numerical': 0.021655295164639574, 'mean_word_length': 5.043010752688172, 'pattern_counts': {'":': 3, '<': 16, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '579790', 'license': 'MIT', 'n_tokens_mistral': 1079, 'n_tokens_neox': 1005, 'n_words': 471, 'path': 'Mutability_sw.ipynb', 'repo_name': 'swyder/ZuriPython_2018', 'size': '3371'}