Static Typing in Python by Dustin Ingram

This video features Dustin Ingram at DjangoCon US 2019 in San Diego, California, USA.

Static Typing in Python by Dustin Ingram
0:32:08
Published October 25, 2019
1,874 views

DjangoCon 2019 - Static Typing in Python by Dustin Ingram

In this talk, we'll discuss the advantages and disadvantages to a static type system, as well as recent efforts to introduce static typing to Python via optional "type hints" and various tools to aid in adding types to Python code.

This talk was presented at: https://2019.djangocon.us/talks/static-typing-in-python/

LINKS:
Follow Dustin Ingram 👇
On Twitter: https://twitter.com/di_codes
Official homepage: https://di.codes

Follow DjangCon US 👇
https://twitter.com/djangocon

Follow DEFNA 👇
https://twitter.com/defnado
https://www.defna.org/

Intro music: "This Is How We Quirk It" by Avocado Junkie.
Video production by Confreaks TV.
Captions by White Coat Captioning.

Summary

Python remains dynamically typed at runtime, but optional annotations and external type checkers can provide gradual static typing without changing program execution. Dustin Ingram explains Python’s progression from function annotations to variable annotations, the typing module, and tools such as MyPy, Pytype, Pyre, and Pyright, noting that checkers differ in inference and strictness. He recommends using typing as machine-verified documentation—especially in large, confusing, public, or frequently refactored codebases—while keeping unit tests, and adopting it incrementally. For circular imports, annotations can refer to types by string, and Django projects can use external annotation packages such as django-stubs/MyPy Django, although Django’s dynamic internals make complete typing more difficult.

Key takeaways

  • Python is dynamically typed, but annotations can describe variables, parameters, return values, containers, unions, callables, and generic types for static analysis.
  • Type annotations do not enforce types at runtime; a separate checker such as MyPy, Pytype, Pyre, or Pyright examines the code while it is at rest.
  • Static typing is most useful in large or confusing codebases, public libraries, and code being refactored, where annotations provide machine-verified documentation and editor support.
  • Typing should complement rather than replace unit tests, and teams can add it gradually instead of annotating an entire codebase at once.
  • Circular imports can often be avoided by referring to types as strings, while Django users can rely on external stubs such as django-stubs/MyPy Django.

Summarised automatically from the transcript.

Chapters

  1. 0:00 Introduction to Static Typing Dustin Ingram introduces Python’s dynamic type system and previews the role of optional static typing.
  2. 4:08 Dynamic Typing and Duck Typing The talk explains changing variable types, flexible function arguments, and how Python infers behavior through duck typing.
  3. 7:13 Static Typing Concepts Static typing is contrasted with dynamic typing using examples from C, Java, Rust, and TypeScript.
  4. 9:32 The History of Python Typing The speaker traces the evolution of optional typing in Python, including its connection to Dropbox’s large codebase.
  5. 10:19 Function Annotations PEP 3107 and Python function annotations are introduced as metadata that does not affect runtime execution.
  6. 12:40 Gradual Typing and MyPy The talk covers gradual typing, Yuka Laks’s research, and MyPy’s origins as an experimental Python variant.
  7. 14:14 Python Type Hints PEPs 483 and 484 establish optional type hints, variable annotations, unions, generics, container types, and aliases.
  8. 19:45 Static Type Checkers The speaker explains how static checkers work and surveys MyPy, Pytype, Pyre, Pyright, and editor integrations.
  9. 22:17 Choosing a Type Checker MyPy and Pytype are compared through their different approaches to cross-function inference and runtime lenience.
  10. 22:49 When to Use Static Typing Recommendations cover large codebases, confusing or public APIs, refactoring, experimentation, and complementing unit tests.
  11. 26:41 Adopting Static Typing The talk concludes with a practical five-step plan for installing a checker, adding annotations gradually, and integrating checks into development.
  12. 28:00 Questions The Q&A addresses circular imports, forward-reference strings, and type annotations for Django.

Transcript

6,595 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:15

Speaker 1: Thanks everyone. So hey, I'm Dustin. Quick intro about me. So I'm a developer advocate at Google. That means that I represent the Python community internally at Google when we build products for Python Eastern. I'm also the chair for Pi Texas, by the way. It's going to be in Austin this year, or next year, sorry, May 16th and 17th. So you should come to Austin, come and hang out, go to Pi Texas. Uh I also work on the Python package index, um critical Python infrastructure that I'm sure you all use. Uh but I'm not actually gonna talk about any of those things today. So We're going to talk about static typing. Quick pop quiz for everyone in the room. Is Python dynamically typed or is it statically typed? So who thinks it's dynamically typed? And who thinks that, like, all right, this talks about static typing in Python? It's probably statically typed.

1:01

Speaker 1: Maybe. How about who thinks this is a trick question? Okay. That's kind of a trick question. Um the answer is that Python is dynamically typed, but can optionally be as statically typed as you want it to be. Uh so the answer to that might not actually make a lot of sense if you're not super familiar with typing and type systems. Uh so that's okay. That means that you're in the right talk. If that doesn't make sense. And we're going to explain that. And the steps to understand and answer that question are basically this. We're going to talk about types in Python, type systems in general. uh dynamic typing in python and then static typing in python. And then once we sort of understand what static typing in Python means, we'll talk about how to use static typing, when you should use it, and when you shouldn't use it. So let's talk about types and specifically let's talk about type.

1:48

Speaker 1: Uh when I say type, I mean the type built in in your Python interpreter. So uh in your Python REPL you could type something like this. Type 42. And this will tell you that 42 is an int. It's an integer. Uh you can also type other things. So the type of 42. 0 is a float, type of foo is string, type of that is list. And you might say, okay, I recognize these things. that are coming out of the the type function. These are like the built-ins that I can use to change one type to another, right? So you can take a integer like a here and you can sometimes we call this casting, cast it to a float Cast it to a string, cast it to a really ugly list. So who's seen a really ugly list like this before where a string is getting turned into a list? Right? Okay. If you've seen that before, that's a type error.

2:34

Speaker 1: You've had a type error in your code, and something that was expecting a list got a string instead. So it would also seem like these. are the only types available to us, but that's not entirely true. So uh these are just basically some uh mappers to classes that exist. So n tier is just a class. So I can say the type of 42 is an int. I get a class of int from int, and is instance 42 int is the same thing as doing class matching if something is an instance. Of another class. So there are more types than just int string float, right? So there's none type. We've all seen none type before, but uh you you can only get it by calling type on none. There's also a function type. So when we define a function, we don't say like function blah blah blah we say def function name etc.

3:20

Speaker 1: And there's like an ellipsis type, there's lots of types. And in fact, uh if you are in your Python REPL and you import types You get this whole list of all sorts of types of things that you could have in your Python code. And so these all live in the types module, and they actually can be used to instantiate a new thing. whatever this is, you can call open close parentheses on it with some arguments and create a new like a new function type. Uh but there's a reason we don't do this. It look looks really messy and there's like a lot of code to it. So when we say Python is a dynamically typed language, what it means is that variables can be any type at all, and they can change. So for example, if I imported random and I called random. choice on this list of things that are an integer, a float, and a string, and I call type of the result of that, what's the result?

4:09

Speaker 1: Like what type is a gonna be? It could be any of these, right? It could be a string and enter a float. And it depends on the outcome from random. So it doesn't matter what I'm putting in that variable, it can contain any of these types. Dynamic typing also means that the arguments and the return types of functions can also be any type, right? So the same is true for the arguments and the return values of any function. So if I create a function like this for abnicate, it takes two, three parameters, A, B, C, returns A plus B plus C. So in Python, when we write a function like this. How do we know that we're getting the type that we're expecting out of it, right? So if you saw this function and you had no idea what Frovniki meant, because it's a made-up word, uh, what would you expect the arguments to be here? And what would you expect the return type to be?

4:55

Speaker 1: So you might say like it might make sense for these to be integers, right? So I define the function I can call from the cate123 and I get six, the sum of all these integers. But also I could call Fromniki with strings. I can call Fromnike with three strings and it would concatenate those strings. That's totally valid, and it only works the function can return integers or strings. However, what I can't do is I can't mix these types now because I can actually use the plus operator on a mixture of strings and integers. So that blows up and that creates a type error. So it'd be great if we could say what this function actually is expecting for all these arguments and what it's expecting for expecting to return. So one thing we could do is write a really long and nice doc string doc string that lists all the arguments and the return types and what they are and some comments.

5:41

Speaker 1: Who does this? Does anyone do this? Does your employer pay you enough to do this? This takes a lot of work, right? Uh that's a long comment and You know, you gotta write it all out. Uh it also doesn't actually change the evaluation of the function really. You can still call this with strings and it's still gonna return a string. It's just a little handy guide there for the developer. We could do this. So we could write a function, we could assert on every single argument that we get, do our business logic or whatever we need to do. And then assert on the return type and then actually return it. I said in a previous talk one time I showed this example and I said, yeah, nobody does this. And then someone came up afterwards and they're like Like, well, actually we do do this. And so the reason you don't want to do this is because

6:27

Speaker 1: each assertion, well, I mean, while this works, each assertion adds just a little bit of overhead to your function call, right? Because that happens at runtime. each assertion. So, you know, while it's modest, there is a small performance problem there. And also, you know, it's a little bit messy looking to have all those asserts all the So we don't really do that. Um what do we do instead? In Python we do something called duct typing. So instead we uh duct typing is If it walks like a duck and it quacks like a duck, it is probably a duck. So we sort of observe the way that we interact with functions and variables and we can sort of infer what the types are from that. So for example, in these examples, right, I'm setting food to uh list comprehension calling f of x on x and bar. Bar is probably some iterable. It's something like a list or a string. And the second line, foo, is bar greater than zero.

7:13

Speaker 1: So bar is probably an integer, something I can compare to an integer, and foo is going to end up being like a Boolean. And the last one bar is something I can call open close parentheses on. But that means it could be a function or it could be a list. I could still actually kind of empty ambiguous what this is. And usually if it was if it was a class that I was calling, or sorry, it could be a function or a class. If it was a class bar would we'd probably capitalize it, but you know, not always. It's not. Required. So it could be anything. So static typing means that these variables and the return types and arguments to function are not gonna change. They're defined what the type is gonna be, and it's not gonna change. And so there are actually a lot of statically typed languages. Um I want to show you some examples of that same function, that Frobnikate function, in multiple languages. So

7:59

Speaker 1: does anyone know which language this is? I heard it. Yeah, C, that's C. How about this one? Java? Yeah, that's a dead giveaway with like public static in, you know. Uh anyone know what this one is? Rust. Yeah, Rust. Every time I do this talk and the Rust one comes up, so I'm like, yeah, that's Rust, I know Rust. Yeah, that's Rust. Because it Rust actually has really fine grain. That's an unsigned 8-bit integer for the argument. argument types there. So you can say exactly uh what type of integer it is, not just an integer. Uh how about this one? Anyone? I heard JavaScript, but it's actually TypeScript. It's typed JavaScript. Uh the way the way you would know is uh JavaScript actually has only one number type. It's number like floats and integers, everything is Uh okay, so you can sort of put languages in two columns, right?

8:45

Speaker 1: Dynamic languages are like Python, Ruby, uh Clojure and JavaScript, static are like C. E Rust and TypeScript. And I actually have to put, you know, like a little asterisk next to Python. And we're gonna talk about why. Uh because you can do some static typing things in Python. And also technically Ruby is going to also get the same thing that Python currently has, but not until the end of next year. So we'll ignore that. So like I earlier I said, Python is dynamically typed, but can optionally be as statically typed as you want it to be. So the story of that Becoming true. That wasn't always true, right? There was a point in time when Python was purely a dynamically typed language. And the story of Python becoming a language you could optionally statically typed is also kind of the story of the evolution of code at Dropbox.

9:32

Speaker 1: Does anyone work at Dropbox here? No. Okay. So so Dropbox uh is a large tech company and they're a Python shop also and they have millions of lines of Python code. And basically at a certain point in their uh evolution as a company, they realized that the fact that their Python code was lacking any kind of type system or type annotation was actually slowing them down. Like it was causing their developers to be be less productive when they were looking at code because there was so much of it. It was so intertangled. There's so many different like areas of the code base. And so they actually wrote a blog post. Uh you can go and find it on their blog. It's super good and it talks about sort of the a little bit about the history of static typing at Dropbox, but I'm gonna actually talk about the history of static typing in Python, which steps back a little bit from the start of it at Dropbox. And so the start of it in Python starts here.

10:19

Speaker 1: Uh at PEP 3107. Uh this is the functions annotations PEP. Uh we got this in 2006 and it only in Python 3. And what this allowed us to do is take a function like this. which is the same one I showed before. And then we can add annotations, any metadata that we want to the arguments and to the return type. So these aren't types. This is just like literally any Python code you want, you could put in the annotation for this function. And this has zero effect on the the execution of the function, what it does instead was it gives you uh this annotations, dunder annotations attribute. And what it would do is evaluate uh the value of all those annotations. So it execute the annotations And so here it would say like, okay, A has this annotation of a string of X, B, I've added five and six, so now it's 11, C, and then the return uh annotation.

11:07

Speaker 1: So this is interesting, and in the PEP they uh sort of outline some uses for these annotations, right? So all the uses kind of basically boil down to you can do interesting typing things with it and then also like maybe or you can use it for documentation, but you probably should use it for types. But it didn't really specify a way to use it for types. But we could imagine doing something like this with it, where uh in the annotation we just write put the type there, right? We say A is an int, B is an int an int, C is an int, returns an int. And so when I look at the annotations, I see what all of those are supposed to be. However, just because I can annotate a function again doesn't mean I can actually evaluate whether that function is being used correctly elsewhere. Like Python is not going to look at these annotations at runtime and figure out if they're right or wrong or it's being called correctly.

11:52

Speaker 1: correctly. So it only gives us a way to annotate this function and actually only functions too. I can't annotate variables with this. It's only for functions. So a little bit around the same time, uh Yuka Lakaslo was working on his PhD thesis at University of Cambridge. And his PhD thesis was about this. It was about the unification of a statically typed and a dynamically typed language. Basically, how could you have a language that had both? And the goal was to be able to use the same language for everything from a tiny script where you don't really care about typing that much, to like a sprawling multi-line code base, where like Dropbox. you start to actually care about typing. And he had this idea of gradual growth where you could go from something that was untyped and slowly add type annotations optionally where you needed it until you had something that was fully statically typed.

12:40

Speaker 1: So he published a paper, uh his thesis in 2011, and in it he concluded this. He said that adding a static type system to a dynamically typed language could be a totally invasive change. requires coordinated modification of like everything basically. Uh the program, the runtime, the virtual machines, all the tools, everything. He also said that You could optionally do an optional an optional pluggable type system that doesn't actually affect the runtime of the program and is basically a layer on top of the program and could wouldn't require this modification of all the existing uh So this sounds really great. Uh without having to change Python would be awesome. So in 2013 he gave a talk at um PyCon US, and he introduced something called MyPy. So if you are familiar with MyPy, you

13:27

Speaker 1: this is probably not what you're familiar with. When he introduced it at the time, this is what MyPy was. MyPy is an experimental variant of Python that supports writing programs that seamlessly mix dynamic and static typing. So this was not what you think of as MyPy now. MyPy used think of now as a static type checker. At the time it was actually a variant of Python itself. It was uh its own language. And so in his research he actually he didn't even use Python as an example. He made up a language sort of as a toy language to show that this was actually possible possible. So my Pi, the language, actually look like this. And it kind of looks like Python. It was definitely inspired from Python. But it had some extra stuff to allow him to do variable annotations and better function annotations. Because at the time all we had was function annotations.

14:14

Speaker 1: So he gave this uh presentation at PyCon and he said he uh he gave the presentation and he talked to Guido about it, and Guido is like, yeah, just ditch this ditch the variant language. Like let's just do this in PyCon. Python proper. And so he said, okay. So he did convince them to drop the custom syntax and stick to just Python 3 where we have function annotations and we can do some other stuff as well. Well. So in 2014 we got uh PEP 483, and this was Guido writing down basically his theory of type hints, like how typing should work in Python. So there were some big ideas here. One was sort of drew from US UK 's paper. One was optional typing. Adding annotations to the runtime shouldn't affect the actual runtime of your program. So like I said before, an unannotated function

15:00

Speaker 1: should run exactly the same as an annotated function. And I think this sort of distills from lessons that we learned in the Python 2 to 3 migration, right? We don't want to introduce this huge breaking change across Python versions. Uh we want it to be sort of like uh gradual and available if you want it, but not going to get in your way. The other was gradual typing. So let's not try to do this all at once, right? Typing a giant code base is going to be impossible to do all at once. You should be able to do little pieces at a time. He also introduced the idea of variable annotation, so this was building on the idea of function annotation so that we could annotate variables as well. And so that would mean that we could have a function like this, and we could add a little comment that says the type of this variable I've just defined is an integer. This also meant that we could do type hinting in Python 2.

15:46

Speaker 1: So the syntax for function annotation is only came in Python 3, but because even those uh on like a super old version of Python still deserve to be able to do static typing, uh, we were able to take these type of type comment annotations and backport them to Python 2 and sort of get the same behavior. So in Python 3, we could actually put them in line with the function annotations. In Python 2, we could just write a comment underneath the function declaration and have the type annotation there. He also introduced some ideas for special type constructs. So these are building blocks that we actually need to do typing that aren't already available in Python. So these are things like any. Any is a type that matches literally anything. Union would be this thing could be a type of one type or another type. Optional means that it's an alias for union of some type and none.

16:34

Speaker 1: A tuple would be a tuple type with specific types inside of it. A callable is similar. So this means that we could define a function like this, where Fromnikate takes an integer and integer, but the last argument could be a float or it could be a an integer. And the same thing with for the return type, right? So this would allow us to statically type this function and actually say exactly what those arguments would be. It also defines some container types. So these would be like uh a list or a set, and this allows us to sort of say like, okay, I'm initializing this list, and it can only contain in integers or I'm initializing this dictionary and the key is a string and the value is a is an integer. And so that would cause the type checker to either pass or fail in the various situations. It also defines some generic types for when a class or function behaves in a generic manner.

17:23

Speaker 1: So that would be like an iterable, right? If I just want to say like this function takes Anything that I can iterate on, like a string or a list, or whatever, uh, I can just call it an iterable that's generic across all types It also defines the relationship between types, subtypes, and classes. So like I said before, a type is basically just a class. So like when I did before, we just get a class out of it. That means that I can define a class as well, and that just becomes a type. So when I instantiali instantiate foo here, I just get the the type which is the the class I used to instantiate it. Um so for int, like int is both a class and uh a type here. I can create this another class like user ID. This is still a custom class, but also still a type. And then it's important to note that things like those special type constructs are

18:12

Speaker 1: not also classes that we can stand. You can't initialize a the union of a string and integer. It doesn't really make a lot of sense. Uh and we can define type aliases. So this is nice if we want to just uh like pretend like we're JavaScript and have a number type that just is every single number available to us. And then after he did PEP 483, he did PEP 484. So this is type hints and this basically standardizes everything in PEP 483. And it basically standardizes on what my PI's behavior. And at this point, MyPy is kind of like uh just the type checker. And so PEP484 is how to build a type checker for Python. And this was all released in Python 3. 5. in 2015. So it had PEP 484 support, it had a typing module that we could import from, get those special type constructs.

18:58

Speaker 1: And then we kept going. So in PEP 526 we got um syntax for variable variable annotations. So using the comments in Python 3 felt kind of janky and it also had some problems. So like I said before, you could initialize an empty list and set the type on that list. It was kind of messy when we tried to instantiate something that we didn't didn't want to set to a value yet. We kind of had to give it an initial value if we wanted to type it. And so the variable annotations let us do things like this. We put the annotation in line. If it didn't have an initialization value, we could just set the annotation and just not initialize the variable at that point. And the same is true for like class variables. We had like the ability to instantiate class variables and things. That was available in Python 3. 6. And this almost had everything we needed to do static typing in Python, uh except we needed a type checker.

19:45

Speaker 1: So um this is the last piece of the puzzle, and at this point we kind of also we have MyPy, but there are also uh other static type checkers available So there are two types, uh static versus dynamic. So static type checker is going to run while your code is at rest. It's going to actually run across your static code base and infer all the types from just from your code. A dynamic type checker is going to sit with your code at runtime and infer types, kind of like those asserts that we were doing. before. So most of the type checkers I'm going to talk about are static. So by this point, MyPy had become like it's no longer a variant. It's a tool that's a type checker that's really East on PyPI. And you could run it like this. You could pip install MyPy. I could define some file that has some bad typing in it.

20:31

Speaker 1: And I could run MyPy on that file, and it will say, okay. You've said that these three arguments are integers, but you're calling them with strings. This is a type error, and I'm gonna give you a warning. Um so there's lots of static type checkers, like I said. Um they all support an image implement PET 484. So there's MyPy that's mostly supported by Dropbox at this point. PyType is from Google. Pyre is from Facebook. right from Microsoft. PyCharm actually has its own built-in SAD type checker, which is kind of cool, but also like you can integrate your editor, whatever your editor is, with all of these other type checkers. And then there's a bunch of dynamic ones, but again, they have some overhead at runtime, so uh they're not you know generally preferred, I think. So disclaimer, I work at Google, so I will talk about PyType. just a little bit too. It's also available on PyPI. And the inevitable question between

21:17

Speaker 1: when you're looking at all these type checkers is like, what is the difference between them? And since they all implement this this PEP, this standard, like what what's the actual difference here? So the difference between MyPy and PyType, uh, there's basically two things I'm gonna point out. Um one is what I'll call cross-function inference, and the other is runtime lenience. So they basically have two different uh philosophies about when they should raise errors So uh cross-function inference means that if I define a function and I one function returns a string and the other tries to take the output from that function and do something that would be a type error at runtime. Uh if I run this example, it's going to create raise a type error. If I run it with MyPy, nothing happens because MyPy doesn't actually have the ability to infer across functions.

22:02

Speaker 1: uh that I that one function is going to return a string and the other function is trying to use that as a string. If I run it with pyType though, it will raise an error. It will say this is going to produce an error when When you try to run this function, you probably want to fix it. Uh the kind of the opposite is that my MyPy is gonna be really strict when you define its types, uh, but PyType is gonna say if this doesn't actually produce a runtime error, I'm not gonna raise a problem. So here uh I have a function that's supposed to return a list of strings, but in in the function I'm appending an integer to it and I'm returning that. MyPy is gonna look at this and say, uh this list is supposed to only contain strings and you're putting an int in it. So when I run this, like it works totally fine. MyPy or PyType is gonna say, yeah, no, no problem, that's not gonna create a runtime error.

22:49

Speaker 1: But my pie is going to raise an error and say, you tried to do something to this list, and I thought that it was going to have a different type. Alright. So why? Why and when should we use static? typing. So first I'll say when you shouldn't use static typing. Basically never. There's not really a point in time when static typing is going to hurt you. Again, it's like optional and it doesn't affect the runtime of your program. The one time that maybe you shouldn't choose to use static typing is to replace your unit tests. So there is sort of a school of thought here that that sometimes you write unit tests and they kind of just look like you're doing type checking right now. You're making assertions on the way you're calling the function. Um and and then that's kind of true because unit tests are some ways kind of like a bad type system. But in reality, like

23:34

Speaker 1: don't replace your unit tests with static typing. You probably should just do both here. So when should you use static typing? Basically use it liberally. Use it as much as possible. It's good for you. So you should use static typing when you're millions lines of scale. So I'm not uh sure anyone's code base is currently millions lines of scale. right now here. But basically, you know, Dropbox found that when you're at that scale, that Python, it's actually a liability that Python is not stacked. The the overhead to understand that much Python code that doesn't have type annotations is so high that it actually uh affects productivity. So that's why companies like Google and Facebook And Microsoft and Dropbox have all invested in these type checkers and have started statically typing their code because it actually is worth doing for them because they have millions of lines of code.

24:22

Speaker 1: So basically like there's this graph that you can imagine where as your lines of code go up, your desire to add type annotations increases, but your ability to add the ease of adding annotations it goes down. Uh so you know you're all probably here maybe. Uh you should migrate and add stag typing probably about here in the graph. That's when it's easiest. This is when you're probably gonna do it. Uh but that's okay. You'll still be able to do it anyway. Um you should also use static typing just like when your code is confusing. So uh let's be honest, everyone has written some confusing code at this point. point. It has been said that annotations are basically like machine verified documentation, right? It's something that your developers can look at and understand what types are available to them. In their function, uh, but also

25:09

Speaker 1: you can run a type checker on it and verify that that's actually the way it's being used. So if you feel like you need to document the input and output types of your function or your class or whatever, that is a good indicator. That you should probably just add some static typing. You should use static typing when your code is for public consumption. So, like if it's a module you're gonna put on PyPI. Um, adding type annotations help developers know how to use your API, uh, also helps your ID figure out how to uh do auto completion and things like that. And then also if your users are doing static typing in their code and they want to use your library, they're gonna love it because the static typing is going to be available to them as well. For your code. Uh you should use static typing before migrating or refactoring. So uh

25:54

Speaker 1: you know add some static typing in a place where you're about to do some refactoring. factoring, and then go make your changes and then run your type checker. Make sure that you're still calling everything in a way that's consistent with the types that you had set up before you did that migration. You can do static typing just to experiment with static typing too, right? Like I said, it's it doesn't hurt. Um you can try it out now if you have a modern version of Python. Uh yeah, uh it's it's totally valid to just experiment with it. And finally, you can use static typing if you're just cold. Gary Bernhardt says static types are a warm blanket that I have missed so much. And by the way, Gary's um background on Twitter is like the best. thing right now. Here lies Gary stung by type errors. Alright, so to wrap up, uh how to use static typing in Python

26:41

Speaker 1: in just five. easy steps. So first, you you could, this is optional, but you could migrate to Python, uh modern version of Python. You can do type annotations, uh, type comments in any Python version. You can still do in Python 2. But in Python 3. 6 or above, you'll get uh variable annotations, function annotations, and everything else as well. And you should probably be migrating anyway, let's be honest. honest. You can install type checker locally. I don't care which one. You can install multiple ones if you're interested in trying out multiple ones. Yeah, install locally and integrate it into your editor, right? You can actually make make it so that the type checker will run whenever you open a Python file in your code and just check out that file. And if you as soon as you start adding type annotations, it will start telling you if you're using them correctly or not. And then you can start optionally typing your code base, right?

27:26

Speaker 1: Start with maybe like your most confusing and hairiest files. You can start with the easiest things to type check as well. Remember you don't have to do it all at once. You can just start in some place, do a gradual, um, pick the critical areas and start there. And then uh once you get to a point where you have some uh Some type checking in your code. You can run type checker when you lint your program and finally convince all your coworkers to join you in static typing. If you need help convincing them to join you, you can show them this talk on YouTube. So thanks everyone.

28:00

Speaker 2: Thank you, Dustin, for your talk. You're an excellent speaker. I wish I could travel the country to listen to you talk. Um I accidentally found static typing the other day. I imported well, I found out that I had to import uh the class in order to statically type a function and then I got a recursive import error and I realized that when I start to import all of the types across all of the application To statically type all of the functions, I was probably going to get a lot of I think they're called recursive import errors. I was wondering if you have any thoughts on on that sort of import. It recurs in mess.

28:41

Speaker 1: Uh so the issue is that like you have some custom types, some classes and stuff defined. You have it in your code, and you need to import it to define the types in in uh some function, but it hasn't actually like you're doing a record. recursive import. So the one thing I will say about when you start to add static typing to uh your application, um it does sort of indicate to you which parts of your code base are not very well written. Not saying that you've written bad code, but it will like when you you sit down to annotate a function and you're like, man, this is really hard to add the typing. like uh I don't I don't I'm confused and like myPi is really complaining about I can't get it right. It probably means that that function is not well written. Like the arguments are ambiguous or it's receiving more than it should or not. Specific to uh circular imports, so what you can do in MyPy

29:27

Speaker 1: and in all the type checkers, you can use strings and stuff. the actual classes themselves. So yeah, if you have circular circular imports like that, you don't actually need to import them to define the type for them. You can use string instead. And then it also sort of depends, a lot of it depends on the way your code base is structured, but yeah. That's a great question.

29:47

Speaker 3: Any more questions? Yes, um if you can make it a really brief one because we want to go for lunch and then you guys can always talk to him in the hallway

29:58

Speaker 4: Cool. Yes, thank you very much just uh Dustin for your great talk. Um I'm a big fan of um the static typing in Python. Um how do you feel about um Django and Personally when I've used the typings, I I've used a plugin called MyPy Django to bring in the types. I'd like to see the types in the Django code base. Would you agree? And and is there an initiative to get to get that move in?

30:28

Speaker 1: So I can't I I I'm not like really I can't say this too loudly, but I'm not actually really a Django developer. I'm Python developer. I do know a little bit about what static typing looks like in Django. So I think there is some ideas maybe about adding But the nice thing that I didn't mention is that um uh so Python, uh the the folks that work on MyPi also ship this project called TypeShed, and that's type annotations for everything across the standard library. Basically you can add type annotations external to any third-party package or library. So there is a project, I am forgetting what it's called, but it's basically like Django type annotation Third-party project that has type annotations for a significant portion of the Django code base for a specific version of Django. So

31:14

Speaker 1: that is something that you could use to get these type annotations. for Django. Um from what I have heard, there's some magic happening inside of Django that causes it to be difficult to do uh type annotations, basically the way that models and things are defined. So my understanding, and this is like a very high-level understanding, is that it is sometimes challenging to add uh type annotations to some parts of your Django application. But generally, I mean you can use it uh in non-jjango-y parts of it as well. Like it it you don't have to exactly type everything in your code base uh in order to use to get the benefits.

31:47

Speaker 4: Cool. Thank you very much.

31:48

Speaker 1: Thank you. Thanks for the question.

31:50

Speaker 3: All right, let's give another round of applause to Mr. Dustin.

Questions this talk answers

Is Python statically typed or dynamically typed?

Python is dynamically typed by default, but it can optionally use static type annotations and checking. Those annotations can be adopted gradually without changing the program’s runtime behavior.

Discussed at 1:01

What are Python type annotations, and do they affect runtime?

Function and variable annotations describe the types that code is expected to use, but Python itself does not enforce them during normal execution. A separate type checker can inspect the annotations and report likely type errors.

Discussed at 10:19

What tools can I use to check Python type annotations?

Options include MyPy, Pytype, Pyre, Pyright, and type checking built into PyCharm; these tools support the Python typing standards and can also be integrated with editors. Dynamic checkers exist too, but they add runtime overhead.

Discussed at 19:45

What is the difference between MyPy and Pytype?

MyPy is stricter about declared types, while Pytype performs more cross-function inference and tends to accept code as long as it does not appear to cause a runtime error. As a result, the two tools can flag different problems.

Discussed at 21:17

When should I use static typing in Python?

It is especially useful for large codebases, confusing code, public libraries, and code that is about to be refactored. The speaker recommends using it liberally, while keeping unit tests rather than replacing them with type checking.

Discussed at 22:49

How do I gradually add static typing to a Python project?

Use a modern Python version if possible, install and integrate a type checker, annotate selected parts of the codebase, and run the checker alongside linting. You can begin with the most confusing or critical files and expand incrementally.

Discussed at 26:41

How can I handle circular imports when adding Python type annotations?

Type checkers such as MyPy can use forward references—type names written as strings—so the referenced classes do not always need to be imported at runtime. Difficult-to-annotate code can also indicate that the code’s structure or function boundaries need improvement.

Discussed at 28:41

How can I add type annotations to Django code?

Third-party annotation packages provide types for substantial portions of Django, including version-specific support, so you do not necessarily need to annotate the whole framework yourself. Some Django internals, especially model-related magic, are difficult to type completely, but application code can still benefit from gradual typing.

Discussed at 30:28

Presenters

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos by Dustin Ingram

More videos from DjangoCon US