Using reveal_type() to debug types
The last fundamental feature I want to introduce about mypy is the existence of
the reveal_type
function.
This function doesn't actually exist in Python, in the typing
module, or
anywhere else. But mypy understands it. reveal_type
is used to ask mypy
about what it thinks the type of a variable is.
Here's a quick demo:
string = "This is a string"
length = len(string)
reveal_type(length)
So we're able to know that mypy thinks that length
is an integer. But if you
try to run the code, it will immediately crash, saying reveal_type
is not
defined.
And this is a feature -- this ensures you don't accidentally leave a
reveal_type
statement in your code after you're done debugging. Also When you
use reveal_type
, mypy exits with an error code, saying that the code isn't
currently in working shape.
But the important part is, now you can easily know what mypy is thinking about your code, and you can debug your types.
An old friend
Let's use reveal_type
to debug an older code example that we saw and fixed:
import json
from itertools import cycle
items_json = """
{
"items": [
"item 1", "item 2", "item 3", "item 4", "item 5",
"item 6", "item 7", "item 8", "item 9", "item 10"
]
}
"""
def get_worker_count(): return 4
def run_processes() -> None:
worker_count = get_worker_count()
print("Running with " + worker_count + " workers")
workers = range(worker_count)
items = json.loads(items_json)
reveal_type(items)
for item, worker in zip(items, cycle(workers)):
print(f"Processing {item} with worker {worker}")
if __name__ == "__main__":
run_processes()
Using reveal_type, we see a new result: it says the type of items
is Any
.
We'll talk about Any
type and its uses in detail in the next chapter, but you
can understand it basically saying "items
can be anything". Mypy has no idea
what it can be.
With the fixed code, mypy understands the types correctly:
import json
from itertools import cycle
from time import sleep
items_json = """
{
"items": [
"item 1", "item 2", "item 3", "item 4", "item 5",
"item 6", "item 7", "item 8", "item 9", "item 10"
]
}
"""
def get_worker_count() -> int: return 4
def run_processes() -> None:
worker_count = get_worker_count()
print("Running with " + str(worker_count) + " workers")
workers = range(worker_count)
data = json.loads(items_json)
items = data['items']
assert isinstance(items, list)
reveal_type(items)
for item, worker in zip(items, cycle(workers)):
reveal_type(item)
reveal_type(worker)
print(f"Running {item} with worker {worker}")
if __name__ == "__main__":
run_processes()
Now mypy is confident that items
is a list. But what does it contain? No idea.
Could be anything. Similarly, checking the types of item
and worker
, we get
Any
and int
respectively. Worker is definitely an integer, but item can still
be anything. Which is alright: print
can print anything out so mypy is okay
with the code.
Hopefully that explains a lot more about mypy's workings. Feel free to make
changes to the code, and use reveal_type
to figure out what mypy thinks.