Python in Detail: Dicts Are Fundamental
Welcome to the Dicts Are Fundamental lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
The data structure itself is very common! But Python's relationship to
dictis different from these other languages in an important way.In all of these languages, dictionaries (or maps or hashes or hashmaps) are a tool to be used as needed. But the entire Python language is built on dictionaries, even including fundamental features like object attributes.
We've already seen that the
.__dict__method on objects gives us the dictionary where the object's attributes are stored.>
class Cat:def __init__(self, name, vaccinated):self.name = nameself.vaccinated = vaccinateddef needs_vaccination(self):return not self.vaccinated- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
keanu = Cat("Keanu", True)keanu.__dict__Result:
We can use
.__dict__like a regular dictionary. If we add a key to.__dict__, it shows up as an attribute on the object.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
keanu = Cat("Keanu", True)keanu.__dict__["we_added_this_attribute"] = 1keanu.we_added_this_attributeResult:
1
This also works for class attributes, since classes are just a special kind of object. And it works for methods, since methods are just attributes that are also callable.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
Cat.needs_vaccination is Cat.__dict__['needs_vaccination']Result:
True
We can even call methods directly from
Cat.__dict__.- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
keanu = Cat("Keanu", True)# This does the same thing as `keanu.needs_vaccination()`.Cat.__dict__['needs_vaccination'](keanu)Result:
False
Most languages let us list an object's attributes, or list an object's methods. But
.__dict__isn't just a way to ask for a list of attributes or methods. It's bidirectional, as we saw when we added a key in the example above: we can change the object by changing its.__dict__. In Python,.__dict__really is where the attributes are stored. It's not just a trick for our convenience!Dicts show up in some other surprisingly fundamental places. For example, we can call
globals()to get a dict of all global variables that are currently in scope.>
type(globals())Result:
dict
>
name = "Amir"globals()["name"]Result:
'Amir'
The
globals()dict allows the same kind of bidirectional manipulation that we saw with.__dict__. We can define a new global variable by adding a key to theglobals()dict!>
globals()["twelve"] = 12twelveResult:
12
We can get a list of all globals by looking at
globals().keys().>
twelve = 12list(globals().keys())Result:
The
assert_raisesglobal is specific to Execute Program. We use it in some code problems to confirm that your code raises the expected exception.The
__builtins__variable is topic we'll cover in another lesson. Andtwelveis the variable that we created.There are limits to the "everything is a dict" philosophy. The first involves
locals(), which is likeglobals()except that it holds local variables. Unlikeglobals(), this dict is meant to be read-only. Python will technically let us write to thelocals()dict, but that won't actually change the variables' values.>
def read_local():x = 1locals()["x"] = 2return xread_local()Result:
1
That exception is an implementation detail of Python. Internally, the Python runtime optimizes functions in many ways, some of which are incompatible with a writable
locals()dict.The second exception involves the
.__dict__attribute. For performance reasons, some Python objects don't have a.__dict__at all. For example,ints,strs, and several other primitive types don't have.__dict__s. Trying to access.__dict__on them is an error.>
(5).__dict__Result:
AttributeError: 'int' object has no attribute '__dict__'
>
("a").__dict__Result:
AttributeError: 'str' object has no attribute '__dict__'
Ints, strings, and other fundamental types are very common, so their performance is critical. Omitting a
.__dict__on these types allows additional optimizations.(It's possible to intentionally opt out of
.__dict__in the same way, even for our own classes. If we write a data structure that's very performance-sensitive or memory-sensitive, we can choose not to have a.__dict__at all. But the full details of that are out of scope here.)As you write more Python code, you'll find dicts in many more places. This makes Python feel more tangible than many other languages. We can see all of the globals just by calling
globals(); we can see an object's attributes by looking at.__dict__; etc. This lets us dynamically explore the structure of our objects, classes, functions, and modules. When learning a new codebase, experienced Python programmers often use the Python console (the "REPL") to dig into the runtime values interactively.