What is Typing In Python
In Python, 'typing' refers to type annotations that specify the type of data that our code can handle. For example, whether a variable is an integer, a string, or perhaps a more complex data structure, like dictionary.
Typing is crucial because it clarifies what each part of your code is supposed to do, not just to you, but also to colleague who might work on your codebase.And It also acts as a form of documentation that's integrated directly into your code. A well-typed codebase is easier to debug and refactor. It's safer to change because you understand exactly what type of data each part of your code expects and produces.
In the provided function greeting, the parameter name is annotated with the type str, indicating that it is expected to be a string. The function performs a concatenation operation using the + operator between the literal string "Hello " and the parameter name. Since both operands are strings, Python supports this operation by returning another string as the result. The static type checker, such as mypy, uses this type annotation to verify that the types being used in operations like concatenation are compatible, and it understands that the output of the function is also a string, as indicated in the popup window.
A bit of historical context—typing was officially introduced in Python with version 3.5, through something known as PEP 484 in 2014
Use case of Typing (1)
Here we introduce two common and simple use cases of typing.
In this slide, we see how type annotations help prevent errors from the perspective of someone calling a function. When the function greeting is correctly called with a string "Andy", it matches the expected type. However, if a developer tries to pass an integer with greeting like 123 as the last image shown, the static type checker, such as the one integrated in VSCode, immediately flags this as an error. This alert helps developers catch type mismatches before the code is even run, ensuring that only appropriate types are used as arguments.Such early detection helps developers catch errors in the earliest phase of development, enhancing overall code quality and reducing debugging time.
Use case of Typing (2)
In the second example, we see how type annotations help prevent errors from fucntion creator perspective. In the first image, my_clever_function() explicitly specifiies its return type as string. But the return data is actually inferred as a boolean type. This mismatch between the annotated return type and the actual return type triggers a type error. This tell the developer that the function's implementation does not align with its annotated return type, indicating a potential bug.
In the second image, instead of annotate the return type explicitly, we can just let the static type checker to infer the return type for us, and then we can manually check if the inferred type meets our expectation.
Static type checker: MyPy and Pyright
In preivous slides, we told about the concept of "static type checker". This tool analyze the type annotation provided by developer, and try to find out the types of all variables as accurately as possible without running the code.
There are many static type checkers available in the python community, In this slide we will focus on two of the most popular ones, MyPy and Pyright.
Mypy is known for its strictness and tend to be more accurate in some rare case. And Pyright is developed by microsoft, and it is designed with performance as priority, making it suitable for large codebase.
Let compare the difference between these two static type checkers. In the case of speed, pyright is 3 to 5 times faster that mypy in typical use cases. In the case of strictness, Mypy tend to be stricter, which can help enforce a higher level of type safety. In the case of flexibility, Pyright offers advanced type inference capabilities and tends to be more forgiving with unannotated code, easing the developer's burden without sacrificing type safety.
Why choose Pyright over MyPy
In my personal experience, I prefer to use Pyright over MyPy. They both offer the same level of strictness, although MyPy is more accurate in some rare cases. However, it is extremely slow on large codebases. In my experience with a tax-risk-analysis project, we were using containers, so we had Docker Desktop running which put my PC under significant load. Each time I made a change to the code, it often took up to 10 seconds for MyPy to perform the check, whereas Pyright could always perform the check in real time. This kind of real-time type checking is important for developers to have a consistent development pace and not to be interrupted by lengthy MyPy checks.
Introduction to Stub Files(1/2)
In this part of our discussion, we'll explore what stub files are and how they contribute to Python typing. Stub files, with the .pyi extension, contain type annotations but no function bodies. This means they provide the necessary type information without including any actual code. These files are particularly valuable when dealing with external libraries or dynamically typed sections of code.
## Introduction to Stub Files(2/2)
Let's examine what happens when you don't have stub files and when you do. In the first scenario, without stub files for an untyped library like the msal library shown here, tools like Pyright do their best to infer types. However, as you can see in the example, without explicit type annotations, Pyright struggles to accurately determine the types and defaults to Unknown, which isn't very helpful for ensuring type safety.
In contrast, when stub files are available, Pyright uses the annotated types in these files, leading to much clearer and more accurate type inference. As shown in the second example, with a stub file, each parameter and the return type in the PublicClientApplication class are well-defined, which helps prevent many common errors in development by providing precise type checks.
Overloading Functions with Typing
Another feature brought with typing is the overload decorator. In Python, the return type of a function could be changed according to the type or the value of the passed arguments. With the overload decorator, we could notify the function caller of the return type given a set of arguments. In this example, the function will return a string if 'arg' is True, and return an integer if 'arg' is False. WIth overloading, function caller would know the return type for a given set of argument, making our function more predictable for user.