In Python, lists do not contain any objects themselves; rather, they contain references to objects. As a result, the same object can appear multiple times within a list, or outside that list.

For example, now that the list contains the milk object twice, simply by having two references to it, rather than storing two copies of it.

```python
class Product:
    def __init__(self, name: str, unit: str):
        self.name = name
        self.unit = unit
 
 
if __name__ == "__main__":
    shopping_list = []
    milk = Product("Milk", "litre")
 
    shopping_list.append(milk)
    shopping_list.append(milk)
    shopping_list.append(Product("Cucumber", "piece"))

A more complex examples is the following (see a visualization)

class Dog:
    def __init__(self, name):
        self.name = name
 
    def __str__(self):
        return self.name
 
dogs = []
fluffy = Dog("Fluffy")
dogs.append(fluffy)
dogs.append(fluffy)
dogs.append(Dog("Fluffy"))
 
print("Dogs initially:")
for dog in dogs:
    print(dog)
 
print("The dog at index 0 is renamed:")
dogs[0].name = "Pooch"
for dog in dogs:
    print(dog)
 
print("The dog at index 2 is renamed:")
dogs[2].name = "Fifi"
for dog in dogs:
    print(dog)

The output of this code is:

Dogs initially:
Fluffy
Fluffy
Fluffy
The dog at index 0 is renamed:
Pooch
Pooch
Fluffy
The dog at index 2 is renamed:
Pooch
Pooch
Fifi

The objects at indexes 0 and 1 are the same; both locations in the list refer to the same object. As a result, when we modify the object at index 0, the change is also reflected at index 1.

is vs ==

This is precisely where the difference between is and == can be seen: the former is checking whether we are talking literally about the same object, while the latter checks (via __eq__) whether the contents of the objects is the same.