<jupyter_start><jupyter_text>L3 - Variables are references--- Attention: This is actually quite simple, but easy to overlook when you're learning Python! Pay close attention to this lecture and you'll be spared hours of debugging.  3.1 Assignments One thing that is very common in programming is assignment using variables in the right side.<jupyter_code>a = 2
b = a
print(a,b)<jupyter_output><empty_output><jupyter_text>This is also possible in Python, but you should be aware of what's going on under the hood, to avoid a big pitfall.The first important thing to understand is that in Python, you shouldn't think of a variable as the memory location of your data (as you would in some classic programming languages). Instead, in Python all variables are *references* to memory locations. This means that when you declare a variable `a` like this<jupyter_code>a = 3<jupyter_output><empty_output><jupyter_text>`a` does not contain the value 3, instead, it contains the address on where to find the value 3 in the computer's memory. If you want to know which address that is, you can use the built-in function `id`.<jupyter_code>id(a)<jupyter_output><empty_output><jupyter_text>Usually memory locations are displayed in hexadecimal format, and we can do that using the built-in `hex` function. So to get the memory location that `a` references, we do<jupyter_code>hex(id(a))<jupyter_output><empty_output><jupyter_text>Now, when we run the code<jupyter_code>b = a<jupyter_output><empty_output><jupyter_text>This line of code just made `b` reference the same thing (memory address) that `a` was referencing. It **does not** make a copy of what `a` references and then saves that to `b`! To show this, we can get the memory address that `b` references:<jupyter_code>hex(id(b))<jupyter_output><empty_output><jupyter_text>As you can see, this is exactly the same address that `a` references, so both variables are referencing the same thing. No copy of the number 3 has been done. You might now be thinking that if we do:<jupyter_code>b = 10<jupyter_output><empty_output><jupyter_text>This should then affect the value of `a`, since we would be overwriting the contents of the memory address that `b` references, and hence overwriting the contents of what `a` references as well (since they both reference the same memory address). However:<jupyter_code>print(a)
print(b)<jupyter_output><empty_output><jupyter_text>this is not the case. The reason for this is that `int`s are *immutable* types. --- 3.2 Mutable vs Immutable types Types in Python can be of two categories: mutable or immutable. Mutable types allow for modifications after creation, while immutable ones don't. If you try to alter an immutable type, it will return a new object instead.Some immutable types are:- `int`- `float`- `string`- `bool`- `tuple`Some mutable types:- `list`- `set`- `dict`For example, this means that, if we create an int:<jupyter_code>a = 3<jupyter_output><empty_output><jupyter_text>Which is in the following memory address:<jupyter_code>hex(id(a))<jupyter_output><empty_output><jupyter_text>And now we try changing its value<jupyter_code>a = -29<jupyter_output><empty_output><jupyter_text>What happens is that our variable will point to a **new** memory address, with the value of -29.<jupyter_code>hex(id(a))<jupyter_output><empty_output><jupyter_text>This explains why we get this type of behavior:<jupyter_code>a = 3
b = a
b = -29
print(a)
print(b)
hex(id(a))
hex(id(b))<jupyter_output><empty_output><jupyter_text>--- However, the `list` type *is* mutable, so trying to change it does not create a new memory address. Hence, trying to do the same with lists yields a different behavior.<jupyter_code>list1 = [1,2,3,4,5]
list2 = list1
hex(id(list1))
hex(id(list2))
list2[2] = -29
list2<jupyter_output><empty_output><jupyter_text>Changing `list2` does not make it reference a new memory address.<jupyter_code>hex(id(list2))<jupyter_output><empty_output><jupyter_text>Hence, changing the value of an element in `list2` **does change** `list1` also.<jupyter_code>list1<jupyter_output><empty_output><jupyter_text>--- 3.3 Copying lists the proper way To copy a list's content into another one, and not just make both variables reference the same memory address, we can use slicing:<jupyter_code>list1 = [1,2,3,4]
list2 = list1[:]<jupyter_output><empty_output><jupyter_text>This makes sure that `list2` points to a different memory address than `list1`, but still contains the same values.<jupyter_code>list1
list2
hex(id(list1))
hex(id(list2))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09152765583845479, 'fraction_numerical': 0.012071992976294996, 'mean_word_length': 6.187697160883281, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 73, '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': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '430791', 'license': 'MIT', 'n_tokens_mistral': 1463, 'n_tokens_neox': 1430, 'n_words': 563, 'path': 'Python Crash Course/Module 1 - Core Language/L3 Python.ipynb', 'repo_name': 'lars-olsson/deep-machine-learning', 'size': '4556'}