`dict()` is More Versatile Than You May Think
A short article to explore the various ways you can use `dict()` to create a dictionary.
The other day, I saw code that used dict()
in a way I had never seen before. My initial thought was:
Hmm, never seen that before…
A couple of seconds of contemplative thinking later and the first thought was replaced by a second:
Yeah, that makes sense, I suppose…
And then my curiosity took over:
So, what are all the different ways I can use
dict()
to create a dictionary?
Let's explore these in this short post. This article is not my usual type of post. It's a bit more 'matter-of-fact-y'. But I enjoy these types of exercises from time to time. They help me understand Python tools better.
The "No Arguments" Option
The simplest and shortest is the call with no arguments:
Useful to know. Should you use this option or the empty braces to create an empty dictionary? The braces, or curly brackets, are usually preferred.
And did you know it's also more efficient to use {}
instead of dict()
to create an empty dictionary?
This code times how long it takes to create a hundred million empty dictionaries using {}
and dict()
. So, if you want to create a few million empty dictionaries, you should definitely use {}
!
Let's Get Help From help()
What about other options? A good place to look is in the docstrings, which you can display using the built-in help()
. I'm truncating the rather long output since it's the first few lines that you need:
The docstring for the dict
class lists four different ways to use the constructor to create a dictionary. We've already discussed the first one, which has no arguments and returns an empty dictionary. Let's now look at the others.
The "Mapping" Option
You can pass a mapping as an argument when you call dict()
. The most common and well-known mapping in Python is the dictionary–but it's not the only one.
For example, you may have a Counter
object that you want to cast into a dictionary:
A Counter
object is already a dictionary since Counter
is a subclass of dict
. However, when you pass it to dict()
, you "downgrade" it to just a dictionary. But you can use any mapping as an argument to dict()
.
You can also use a dictionary as an argument to dict()
, but you'll get a dictionary back again. So what's the point, you may think? And you may be right.
But you may have a function that accepts a mapping, say, and you want to ensure that you're using a dictionary within your function definition by calling dict()
:
So, you need to pass a dictionary to dict()
in this situation if you call some_useful_function()
with a dictionary.
There's another reason you may want to pass a dictionary to dict()
. The dict()
call creates and returns a new dictionary. Therefore, you can use it to create a copy of a dictionary:
When you pass a dictionary to dict()
you get a copy of the dictionary. You can see the dictionaries are not the same object since you get False
with the is
keyword. You can also see that id()
returns a different number since they're different objects. But you get True
when using ==
, which shows that the dictionaries are equal.
However, if you want to create a copy of a dictionary, you can use the .copy()
dictionary method.
The "Iterable of Pairs" Option
The third option listed in the docstring is to pass an iterable as an argument to dict()
. But you can't just use any iterable:
Python's dict()
constructor can't make sense of this list. How can it create a dictionary from it? It can't. A dictionary needs key and value pairs.
However, if you have an iterable of pairs, dict()
can use the first in each pair as a key and the second as a value:
You start with a list of tuples, and each tuple has two items. You convert this list of tuples to a dictionary when you pass it to dict()
.
Perhaps a more useful example of this scenario is one that uses zip()
:
The built-in zip()
returns an iterator. Each item yielded by the zip
object contains two elements since you pass two objects to zip()
.
Are you considering joining my community at The Python Coding Place? Just email me if you have any questions about The Place
The "Keyword Arguments" Option
The final option listed in the docstring says dict(**kwargs)
. You can use any number of keyword arguments when you call dict()
:
The keywords become dictionary keys, and the arguments are the values.
Can You Mix and Match?
The docstring may give the impression that those are the only options. But can you mix and match the argument types?
Let's find out. Let's stick with the names and points example:
You create a dictionary and a list of two-tuples (they're tuples with two items within them). Can you include a mapping and an iterable of pairs in a single call to dict()
?
No, you can't. Could it be that we can't mix and match different argument types? Let's try some other options. Let's start with a dictionary as the positional argument followed by keyword arguments:
This works. The new dictionary includes the items in points_dict
and the two additional keyword arguments.
How about passing an iterable of pairs and keyword arguments to dict()
?
This also works. The first two items, which include James's and Denise's points, come from points_iterable
. The final two items in the dictionary come from the two keyword arguments.
The dictionary is created using any positional argument first, such as points_dict
or points_iterable
in the examples above. Any keyword arguments are added after. So, what if you include a keyword argument with a key that's already included in the positional argument?
The list of tuples points_iterable
already includes a value for James–James had 12 points. But you also include the keyword argument James=2
. The dictionary returned by dict()
shows that James only has two points.
Fun fact (or perhaps just 'fact'!): If you include both a positional argument and keyword arguments and the keys in the positional argument aren't strings, you'll get a type hint warning. This is not one of the intended uses for dict()
. But you can still create a dictionary this way despite the type hint warning:
You'll get a warning for both of these calls if you're using tools that flag unexpected data types since the dictionary created from the positional arguments has an integer key. However, dict()
still returns the dictionaries.
You can also read the 'dict' entry in the documentation, which adds a bit more information about the types of arguments you can use.
Final Words
Did I miss any options when calling dict()
? I think the ones mentioned here cover all of them. There are many other ways of creating dictionaries without calling dict()
directly, of course. But this brief post covers what you can do by calling dict()
.
Code in this article uses Python 3.12
Stop Stack
#63
Thank you to all those who supported me with a one-off donation recently. This means a lot and helps me focus on writing more articles and keeping more of these articles free for everyone.
Here's the link again for anyone who wants to make a one-off donation to support The Python Coding Stack
The Python Coding Book is available (Ebook and paperback). This is the First Edition, which follows from the "Zeroth" Edition that has been available online for a while—Just ask Google for "python book"!
And if you read the book already, I'd appreciate a review on Amazon. These things matter so much for individual authors!
I'm also releasing The NumPy Mindset at the moment. Currently, this is available as an Early Release—I'm publishing chapters as and when they're ready. Members of The Python Coding Place already have access to this Early Release. Everyone else can get it here—You'll get the final ebook version too once it's ready if you get the Early Release version, of course!
And for those who want to join The Python Coding Place to access all of my video courses—past and future—join regular live sessions, and interact with me and other learners on the members-only forum, here's the link:
Any questions? Just ask…
Appendix: Code Blocks
Code Block #1
here_is_a_dictionary = dict()
here_is_a_dictionary
# {}
Code Block #2
import timeit
timeit.timeit("{}", number=100_000_000)
# 2.0524105639997288
timeit.timeit("dict()", number=100_000_000)
# 6.426134301997081
Code Block #3
help(dict)
# Help on class dict in module builtins:
# class dict(object)
# | dict() -> new empty dictionary
# | dict(mapping) -> new dictionary initialized from a mapping object's
# | (key, value) pairs
# | dict(iterable) -> new dictionary initialized as if via:
# | d = {}
# | for k, v in iterable:
# | d[k] = v
# | dict(**kwargs) -> new dictionary initialized with the name=value pairs
# | in the keyword argument list. For example: dict(one=1, two=2)
# |
# | Built-in subclasses:
# | StgDict
# |
# | Methods defined here:
# ...
Code Block #4
from collections import Counter
letters = Counter(
"I love reading The Python Coding Stack".lower()
)
letters
# Counter({' ': 6, 'i': 3, 'o': 3, 'e': 3, 'n': 3, 't': 3,
# 'a': 2, 'd': 2, 'g': 2, 'h': 2, 'c': 2, 'l': 1,
# 'v': 1, 'r': 1, 'p': 1, 'y': 1, 's': 1, 'k': 1})
type(letters)
# <class 'collections.Counter'>
letters_dict = dict(letters)
letters_dict
# {'i': 3, ' ': 6, 'l': 1, 'o': 3, 'v': 1, 'e': 3, 'r': 1,
# 'a': 2, 'd': 2, 'n': 3, 'g': 2, 't': 3, 'h': 2, 'p': 1,
# 'y': 1, 'c': 2, 's': 1, 'k': 1}
type(letters_dict)
# <class 'dict'>
Code Block #5
def some_useful_function(mapping):
mapping = dict(mapping)
# do something with mapping with the
# guarantee that it is a dictionary
...
Code Block #6
publication = {
"name": "The Python Coding Stack",
"topic": "Python articles and tutorials",
"author": "Stephen Gruppetta",
}
publication_again = dict(publication)
id(publication)
# 4393125120
id(publication_again)
# 4373143296
publication_again is publication
# False
publication_again == publication
# True
Code Block #7
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
days_dict = dict(days)
# Traceback (most recent call last):
# ...
# ValueError: dictionary update sequence element #0 has length 6; 2 is required
Code Block #8
my_week_planner = [
("Monday", "Write The NumPy Mindset"),
("Tuesday", "Record video courses"),
("Wednesday", "Write articles and run Office Hours"),
("Thursday", "Write The NumPy Mindset"),
("Friday", "Record video courses"),
]
my_week_dict = dict(my_week_planner)
my_week_dict
# {
# 'Monday': 'Write The NumPy Mindset',
# 'Tuesday': 'Record video courses',
# 'Wednesday': 'Write articles and run Office Hours',
# 'Thursday': 'Write The NumPy Mindset',
# 'Friday': 'Record video courses',
# }
Code Block #9
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
activities = [
"Write The NumPy Mindset",
"Record video courses",
"Write articles and run Office Hours",
"Write The NumPy Mindset",
"Record video courses",
]
my_week_dict = dict(zip(days, activities))
my_week_dict
# {
# 'Monday': 'Write The NumPy Mindset',
# 'Tuesday': 'Record video courses',
# 'Wednesday': 'Write articles and run Office Hours',
# 'Thursday': 'Write The NumPy Mindset',
# 'Friday': 'Record video courses',
# }
Code Block #10
points = dict(Paul=10, Mary=8, Kate=13)
points
# {'Paul': 10, 'Mary': 8, 'Kate': 13}
Code Block #11
points_dict = {"Paul": 10, "Mary": 8}
points_iterable = [("James", 12), ("Denise", 9)]
Code Block #12
dict(points_dict, points_iterable)
# Traceback (most recent call last):
# ...
# TypeError: dict expected at most 1 argument, got 2
Code Block #13
dict(points_dict, James=12, Denise=9)
# {'Paul': 10, 'Mary': 8, 'James': 12, 'Denise': 9}
Code Block #14
dict(points_iterable, Paul=10, Mary=8)
# {'James': 12, 'Denise': 9, 'Paul': 10, 'Mary': 8}
Code Block #15
dict(points_iterable, Paul=10, Mary=8, James=2)
# {'James': 2, 'Denise': 9, 'Paul': 10, 'Mary': 8}
Code Block #16
dict([(1, "one")], Paul=10, Mary=8, James=2)
# {1: 'one', 'Paul': 10, 'Mary': 8, 'James': 2}
dict({1: "one"}, Paul=10, Mary=8, James=2)
# {1: 'one', 'Paul': 10, 'Mary': 8, 'James': 2}
The dict (and set) classes are a favorite part of Python. I subclass dict a lot to create self-loading dictionaries and whatnot. Very useful class!
I suppose it isn't an obvious call to the dict constructor, but there is a dict comprehension:
hex_chars = {cx: chr(cx) for cx in range(65, 71)}
Comprehensions being one of the coolest things about Python!
When using `dict()` to copy a dictionary is it a shallow copy or a deep copy?