Dictionaries created by lists (coach is not always right)

In the module “Prioritizing Tasks with Dictionaries and AI” of week 2
I was wondering whether the

dictionary prioritized_tasks {}

that has been created by using the list variables

high_priority_tasks, medium_priority_tasks, low_priority_tasks

changes if I modify these variables later on.
An experiment showed, it does not. The dictionary will be created and stores copies of the lists, not references to the lists. So you have to modify the values of the dictionary if you want a modification of the dictionary:

prioritized_tasks[“high_priority”] = [“new task 1”, “new task 2”]
In contrast high_priority_tasks = [“new task 1”, “new task 2”] will not do the trick and will only change that list but not the dictionary.

But the coach gave another answer (see below)
So, always check twice!

If a define a dictionary like this
prioritized_tasks = {
“high_priority”: high_priority_tasks,
“medium_priority”: medium_priority_tasks,
“low_priority”: low_priority_tasks
}
with high_priority_tasks being a list variable
would the dictionary change when I chnage high_priority_tasks later on?

Coach
Yes, if high_priority_tasks is a list and you change its contents later, the changes will be reflected in the prioritized_tasks dictionary. This is because the dictionary stores a reference to the list, not a copy of it. So, any modifications to the list will be visible through the dictionary.

Correct. A dictionary stores references to objects, not copies of them (unless explicitly copied). Lists are mutable objects, which means that if you change the contents of a list, the changes are reflected wherever the list is referenced, including within a dictionary; if you change the high_priority_tasks list by appending "new task 3", the change is reflected in the dictionary because the dictionary points to the same list object:

Modify the high_priority_tasks list:

high_priority_tasks.append(“new task 3”)

Check the dictionary:

print(prioritized_tasks[“high_priority”]) # Output: [‘new task 1’, ‘new task 2’, ‘new task 3’]

However, if you reassign the high_priority_tasks variable to a new list, the dictionary will no longer reflect the changes, as it will still be referencing the old list:

Reassign high_priority_tasks:

high_priority_tasks = [“new task4”, “new task5”]

Check the dictionary:

print(prioritized_tasks[“high_priority”]) # Output: [‘new task 1’, ‘new task 2’, ‘new task 3’]

Here, high_priority_tasks now points to a new list, but the prioritized_tasks dictionary still points to the original list. Thus, modifying the contents of a list is reflected in the dictionary, and reassigning the variable that holds the list does not affect the dictionary. This distinction is key to understanding how mutable objects like lists work when used as values in dictionaries.

5 Likes

Yes, as Nayid points out, object references in python can be subtle. Here’s another thread from a while back that gives some examples that are also relevant to the behavior of dictionaries containing array references (also mutable objects), although in that case the point is not to modify the global objects. Please don’t miss this post later in the thread to see another interesting example.

As always in programming, it’s crucial to have both the correct intentions and the correct understanding of the semantics of the language you’re using. :nerd_face:

3 Likes

Oh wow, that is very interesting. Thank you very much nadtriana and paulinpaloalto.
Indeed in my experiment I reassigned the high_priority_tasks list to a new list. I did not modify the current list. The list variable high_priority_tasks now points to another object.
Now I understand why
prioritized_tasks[“high_priority”]
still kept the old values. It points to the old object which in a way lost its reference by high_priority_tasks. So yes, the answer of the coach was correct (and I learned a lot).
Thank you also for the links to other threads about this topic.

3 Likes

Thank you again. I tried to put it in my words:

So as I understand if I create the dictionary

prioritized_tasks = {
“high_priority”: high_priority_tasks,
“medium_priority”: medium_priority_tasks,
“low_priority”: low_priority_tasks
}

then the values are list pointers that point at those objects that

high_priority_tasks, medium_priority_tasks and low_priority_tasks

are pointing at at the moment of creation of that dictionary.

They do not necessarily point at the objects high_priority_tasks, medium_priority_tasks and low_priority_tasks might be pointing at in the future.

If I modify any of these lists, then the pointers high_priority_tasks, medium_priority_tasks and low_priority_tasks are still referring to the same objects (and so any change is reflected in the dictionary), if I reassign them, they point to new objects (and so the value list pointers in the dictionary are still referring to the old objects).

So it is not that the value list pointers of the dictionary point to the objects of the defining list variables, they point to the objects, that these variables have been pointing at when the dictionary has been created.

So as you explained
reassignment = pointing somewhere else, dictionary still points to the same objects

modification = pointing is the same direction, therefore dictionary reflects change

Mammamia :upside_down_face:

1 Like

Hello Jochen, nice chat, I made this summarization to understand the problems, and I will drop it here in case it can help anyone, greeting!!

1. Dictionaries Store References, Not Copies

When you assign a list to a dictionary key:

high_priority_tasks = ["task1", "task2"]
prioritized_tasks = {"high_priority": high_priority_tasks}

The dictionary doesn’t create a new copy of the list. Instead, it stores a reference to the same list object in memory.

  • Implication: Any changes made to the list (mutations) will reflect in the dictionary.

2. Mutability of Lists

Lists are mutable, meaning you can change their contents without creating a new object.

Example:

high_priority_tasks.append("task3")
print(prioritized_tasks["high_priority"])  # ["task1", "task2", "task3"]

The change to high_priority_tasks is visible in the dictionary because the dictionary points to the same list object.


3. Reassignment Does Not Affect the Dictionary

If you reassign the list variable to a new list:

high_priority_tasks = ["new_task1", "new_task2"]
print(prioritized_tasks["high_priority"])  # ["task1", "task2", "task3"]

The dictionary still references the original list, not the new one. Reassignment only changes what the variable high_priority_tasks points to, not what the dictionary references.


4. Modifying the Dictionary Directly

To update the dictionary with a new list:

prioritized_tasks["high_priority"] = ["new_task1", "new_task2"]
print(prioritized_tasks["high_priority"])  # ["new_task1", "new_task2"]

This replaces the reference in the dictionary with a reference to the new list.


5. Shallow Copy vs. Deep Copy

Dictionaries store shallow references to objects. This distinction matters when dealing with nested structures.

  • Shallow Copy: Only the top-level references are copied.
import copy
shallow_copy = prioritized_tasks.copy()
  • Deep Copy: Creates a completely independent copy of all objects.
deep_copy = copy.deepcopy(prioritized_tasks)

Example with Nested Lists:

high_priority_tasks = [["subtask1", "subtask2"]]
prioritized_tasks = {"high_priority": high_priority_tasks}

# Modifying a nested element affects the dictionary
high_priority_tasks[0][1] = "changed"
print(prioritized_tasks["high_priority"])  # [["subtask1", "changed"]]

To prevent this, use a deep copy.


6. Immutable Types in Dictionaries

Immutable types (like integers, strings, and tuples) behave differently:

  • Dictionaries store copies of immutable types.
  • Changing a variable holding an immutable value has no effect on the dictionary.

Example:

count = 5
prioritized_tasks = {"task_count": count}

count = 10
print(prioritized_tasks["task_count"])  # 5

Practical Examples

Use Case: Syncing Data

If you need synchronized updates between a list and a dictionary, use references:

tasks = ["task1", "task2"]
task_dict = {"tasks": tasks}

tasks.append("task3")
print(task_dict["tasks"])  # ["task1", "task2", "task3"]

Avoiding Unintended Changes

To avoid changes propagating unintentionally, create a copy:

task_dict["tasks"] = tasks.copy()
2 Likes

Hello Nicolas,

thank you very much for this profound summary. I really appreciate the time and effort you and your colleagues take to support us and make things clear.

I realize it is not an easy topic and a misconception may result in unintended results that are very difficult to trace back.

It is very tempting to think that the dictionary stores a reference that points to wherever high_priority_tasks will point at (a pointer to a pointer so to say). Instead it points to the same list object in memory (two pointers now pointing at the same object and these pointers may be in sync or not).

Thank you also for the explanation of what mutable objects are and the practical examples creating awareness about what is intended and how to implement it.

1 Like