<jupyter_start><jupyter_text>Containers  Checking for containment. The `list` we saw is a container type: its purpose is to hold other objects. We can ask python whether or not acontainer contains a particular item:<jupyter_code>"Dog" in ["Cat", "Dog", "Horse"]
"Bird" in ["Cat", "Dog", "Horse"]
2 in range(5)
99 in range(5)<jupyter_output><empty_output><jupyter_text>Mutability A list can be modified:<jupyter_code>name = "<NAME>".split(" ")
print(name)
name[0] = "Dr"
name[1:3] = ["Griffiths-"]
name.append("PhD")

print(" ".join(name))<jupyter_output>Dr Griffiths- Hetherington PhD<jupyter_text>Tuples A `tuple` is an immutable sequence. It is like a list, except it cannot be changed. It is defined with round brackets.<jupyter_code>x = (0,)
type(x)
my_tuple = ("Hello", "World")
my_tuple[0] = "Goodbye"
type(my_tuple)<jupyter_output><empty_output><jupyter_text>`str` is immutable too:<jupyter_code>fish = "Hake"
fish[0] = "R"<jupyter_output><empty_output><jupyter_text>But note that container reassignment is moving a label, **not** changing an element:<jupyter_code>fish = "Rake"  ## OK!<jupyter_output><empty_output><jupyter_text>*Supplementary material*: Try the [online memory visualiser](http://www.pythontutor.com/visualize.htmlcode=name+%3D++%22James+Philip+John+Hetherington%22.split%28%22+%22%29%0A%0Aname%5B0%5D+%3D+%22Dr%22%0Aname%5B1%3A3%5D+%3D+%5B%22Griffiths-%22%5D%0Aname.append%28%22PhD%22%29%0A%0Aname+%3D+%22Bilbo+Baggins%22&mode=display&origin=opt-frontend.js&cumulative=false&heapPrimitives=true&textReferences=false&py=2&rawInputLstJSON=%5B%5D&curInstr=0) for this one.  Memory and containers The way memory works with containers can be important:<jupyter_code>x = list(range(3))
x
y = x
y
z = x[0:3]
y[1] = "Gotcha!"
x
y
z
z[2] = "Really?"
x
y
z<jupyter_output><empty_output><jupyter_text>*Supplementary material*: This one works well at the [memory visualiser](http://www.pythontutor.com/visualize.htmlcode=x+%3D+%5B%22What's%22,+%22Going%22,+%22On%3F%22%5D%0Ay+%3D+x%0Az+%3D+x%5B0%3A3%5D%0A%0Ay%5B1%5D+%3D+%22Gotcha!%22%0Az%5B2%5D+%3D+%22Really%3F%22&mode=display&origin=opt-frontend.js&cumulative=false&heapPrimitives=true&textReferences=false&py=2&rawInputLstJSON=%5B%5D&curInstr=0). The explanation: While `y` is a second label on the *same object*, `z` is a separate object with the same data. Writing `x[:]` creates a new list containing all the elements of `x` (remember: `[:]` is equivalent to `[0:]`). This is the case whenever we take a slice from a list, not just when taking all the elements with `[:]`.The difference between `y=x` and `z=x[:]` is important! Nested objects make it even more complicated:<jupyter_code>x = [["a", "b"], "c"]
y = x
z = x[0:2]
x[0][1] = "d"
z[1] = "e"
x
y
z<jupyter_output><empty_output><jupyter_text>Try the [visualiser](http://www.pythontutor.com/visualize.htmlcode=x%3D%5B%5B'a','b'%5D,'c'%5D%0Ay%3Dx%0Az%3Dx%5B0%3A2%5D%0A%0Ax%5B0%5D%5B1%5D%3D'd'%0Az%5B1%5D%3D'e'&mode=display&origin=opt-frontend.js&cumulative=false&heapPrimitives=true&textReferences=false&py=2&rawInputLstJSON=%5B%5D&curInstr=0) again.*Supplementary material*: The copies that we make through slicing are called *shallow copies*: we don't copy all the objects they contain, only the references to them. This is why the nested list in `x[0]` is not copied, so `z[0]` still refers to it. It is possible to actually create copies of all the contents, however deeply nested they are - this is called a *deep copy*. Python provides methods for that in its standard library, in the `copy` module. You can read more about that, as well as about shallow and deep copies, in the [library reference](https://docs.python.org/3/library/copy.html).  Identity vs EqualityHaving the same data is different from being the same actual objectin memory:<jupyter_code>[1, 2] == [1, 2]
[1, 2] is [1, 2]<jupyter_output><empty_output><jupyter_text>The == operator checks, element by element, that two containers have the same data. The `is` operator checks that they are actually the same object. But, and this point is really subtle, for immutables, the python language might save memory by reusing a single instantiated copy. This will always be safe.<jupyter_code>"Hello" == "Hello"
"Hello" is "Hello"<jupyter_output><empty_output><jupyter_text>This can be useful in understanding problems like the one above:<jupyter_code>x = range(3)
y = x
z = x[:]
x == y
x is y
x == z
x is z<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1437923250564334, 'fraction_numerical': 0.03860045146726862, 'mean_word_length': 7.1602209944751385, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 1, 'lorem ipsum': 0, 'www.': 3, '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': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '74152', 'license': 'CC-BY-3.0', 'n_tokens_mistral': 1752, 'n_tokens_neox': 1663, 'n_words': 447, 'path': 'module01_introduction_to_python/01_05_containers.ipynb', 'repo_name': 'marquesafonso/rse-course', 'size': '4454'}