 My name is Laura Hampton. I am here from New York City. Very happy to be back at my third North Bay Python. I work at Datadog in New York City, and I'm here today to speak to you about typing. Not this kind of typing. I'm going to talk about the types of values, not fingers on a keyboard. That would be a very cool talk, maybe propose it for next year, but today I'm going to talk about the types of values. So I'm going to describe the problem domain, the type checking addresses, describe what type annotations and type checking are. I'm also going to share some information about types and type theory, and then finally I'm going to discuss handling some edge cases in Python using type annotations. All of the code examples in this talk are written in Python 3.7 and mypy 0.730. So if you want to try them out, that's what you can use. And also as a warning, I have included some illustrations of snakes, so if you are not a fan of snakes, please be warned. So there is a debate in the programming community about the relative merits of static typing and dynamic typing. If there are two equally good solutions to any problem, programmers will invariably start a holy war about it. Thank you. No. Stop. You are misbehaving. Stop that. Okay. Programmers will invariably start a holy war about it. Please don't do that here. If you do, you must dress like them. So what are types? Most programming languages have a type system, and types are an abstraction for a set of possible values, such as integers and floats and strings, or they describe sets of objects with shared behavior, like lists or booleans or tuples, and all of these are Python sort of basic built-in types. These sets of values allow you to have functions that operate on these types, and allow you to do things like, for instance, concatenate strings, add numbers, or iterate through a list. You can also create your own types and define their behavior by defining classes, and all instances of a class belong to that class type. So Python is what we call strongly typed language. It doesn't allow dividing an integer by a string, for instance, because that doesn't make much sense. There are other languages that will try and change types to avoid these types of errors, but Python will just yell at you. Python's strong typing does give us some guarantees about the correctness of types in our application, but by adding type checking, we can sort of do a belt and suspenders type thing and add even more safety around that. So the purpose of type checking gives us code that's easier to reason about. So it allows you to know what parameters to pass to a function and what functions can operate on which classes. It shows clearly how classes travel through the program and acts as a form of machine check documentation. So if you use a type checker regularly, this machine check documentation will stay up to date with any code changes you may make. Type checking also allows you to refactor things more easily. It's easy to see where a given class is used in the code base, and it also allows your application to fail in more predictable ways. It's useful as a debugging tool. It allows you to know the value and type of variables, and it's important for giving you better reasoning about program behavior. I do have to warn you now it is mathematically impossible to build a type checker that will catch all type errors. However, type checkers in general tend to fail on the side of ... dare on the side of being conservative, so hopefully they'll catch everything that might pop up. In addition, type checking doesn't affect the run time in Python. Type annotations are essentially comments, and type checking is done as a separate step for your application runs. Python has a number of tools for type annotation. There's the typing module in the standard library, which provides a lot of useful things that help your type checker work. It contains objects to import for type annotations, like list and any, and I'll show you later on in the talk how that works. There's also typing extensions library, which you can download from PyPI. It contains types for users of Python 3.5 and 3.6, who may not have the latest and coolest ones. It also contains neat things like a protocol and typed dict. With these tools, you can use PyPI, to check these type annotations. These applications check type annotations and make sure they're correct. In this talk, I'm going to speak about PyPI partly because it's the one that I'm the most familiar with, also because it exists under the umbrella of the PSF. It was initially developed in 2012 as an extension of his work on allure, which is a language with optional type checking. This is what type annotations in Python look like. Once again, this is in Python 3.7. Here I have an example of an annotated function and an annotated class, and it shows which the types that the function takes in and the types that it returns. The types that the methods on the class return. Using PyPI is extremely easy. All you have to do is pip install it and then run it against your program file and aim for the success message. Python has a type function, and when given a single parameter, type can tell us what the type of a variable is. However, it has a second role, and it is also the class constructor. So passing it a bunch of, passing it three parameters turns it into the class constructor. In this case, both of these mammal classes are equivalent. In the type constructor, we have the name passed as a string, an empty tuple which says that the class doesn't inherit from anything, and when people joke that Python is dictionaries all the way down, this is part of what they're referring to. It's also possible to create new types through inheritance. So here, once again, I have the mammal class, except that it has a new method on it now to allow our mammals to breathe. And type, this is going to be our parent class for my next couple of examples. And types defined through inheritance are automatically subtypes of the parent class. Subtypes are more specific versions of their parent type, and so this is going to be my parent type. So I can take a mammal and I can make it cat. It has all the attributes of the parent class plus purring and running methods. I can also make a whale. The whale is also a subtype of the mammal class, but it has singing and swimming methods which are different from the methods on a cat. Every whale is a mammal, but not every mammal is a whale, so there are fewer whales than there are mammals. And the whale can do everything that mammal can, so it has access to all of the methods on the parent class, but mammal cannot necessarily do everything a whale can because not all mammals can swim, for instance. And now, in addition, there's what's called the Lyskov substitution principle or behavioral subtyping, which introduces principles to determine if one type is a subtype of another. So if one class is a subtype of another, the child class can replace the parent class anywhere in the application without altering the desirable properties of the application, like correctness or tasks performed. And so if I had an application called for a generic mammal, I could substitute a whale and my application would still work. Inheritance also raises issues of type safety. We can assign a variable of type whale to a variable of mammal type since a whale is a mammal, and assigning a mammal to a whale, however, is not safe since all mammals are not whales. Subtype relationships in Python are determined by the methods implemented on the object, and I'll discuss this kind of structural subtyping later. So in Python, we also have what's called duck typing, and it means that in order to be compatible, types have to pass what's called the duck test. So if it walks like a duck and it quacks like a duck, it must be a duck. And this means that type compatibility is determined by looking at the methods on the classes that are accessed at runtime. You're not looking at what the class is inherent from or what their names are. You can also add to built-in types in Python. Built-in types can be used to define more complex ones. So for instance, here I'm creating a list that only accepts whales. If I try and add an integer to this list, my pie is going to yell at me and tell me I can't. Now I mentioned earlier structural typing, and Python uses structural typing. It looks at the shape of the type and compares the methods on it, even if these classes don't necessarily have the same parent class or the same name. So the type of the object is less important than the methods it defines. So in this case, I've defined a narwhal class. Now the narwhal class has no parents. It doesn't inherit from the mammal class, but I've implemented all the same constants and methods on it. So I could take this narwhal class and just plug it into my application wherever whale would be used and my pie would be happy. I've also defined the dunderlen method on this class. And this means that I can use the len function on it. And len doesn't care that it's not like a string or a list, it just cares looking for that specific method. Now there's also a system called nominal typing. And this nominal typing looks at whether classes share a name or a subtype of the same parent class with the same name. The compatibility and equivalence of the two types are determined by explicit declarations of the names of the types. And types are only considered type compatible if they have the same name or inherit from a parent class with the same name. It's also important to consider dynamic and static typing. Oh, behave. So I have here an example in Python and an example in Go. And Go is a statically typed language. And in Python, print doesn't care what I pass to it. I can pass it an integer, I can pass it a string, I can pass it anything with the dunder Reaper method. And it'll print it for me. In Go, I have to specifically say that this number that I want to print is a type of the float64 type. And that's because Go is a statically typed language. You can in Go, you don't necessarily have to do this, but I'm just doing this to be explicit. So in dynamically typed systems, like Python, names are untyped and values are typed. So any name can be bound to any type. Dynamic types are checked at runtime. Variables can be reassigned to values of any type over their lifetime. And fans of dynamic typing say the code is more flexible and you can run the program immediately without having to make the type checker happy. You can use simplified data structures to mock up an application. For prototyping and then expand on them later as needed. And dynamic type systems also allow types to change based on the information at runtime. In a statically typed system like Go, values and names are both typed and a name can only be bound to a value of a compatible type. Fans of static typing say the type system helps them catch type errors earlier in the development cycle and avoids having them surface in production. There's also what's called gradual typing and that's what this system is what the Python type checking aims to implement. It allows you to, it's kind of the you got chocolate in my peanut butter of static and dynamic typing. It allows you to add types when you can and allow the rest of your program to be dynamically typed. And it combines the benefits of both static and dynamic typing system. So this system allows you to add type checking gradually so you can add it just to the most critical parts of your application and then sort of expand and go from there. And I'll show you later on in the talk how the any type in my pie allows you to move between dynamic and type annotated code. So now let's put all this theory into practice with some special cases of type checking in Python. So here we have how it's possible to declare the type before the value. I promised in my talk description that I would show you how to do this. And it is perfectly legal in Python to assign the type to a value before assigning a value to it or assigning a type to a variable before assigning the value to it. So once again in Go we have, we need to declare the type of our variable and in Python before we assign the value to it. And in this case we're also assigning the type to the variable before in Python as well. It's also possible to annotate functions that return none. The type of none in Python is none type, but in the type annotations we just use none. My pie will complain if I also, if I try to assign the none return value from this function to any variable. And this can help catch some types of errors and bugs. It's also possible to use type checking to create new types similar to the way that I showed you on expanding the built-in types earlier. The type checker will treat this new type as if it were a subclass of the parent type. In this case an integer. So you can do things like add to user IDs together, but the result of that will be an integer. So if you're trying to pass in that result of that computation to a function that's expecting a user ID it will throw an error. The any type allows you to sort of move seamlessly between the statically and dynamically typed parts of your application. Any is compatible with all types and all types are compatible with it. All method calls and operations can be performed on the any type and it can be assigned to any variable. And it acts as a gateway between the dynamic and type checked parts of your application. Any functions and variables also that are not annotated default to the any type. You can also use type checking to annotate container types to only accept certain types within them. So for instance if I have, oops, there you go. So instance if I have my list of cat names, if I try and try and put an integer into it, my pie will complain. Next I have optional types. They allow you to annotate functions that return either a typed variable or none. And union types which allow variables to have different types depending on the situation. For instance you can have these dimensions which allow either allow both integers and floats, but not any other type. Finally I want to show you type checking using protocols. Protocols allow kind of like a duck typing type behavior with your type checked code. They're introduced in PEP 544 and they're one or more methods that they define one or more methods that must be present on the type. So for instance all classes implementing the DunderLend method are part of the sized protocols. You can call Lend on them and get their length. And other protocols that you can add include iterable, awaitable, container and context manager. And it's also possible to define custom protocols using the protocols from the typing extensions library. So when should we use all this? In all things we should obey the testing goat. So when should we consider using type checking? Type hints and type checking help document your code. They build and maintain a cleaner architecture in your code. They show how types are used in the code and how they change throughout your application. They're useful if multiple people all be working on the same application. For instance if you have an open source project or a project a lot of people are touching at work, it's probably a good idea to consider adding type annotations. It's also a good idea to add type hints whenever unit tests are worth writing. So if you have an application that you consider worthy of unit testing, then consider adding type annotations as well. If you have cases in your code when you're asserting is instance or type of a variable, then yes definitely add type checking there. However type checking can't do some things. It can't be the only thing that screens user input for security. Drop table students is still a valid string. It also can't predict type errors or make guesses about where they may occur. But it can predict most of them, just not all of them. They also take some developer time and effort to add. So if you have an application that you're only going to run maybe once or twice, then don't worry about it. But if you have something more permanent then they're probably worth adding. Lastly, I just want to say that type annotations in Python are not required. They are a tool that you can use. And Python will remain a dynamically typed language. So type checking is a bonus add-on, but it's not required. So thank you very much. And thank you all for coming.