Couldn't Understand The Boredless Tourist Tasks

I was doing the python project: The Boredless Tourist. I am stuck at task 32 and 33.

Task 32 says, append the attraction passed into add_attraction to the list attractions_for_destination .

What I don’t understand is how is attractions getting modified here, because we created a variable attractions_for_destination inside the function add_attraction() and assigned it to the corresponding list of attraction in attractions. Then we appended the attraction passed inside the function to the variable attractions_for_destination. Nowhere did we modify the lists inside attractions. But when we call the function in task 33 and then print attractions, the attractions passed inside the function add_attractions() has been added to the actual list attractions.

Here is my code:

destinations = ["Paris, France", "Shanghai, China", "Los Angeles, USA", "Sao Paulo, Brazil", "Cairo, Egypt"]

test_traveler = ["Erin Wilkes", "Shanghai, China", ["historical site", "art"]]

def get_destination_index(destination):
  destination_index = destinations.index(destination)
  return destination_index

# print(get_destination_index("Los Angeles, USA"))
# print(get_destination_index("Paris, France"))
# print(get_destination_index("Hyderabad, India"))

def get_traveler_location(traveler):
  traveler_destination = traveler[1]
  traveler_destination_index = get_destination_index(traveler_destination)
  return traveler_destination_index

test_destination_index = get_traveler_location(test_traveler)

# print(test_destination_index)

attractions = [[], [], [], [], []]
# print(attractions)

def add_attraction(destination, attraction):
  destination_index = get_destination_index(destination)
  attractions_for_destination = attractions[destination_index]
  attractions_for_destination.append(attraction)
  return attractions_for_destination

add_attraction('Los Angeles, USA', ['Venice Beach', ['beach']])

print(attractions)

Here is the output:

$ python3 script.py
[[], [], [['Venice Beach', ['beach']]], [], []]
$

(Sorry I’m not allowed to add another image)

Please clear this confusion. So much of attraction here!! :smiling_face_with_tear:

Have a look at this post (If you still find something confusing, share your thoughts):


Appending to a list modifies/mutates the existing list.

x = [1, 2, 3]
y = x # Doesn't copy or create a new list. 
# Both variables hold reference to same memory location.
# Both variables point to same list.
print(x) # [1, 2, 3]
print(y) # [1, 2, 3]

y.append(4)
# append modifies/mutates existing list.
# Since both variables point to same list, so mutation is seen by both variables.
# We appended to y, but x points to the same list so it is also mutated/modified
print(x) # [1, 2, 3, 4]
print(y) # [1, 2, 3, 4]
2 Likes

If I do this

x = 5
y = x
y += 5
print(y) # 10
print(x) # 5

# Same happens with strings
x = "hello"
y = x
y += "hi"

print(x) # hello
print(y) # hellohi

# But Dictionaries get modified the same way lists does in your example.
x = {"key1" : 3}
y = x
y["key2"] = 5

print(x) # {"key1": 3, "key2": 5}
print(y) # {"key1": 3, "key2": 5}

Does this only happens with lists and dictionaries?
If yes, how would I make a copy of a list, to perform some tasks on the list without actually modifying it?

If no, can you tell me with what all data types does this occur?

Numbers, strings, tuples are immutable, whereas lists and dictionaries are mutable.

For a bit more on how += behaves with lists (as opposed to immutable data types), have a look at this post: Why use append when you can directly update a list? - #2 by mtrtmk

You may want to play around with a few examples on your own.

# If a is a list, then
# a += [4] is NOT equivalent to a = a + [4]

a = [1, 2, 3]
b = a
a += [4]  # += mutates the existing list
print(a) # [1, 2, 3, 4]
print(b) # [1, 2, 3, 4]

a = [1, 2, 3]
b = a
a = a + [4]  # Not mutation. Instead new list is created. 
print(a) # [1, 2, 3, 4]
print(b) # [1, 2, 3]

To create a copy of list, you can use slicing or the list() type constructor (and a few other ways).

# Slicing
a = [4, 9, 12]
copied_list = a[:]
a[2] = 100
print(a)  
# Output: [4, 9, 100]
print(copied_list) 
# Output: [4, 9, 12]

# Type Constructor
a = [4, 9, 12]
copied_list = list(a)
a[2] = 100
print(a)  
# Output: [4, 9, 100]
print(copied_list) 
# Output: [4, 9, 12]

Slicing or the list type constructor actually create shallow copies. If there was a list nested within a list, then mutating the nested list in the original list would also mutate the copied list.

a = [4, 9, [12, 32, 22]]
copied_list = a[:]
a[2][1] = 100
print(a)  
# Output: [4, 9, [12, 100, 22]]
print(copied_list) 
# Output: [4, 9, [12, 100, 22]]

For such cases, we can use deepcopy from the copy module after importing (copy — Shallow and deep copy operations — Python 3.11.1 documentation).

If the elements of the list you want to copy are numbers/strings, then slicing or list() type constructor is sufficient. In such a situation, you aren’t dealing with mutable types such as lists/dictionaries, so a shallow copy of the list gets the job done.
However, if the elements of the list you want to copy are lists/dictionaries, then you need to make a deep copy so that any changes/mutations to the nested mutable elements doesn’t affect the copied list.

1 Like

You really seem to be a genius problem solver. Such in depth knowledge of python, I am amazed. Thanks for clearing my doubt!