Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Annotating Callable Objects

00:00 Annotating Callable Objects With Type Hints In some programming languages, including Python, functions can return other functions or take them as arguments.

00:11 These commonly known as higher order functions are a powerful tool in functional programming. To annotate callable objects with type hints, you can use the Callable type from the collections ABC module. Know that this is different from typing.Callable, which was deprecated in Python 3.9.

00:30 A common type of higher order function is one that takes a callback as an argument. Many built-in functions in Python, including sorted(), map() and filter(), accept a callback function and repeatedly apply it to a sequence of events.

00:44 Such higher order functions eliminate the need for writing explicit loops, so they align with a functional programming style. On screen is a custom function that takes a callable as an argument illustrating how you’d annotate it with type hints.

01:03 The first function apply_func() takes a callable object as the first argument, and a string value as the second argument.

01:12 The callable object could be a regular function, a lambda expression, or a custom class with a special __call__ method. Other than that, this function returns a pair of strings.

01:26 The Callable type hint has two parameters defined inside square brackets. The first parameter is a list of arguments that the input function takes.

01:35 In this case, func expects only one argument of type str. The second parameter of the Callable type hint is the return type, which in this case is a tuple of two strings.

01:48 The next function parse_email is an adapted version of the function you saw earlier on that always returns at tuple of strings.

02:04 Then you call apply_func() with a reference to parse_email as the first argument and the string “darren@realpython.com” as the second argument. In turn, apply_func() invokes your supplied function with the given argument and passes the return value back to you.

02:20 Finally apply_func() is called once more with an invalid email address to check the behavior when an invalid email is supplied. Running this code is as straightforward as you’d expect.

02:38 Now, what if you want apply_func() to be able to take different functions with multiple input types as arguments, and have multiple return types?

02:46 In this case, you can modify the parameters inside the Callable type hint to make it more generic. Instead of listing the individual argument types of the input function, you can use an ellipsis to indicate that the callable can take an arbitrary list of arguments.

03:02 You can also use the Any type from the typing module to specify that any return type is acceptable for this callable. Even better, you can use type variables to specify the connection between the return type of the callable and of apply_func.

03:17 Either option would apply type hinting to the return type only for the function in question. Update the previous example as seen on screen.

03:43 The annotation of the callable is now modified. The ellipsis literal is the first argument in square brackets, meaning that the input function can take any number of arguments of arbitrary types.

03:55 The second parameter is changed to the TypeVar("T"). This is a type variable that can stand in for any type. Since you use "T" as the return type for apply_func() as well, this declares that apply_func() returns the same type as func.

04:11 Because the callable supplied to apply_func() can take arguments of an arbitrary number or no arguments at all, you can use *args and **kwargs to indicate this.

04:23 And when a return type is added, the TypeVar("T"). And finally, the return line is updated to use *args and **kwargs.

04:36 This concept can be taken further. At the moment, there’s no explicit relationship between the ellipsis inside Callable and the any annotations of *args and **kwargs.

04:48 You can use parameter specification variables to improve these type hints further.

04:59 First, the import of Any is removed and ParamSpec is imported.

05:06 An instance of ParamSpec("P") is then created. It represents all of the parameters of func() and apply_func() will inherit the same type for its *args and **kwargs. The type hints for *args and **kwargs are now replaced with the appropriate attributes from “P”.

05:32 Note that if you’re on a Python version before 3.10, you’d need to import ParamSpec from typing_extensions instead. In addition to annotating callable arguments, you can also use type hints to formally specify a callable return type of a factory function or generator, which you’ll be looking at in the next section of the course.

Become a Member to join the conversation.