A Wandering Mind · Computer Science
PYTHON GUIDE

Learn · Run · Break · Fix

A learning series from A Wandering Mind

Eighteen interlinked lessons on strings, lists, files, exceptions, and dictionaries—with original explanations, runnable examples, real output, and supporting CPSC 1301K lectures.

course.py
>>> guide = {
...   "lessons": 18,
...   "modules": 5,
...   "python": "3.10+"
... }
>>> guide["goal"] = "understand it"
Module 5 · Dictionaries · Lesson 17 of 18

Checking, Aliasing, and Copying Dictionaries in Python

Dictionary membership tests answer a slightly different question from list membership: by default, in checks keys. Dictionaries are also mutable objects, so assigning one dictionary variable to another can create an alias rather than an independent copy. This lesson connects those ideas with values(), object identity, copy(), and the shallow-copy behavior of nested mutable values.

Prerequisite: Complete Dictionary Keys, Values, and Methods. You should already understand direct key lookup, assignment, get(), and dictionary views.

in checks dictionary keys by default

With a list, value in items asks whether that value appears as one of the list elements. With a dictionary, a plain membership test asks whether a value is present among the dictionary’s keys. It does not automatically search the associated values.

This is useful because checking for a key is one of the most common dictionary questions. Before a strict square-bracket lookup, your program may want to know whether a key exists. The expression returns a Boolean just as list membership does.

Example 1 · Check keys and values deliberatelyVerified
profile = {"name": "Ada", "language": "Python"}
print("name" in profile)
print("level" in profile)
print("Python" in profile)
print("Python" in profile.values())
True
False
False
True

"name" is a key, so the first test is True. "Python" is a value rather than a key, so "Python" in profile is False. When the question is specifically about values, the expression can test profile.values() instead.

Read the expression as a question about keys. key in dictionary and key in dictionary.keys() test the same basic membership relationship. The shorter form is usually preferred.

not in checks for an absent key

The not in operator expresses the opposite membership test. A common pattern is to initialize a dictionary entry only when a key is absent. For example, if "visits" not in data: can guard the creation of a new counter before later code updates it.

That does not replace get(). The two tools answer related but different needs. Use membership when the program needs a Boolean decision about existence; use get() when the program wants a retrieved value with an optional fallback.

Assignment can create an alias, not a copy

Dictionaries are mutable objects, just like lists. If you write alias = original, Python does not automatically duplicate the dictionary. Both variable names refer to the same dictionary object.

Example 2 · Two names can share one dictionaryVerified
original = {"theme": "dark", "volume": 5}
alias = original
alias["volume"] = 8
print(original)
print(alias is original)
{'theme': 'dark', 'volume': 8}
True

Changing alias changes the object also visible through original. The identity operator is confirms that both names refer to the same object. This is the dictionary version of the aliasing behavior you saw with lists in Lesson 8.

copy() creates a separate outer dictionary

When you want a new dictionary object containing the same current key/value mappings, dict.copy() creates a shallow copy. The new dictionary has a different identity from the original, so replacing one of its top-level values does not replace the corresponding top-level value in the original.

Example 3 · Change a copied dictionary independentlyVerified
original = {"theme": "dark", "volume": 5}
copied = original.copy()
copied["volume"] = 8
print(original)
print(copied)
print(copied is original)
print(copied == original)
{'theme': 'dark', 'volume': 5}
{'theme': 'dark', 'volume': 8}
False
False

The dictionaries are different objects, so copied is original is False. After the copy’s volume changes, their contents are also different, so equality becomes False. Before that mutation, two separate dictionaries with equal key/value content could compare equal with == while still being different objects under is.

A shallow copy does not recursively copy nested objects

The word shallow is important. copy() creates a new outer dictionary, but it reuses references to the same nested mutable objects stored as values. If a value is a list and you mutate that list, both dictionaries can still observe the same nested change.

Example 4 · See the shallow-copy boundaryVerified
original = {"tags": ["python", "files"]}
copied = original.copy()
copied["tags"].append("dicts")
print(original)
print(copied)
print(copied["tags"] is original["tags"])
{'tags': ['python', 'files', 'dicts']}
{'tags': ['python', 'files', 'dicts']}
True

The outer dictionaries are separate, but both "tags" entries still point to the same list. Mutating that shared inner list is visible through both dictionaries. A truly independent recursive copy requires a deeper copying strategy, such as copy.deepcopy() from Python’s standard-library copy module. That is useful to recognize, but it is beyond the core syntax this beginner guide needs.

Key existskey in data

Checks the dictionary’s keys and returns a Boolean.

Value existsvalue in data.values()

Checks the values view instead of the keys.

Aliasother = data

Creates another reference to the same dictionary object.

Shallow copyother = data.copy()

Creates a new outer dictionary while nested mutable values may remain shared.

Dictionary views are iterable, but they are not lists

The keys(), values(), and items() methods return view objects. These views can be looped over and can participate in membership tests, but they do not behave exactly like list snapshots. In particular, you cannot assume that data.keys()[0] or data.values()[0] works like list indexing.

If you genuinely need a separate list, convert the view explicitly with list(data.keys()) or a similar expression. Do this because a list is useful to the next operation, not merely because the printed view looks unfamiliar.

Common mistakes when checking and copying dictionaries

The four failures below were executed in Python. They show where dictionary membership and view/copy behavior differ from common list assumptions.

Mistake 1 · Testing an unhashable list as a key
profile = {"name": "Ada"}
print(["name"] in profile)
Traceback (most recent call last):
  File "/tmp/ipykernel_313/2045328123.py", line 12, in run_err
    exec(compile(code, filename, "exec"), {})
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/awm_lesson17/mistake1_unhashable_membership.py", line 2, in <module>
TypeError: unhashable type: 'list'

Dictionary membership checks keys. A list cannot be a dictionary key because lists are mutable and unhashable.

Mistake 2 · Indexing a keys view like a list
profile = {"name": "Ada", "language": "Python"}
keys = profile.keys()
print(keys[0])
Traceback (most recent call last):
  File "/tmp/ipykernel_313/2045328123.py", line 12, in run_err
    exec(compile(code, filename, "exec"), {})
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/awm_lesson17/mistake2_keys_index.py", line 3, in <module>
TypeError: 'dict_keys' object is not subscriptable

A dict_keys view is iterable but not subscriptable. Convert it to a list only when positional list behavior is actually required.

Mistake 3 · Indexing a values view like a list
profile = {"name": "Ada", "language": "Python"}
values = profile.values()
print(values[0])
Traceback (most recent call last):
  File "/tmp/ipykernel_313/2045328123.py", line 12, in run_err
    exec(compile(code, filename, "exec"), {})
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/awm_lesson17/mistake3_values_index.py", line 3, in <module>
TypeError: 'dict_values' object is not subscriptable

The same rule applies to dict_values. It supports iteration, but not numeric indexing.

Mistake 4 · Passing an argument to copy()
profile = {"name": "Ada"}
copied = profile.copy("extra")
Traceback (most recent call last):
  File "/tmp/ipykernel_313/2045328123.py", line 12, in run_err
    exec(compile(code, filename, "exec"), {})
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/tmp/awm_lesson17/mistake4_copy_argument.py", line 2, in <module>
TypeError: dict.copy() takes no arguments (1 given)

dict.copy() takes no arguments. It copies the dictionary object on which the method is called.

Supporting classroom explanations

This page is original A Wandering Mind instructional material. The two assigned CPSC 1301K lectures below reinforce dictionary membership and the aliasing/copying behavior of mutable mappings.

in Operator — by Hyrum Carroll · 4:18. Watch on YouTube ↗ · CPSC 1301K playlist ↗

What it adds: a focused classroom explanation of using membership tests with dictionary keys.

Aliasing and Copying Dictionaries — by Hyrum Carroll · 3:55. Watch on YouTube ↗ · CPSC 1301K playlist ↗

What it adds: a concise comparison between sharing one dictionary through aliases and creating a separate shallow copy.

Try it yourself

Before running each answer, predict whether the expression is checking keys or values and whether two variable names refer to one object or two.

Exercise 1 · Distinguish keys from values

Create settings = {"theme": "dark", "volume": 5}. Test whether "theme" is in the dictionary, whether "dark" is in the dictionary, and whether "dark" is in the values view.

Show one answer
settings = {"theme": "dark", "volume": 5}
print("theme" in settings)
print("dark" in settings)
print("dark" in settings.values())
True
False
True

Exercise 2 · Demonstrate aliasing

Create original = {"score": 10}, assign alias = original, change the score through alias, then print the original score and whether the objects are identical.

Show one answer
original = {"score": 10}
alias = original
alias["score"] = 20
print(original["score"])
print(alias is original)
20
True

Exercise 3 · Demonstrate an independent top-level copy

Copy {"score": 10} with copy(), change only the copy’s score to 20, then print both scores and whether the objects are identical.

Show one answer
original = {"score": 10}
copied = original.copy()
copied["score"] = 20
print(original["score"])
print(copied["score"])
print(copied is original)
10
20
False

What you should carry into Lesson 18

You now know that plain dictionary membership checks keys, value membership can be tested through values(), direct assignment between dictionary variables creates an alias, and copy() creates a separate outer dictionary while nested mutable values may still be shared.

The final lesson steps back from syntax and asks a design question: when should a program use a list, and when should it use a dictionary? Continue to List or Dictionary? Choosing the Right Collection.