Python Higher Order Functions (HOFs) are like superheroes of the programming world they can take other functions as their sidekicks and perform some pretty awesome feats! For example, if you have a list of numbers and want to square them all, you can use:
# Define a list of numbers
numbers = [1, 2, 3]
# Define a lambda function that squares its input
square_function = lambda x: x ** 2
# Use the map function to apply the square_function to each element in the numbers list
# The map function takes in a function and an iterable as arguments
# It applies the function to each element in the iterable and returns a map object
# We convert the map object to a list to get the squared_list
squared_list = list(map(square_function, numbers))
# Print the squared_list
print(squared_list) # Output: [1, 4, 9]
Another HOF is `filter()`, which returns a new list containing only those elements that satisfy a certain condition. For example, if you have a list of strings and want to filter out any string shorter than 5 characters, you can use:
# This script uses the `filter()` function to create a new list containing only strings with a length of 5 or more characters.
# Create a list of strings
words = ["hello", "world", "how", "are", "you?"]
# Define a lambda function to check if the length of a string is greater than or equal to 5
filter_function = lambda x: len(x) >= 5
# Use the `filter()` function to filter out strings that do not satisfy the condition defined by the lambda function
longer_words = list(filter(filter_function, words))
# Print the resulting list
print(longer_words) # Output: ['world', 'are', 'you?']
HOFs can also be used for more complex operations such as sorting and searching through lists or dictionaries. In Python 3.11, TypeVarTuple has been introduced to handle variadic parameters in type hints, making it easier to annotate the shape of multidimensional arrays. Additionally, support for TOML configuration parsing is now included in Python’s standard library with the introduction of tomllib module.
But thats not all! In Python 3.11, exception groups have been introduced which allow you to handle multiple exceptions at once using syntax like:
# This script demonstrates the use of exception groups in Python 3.11
# Import the ExceptionGroup class from the built-in exceptions module
from exceptions import ExceptionGroup
# Define a function that might raise a ValueError or TypeError
def some_function():
# Code that may raise a ValueError or TypeError
pass
# Use a try-except block to catch any exceptions that may be raised by the function
try:
some_function()
# Use the ExceptionGroup class to handle multiple exceptions at once
except ExceptionGroup as eg:
# Loop through each exception in the group
for exc in eg:
# Print the name of the exception being handled
print(f"Handling {exc.__class__.__name__}")
This is much more concise than writing multiple except statements!
Exception groups and the `except*` syntax wont replace regular exceptions or plain `except`. In fact, you probably wont have many use cases for creating exception groups yourself. Instead, they’ll mostly be raised by libraries like asyncio. It’s possible to catch regular exceptions with `except*`, but you should stick to plain `except` in most cases and only use `except*` for code that actually may raise an exception group.
To learn more about how exception groups work, how they can be nested, and the full power of `except*`, see Python 3.11 Preview: Task and Exception Groups. Irit Katriel, one of Python’s core developers, presented exception groups at the Python Language Summit in 2021 and at PyCon UK in 2022.
You can read more about the motivation for exception groups and the discussions that led to the current implementation in PEP 654.
Exception Notes is another extension to regular exceptions, which allows you to add arbitrary notes. PEP 678 describes how these notes can be used to add information to an exception in a different piece of code than the one that raised the exception originally. For example, a testing library like Hypothesis can add information about which test failed.
You can add a note to any exception with .add_note() and look at existing notes by inspecting the `.__notes__` attribute:
If an error is raised, then any related notes are printed at the bottom of the traceback.
In the following example, youre wrapping your main loop in a try except block that adds a timestamp to the error. This can be useful if you need to compare the error message with a running log for your program:
As you’ve seen earlier, this program calculates the multiplicative inverse. Here, you’ve added a short `main()` function, which you later call.
You’ve wrapped the call to main() in a try except block that catches any Exception. While you normally want to be more specific, you use Exception here to effectively add context to any exception that your main program happens to raise.
When you run this code, you’ll see the expected ZeroDivisionError. Additionally, your traceback contains a timestamp that may help you in your debugging efforts:
You can use the same pattern to add other useful information to your exceptions. See this Python 3.11 preview and PEP 678 for more information.