Techno Blender
Digitally Yours.

12 Beginner Concepts About Type Hints To Improve Your Python Code | by Ahmed Besbes | Oct, 2022

0 56


Just like unit tests, type hints take developer time but pay off in the long run

Photo by Arian Darvishi on Unsplash

Python is a dynamically-typed programming language. This means that the interpreter performs type checking only when the code runs and the variable type is allowed to change over its lifetime.

Although Python has always remained dynamically typed and was never intended to change this direction, type hints have been introduced since PEP 484 with the goal of bringing static type checking to the code analysis.

In this article, we’ll introduce type hints and go over 12 concepts that you should be familiar with when using them.

At the end of this post, you should have a global overview of type hints, what they are and when you should use them. You’ll also learn how to build complex types and perform static type checking.

With that being said, let’s dive in and look at some code 💻

Type hints are performed using Python annotations (introduced since PEP 3107). They are used to add types to variables, parameters, function arguments as well as their return values, class attributes, and methods.

Adding type hints has no runtime effect: these are only hints and are not enforced on their own. If you worry about them, do remember that the interpreter always ignores them.

👉 Variable annotations
Variable annotations follow this syntax:

<variable_name>: <variable_type> = <variable_value>

You can perform type hints with one of the following basic built-in types: int, float, str, bool, bytes, list, tuple, dict, set, frozenset, None .

Here are some examples to get started:

To build more advanced types, we’ll use the typing module. Keep reading to learn more.

👉 Function annotations
You can also add type hints to functions to specify the type of their arguments and their returned value.

Specifying the types of arguments is done using the previous syntax.

Type hinting the returned value is done using an arrow -> .

In the following example, type_x , type_y , type_z , type_return must be valid python expressions such as built-in types or more complex ones using the typing module.

→ If a function doesn’t return anything, you can set the return type (after the arrow) to None .

👉 Class annotations
You can even annotate attributes and methods inside your classes.

To check that python doesn’t enforce type hints, try running the following script that declares a variable x with an int type and later changes its value to store a float.

# script.pyx: int = 2# ...x = 3.5
Screenshot by the author

No errors. (as expected)

Now if you’re really rigorous about your code and still want to detect these types of errors, you can use mypy, a tool to perform static type checking.

To install it, simply run pip install mypy .

Then, to analyze your code, simply run: mypy script.py

Screenshot by the author

In this simple example, mypy detects an error: Incompatible types in an assignment, which is exactly what we want to highlight.

By statically analyzing your code, mypy helps you conform to the types you defined, improve your code quality, prevent silent errors and standardize your code by following best practices.

To learn more, check http://mypy-lang.org/.

Why bother writing type hints if they’re not enforced by the interpreter?

I hear that a lot.

It’s true that type hints don’t change the way your code is run: Python is dynamically typed by nature and is not intended to change any time soon.

However, from a developer experience standpoint, type hints bring multiple benefits to your codebase.

Off the top of my head, I can think of the following advantages:

  1. Using type hints, especially in functions, document your code by providing an informative signature that clarifies the assumptions about the types of arguments and the type of the result produced
  2. Informing the types removes a cognitive overhead and makes the code easier to read and debug. With the types of inputs and outputs in mind, you can easily reason about your objects and how they can be coupled
  3. Type hints improve your code editing experience:
    Coupled with mypy, your IDE relies on them to statically analyze your code and help detect potential errors (e.g. passing an argument of the wrong type, calling a wrong method, etc.)
    Plus, auto-completion is provided for each variable depending on its type hint.
mypy detects supported type operations and incompatible types based on type hints — Screenshot by the author
VSCode uses the type hint information to provide relevant autocomplete — Screenshot by the author

In the next sections, we’ll cover advanced types from the typing module.

Let’s say you want to pass your function a slightly complex argument consisting of … a list of floats. How would you annotate it?

You can try writing something like this by composing the list type with float type.

Nice try.

Sadly, this doesn’t work although it seems natural and intuitive to write.

To support this, you simply need to replace the built-in standard list type with the List type that you import from the typing module.

The interesting thing about the List type is that it can hold any type inside it, not the built-in ones only. Our own types can basically be combined with List.

So if you have, for example, a list of lists of strings, here’s how you’d type it:

I like using python dictionaries because they are flexible. They allow you to use keys and values of different types. For example, you can have a dictionary with string keys and dict values (which, in this case, indicate a nested dict structure)

However, as a price for this flexibility, it’s not always clear what the underlying schema is: what type are the dictionary keys? what types are the dictionary values?

To control the types of keys and values inside your dictionaries, you can use the Dict type from the typing module.

Let’s say that I deal with dictionaries with string keys and string values.

{"name": "Ahmed", "job": "ML engineer"}

To model such a dictionary structure, you can pass two arguments to Dict where the first one is the type of the keys and the second one is the type of the values.

from typing import Dictmy_dict_type = Dict[str, str]

Starting from Python 3.10, Union is replaced by | which means that Union[X, Y] is now equivalent to X | Y .

Union[X, Y] (or X | Y) means either X or Y.

Example 👇

Let’s say your function needs to read a file from a cache directory and load a torch model. This cache directory location can either be a string value (such as /home/cache ) or a Path object from the Pathlib library.

In that case, the code would be as follows:

What if your Python dictionary has a fixed schema with known string keys and values of different types?

Something like this:

d = {"name": "Ahmed", "interests": ["chess", "tennis"]}

How would you type it?

❌ Using Dict with Union is not ideal since we lose information about what keys need to have values of which types.

✅ Using TypedDict allows you to declare a dictionary type that expects all of its instances to have a certain set of keys, where each key is associated with a value of a consistent type.

Here’s an example of how this works 👇

Image by the author — made on https://carbon.now.sh/

Callable is what you use when you want to use a function as an argument.

You can also specify the type of the parameters and the return value of the callable function this syntax.

Callable[[input_type_1, ...], return_type]

Here’s the same example with the additional type hints:

The Any type is pretty straightforward. In fact, writing this

def bar(input: Any):
...

is equivalent to writing this:

def bar(input):
...

The Any type is just here to explicitly state that the bar function is intended to receive any input type as an argument. (probably because what this function performs later doesn’t depend on the type of its argument).

Is this Any type even useful? 🤔

Honestly, I don’t know.

If any of your functions use an argument that’s optional because, say, it has a default value, then you can use the Optional type from the typing module.

An object of type Sequence is anything that can be indexed: a list, a tuple, a string, a list of objects, a tuple of lists of tuples, etc.

⚠️ Please note that sets or dictionaries are not Sequence types.

The Tuple type works a little bit differently than the List type.

If you don’t care about the types of each element of your tuple, you can continue using the tuple built-in type.

t: tuple = (1, 2, 3, ["cat", "dog"], {"name": "John"})

If you do however want to specify the type of every element in the type, use Tuple.

Type hints bring an additional layer of abstraction on top of your code: they help to document it, clarify the assumptions about inputs/outputs and prevent insidious and silent mistakes when static code analysis (👋 mypy) is performed on top.

Now the question that remains is: should I start using type hints in my projects?

If you’re collaborating on a large codebase where type hints (and unit tests) are already implemented, then it’s only natural to add
If you want to write modern python code and increase your code quality, type hints are the way to go.

However, you probably should avoid adding type hints if you use an old version of Python, if you’re new to the programming language or if performance is a real issue. In fact, regarding this last point, type hints introduce a slight overhead in start-up time.


Just like unit tests, type hints take developer time but pay off in the long run

Photo by Arian Darvishi on Unsplash

Python is a dynamically-typed programming language. This means that the interpreter performs type checking only when the code runs and the variable type is allowed to change over its lifetime.

Although Python has always remained dynamically typed and was never intended to change this direction, type hints have been introduced since PEP 484 with the goal of bringing static type checking to the code analysis.

In this article, we’ll introduce type hints and go over 12 concepts that you should be familiar with when using them.

At the end of this post, you should have a global overview of type hints, what they are and when you should use them. You’ll also learn how to build complex types and perform static type checking.

With that being said, let’s dive in and look at some code 💻

Type hints are performed using Python annotations (introduced since PEP 3107). They are used to add types to variables, parameters, function arguments as well as their return values, class attributes, and methods.

Adding type hints has no runtime effect: these are only hints and are not enforced on their own. If you worry about them, do remember that the interpreter always ignores them.

👉 Variable annotations
Variable annotations follow this syntax:

<variable_name>: <variable_type> = <variable_value>

You can perform type hints with one of the following basic built-in types: int, float, str, bool, bytes, list, tuple, dict, set, frozenset, None .

Here are some examples to get started:

To build more advanced types, we’ll use the typing module. Keep reading to learn more.

👉 Function annotations
You can also add type hints to functions to specify the type of their arguments and their returned value.

Specifying the types of arguments is done using the previous syntax.

Type hinting the returned value is done using an arrow -> .

In the following example, type_x , type_y , type_z , type_return must be valid python expressions such as built-in types or more complex ones using the typing module.

→ If a function doesn’t return anything, you can set the return type (after the arrow) to None .

👉 Class annotations
You can even annotate attributes and methods inside your classes.

To check that python doesn’t enforce type hints, try running the following script that declares a variable x with an int type and later changes its value to store a float.

# script.pyx: int = 2# ...x = 3.5
Screenshot by the author

No errors. (as expected)

Now if you’re really rigorous about your code and still want to detect these types of errors, you can use mypy, a tool to perform static type checking.

To install it, simply run pip install mypy .

Then, to analyze your code, simply run: mypy script.py

Screenshot by the author

In this simple example, mypy detects an error: Incompatible types in an assignment, which is exactly what we want to highlight.

By statically analyzing your code, mypy helps you conform to the types you defined, improve your code quality, prevent silent errors and standardize your code by following best practices.

To learn more, check http://mypy-lang.org/.

Why bother writing type hints if they’re not enforced by the interpreter?

I hear that a lot.

It’s true that type hints don’t change the way your code is run: Python is dynamically typed by nature and is not intended to change any time soon.

However, from a developer experience standpoint, type hints bring multiple benefits to your codebase.

Off the top of my head, I can think of the following advantages:

  1. Using type hints, especially in functions, document your code by providing an informative signature that clarifies the assumptions about the types of arguments and the type of the result produced
  2. Informing the types removes a cognitive overhead and makes the code easier to read and debug. With the types of inputs and outputs in mind, you can easily reason about your objects and how they can be coupled
  3. Type hints improve your code editing experience:
    Coupled with mypy, your IDE relies on them to statically analyze your code and help detect potential errors (e.g. passing an argument of the wrong type, calling a wrong method, etc.)
    Plus, auto-completion is provided for each variable depending on its type hint.
mypy detects supported type operations and incompatible types based on type hints — Screenshot by the author
VSCode uses the type hint information to provide relevant autocomplete — Screenshot by the author

In the next sections, we’ll cover advanced types from the typing module.

Let’s say you want to pass your function a slightly complex argument consisting of … a list of floats. How would you annotate it?

You can try writing something like this by composing the list type with float type.

Nice try.

Sadly, this doesn’t work although it seems natural and intuitive to write.

To support this, you simply need to replace the built-in standard list type with the List type that you import from the typing module.

The interesting thing about the List type is that it can hold any type inside it, not the built-in ones only. Our own types can basically be combined with List.

So if you have, for example, a list of lists of strings, here’s how you’d type it:

I like using python dictionaries because they are flexible. They allow you to use keys and values of different types. For example, you can have a dictionary with string keys and dict values (which, in this case, indicate a nested dict structure)

However, as a price for this flexibility, it’s not always clear what the underlying schema is: what type are the dictionary keys? what types are the dictionary values?

To control the types of keys and values inside your dictionaries, you can use the Dict type from the typing module.

Let’s say that I deal with dictionaries with string keys and string values.

{"name": "Ahmed", "job": "ML engineer"}

To model such a dictionary structure, you can pass two arguments to Dict where the first one is the type of the keys and the second one is the type of the values.

from typing import Dictmy_dict_type = Dict[str, str]

Starting from Python 3.10, Union is replaced by | which means that Union[X, Y] is now equivalent to X | Y .

Union[X, Y] (or X | Y) means either X or Y.

Example 👇

Let’s say your function needs to read a file from a cache directory and load a torch model. This cache directory location can either be a string value (such as /home/cache ) or a Path object from the Pathlib library.

In that case, the code would be as follows:

What if your Python dictionary has a fixed schema with known string keys and values of different types?

Something like this:

d = {"name": "Ahmed", "interests": ["chess", "tennis"]}

How would you type it?

❌ Using Dict with Union is not ideal since we lose information about what keys need to have values of which types.

✅ Using TypedDict allows you to declare a dictionary type that expects all of its instances to have a certain set of keys, where each key is associated with a value of a consistent type.

Here’s an example of how this works 👇

Image by the author — made on https://carbon.now.sh/

Callable is what you use when you want to use a function as an argument.

You can also specify the type of the parameters and the return value of the callable function this syntax.

Callable[[input_type_1, ...], return_type]

Here’s the same example with the additional type hints:

The Any type is pretty straightforward. In fact, writing this

def bar(input: Any):
...

is equivalent to writing this:

def bar(input):
...

The Any type is just here to explicitly state that the bar function is intended to receive any input type as an argument. (probably because what this function performs later doesn’t depend on the type of its argument).

Is this Any type even useful? 🤔

Honestly, I don’t know.

If any of your functions use an argument that’s optional because, say, it has a default value, then you can use the Optional type from the typing module.

An object of type Sequence is anything that can be indexed: a list, a tuple, a string, a list of objects, a tuple of lists of tuples, etc.

⚠️ Please note that sets or dictionaries are not Sequence types.

The Tuple type works a little bit differently than the List type.

If you don’t care about the types of each element of your tuple, you can continue using the tuple built-in type.

t: tuple = (1, 2, 3, ["cat", "dog"], {"name": "John"})

If you do however want to specify the type of every element in the type, use Tuple.

Type hints bring an additional layer of abstraction on top of your code: they help to document it, clarify the assumptions about inputs/outputs and prevent insidious and silent mistakes when static code analysis (👋 mypy) is performed on top.

Now the question that remains is: should I start using type hints in my projects?

If you’re collaborating on a large codebase where type hints (and unit tests) are already implemented, then it’s only natural to add
If you want to write modern python code and increase your code quality, type hints are the way to go.

However, you probably should avoid adding type hints if you use an old version of Python, if you’re new to the programming language or if performance is a real issue. In fact, regarding this last point, type hints introduce a slight overhead in start-up time.

FOLLOW US ON GOOGLE NEWS

Read original article here

Denial of responsibility! Techno Blender is an automatic aggregator of the all world’s media. In each content, the hyperlink to the primary source is specified. All trademarks belong to their rightful owners, all materials to their authors. If you are the owner of the content and do not want us to publish your materials, please contact us by email – [email protected]. The content will be deleted within 24 hours.
Leave a comment