The operator
Module and Functional Programming
Functional programming is a paradigm that consists of building a program from small functions that perform specific tasks and that can be passed as arguments to other functions to express complex operations. The standard operator
module makes it easy to write functional code (along with the functools
and itertools
modules) by exposing Python operators (for example, the addition operator +
or the item access operator []
) as functions. This is useful because functions, unlike operators, can be stored in variables and passed as arguments.
We won't walk through all the functions available in operator
. Instead, let's focus on three of them: itemgetter()
, attrgetter()
, and methodcaller()
. They encapsulate in functions the item access operation (such as dictionary[key]
or list[index]
), the attribute access operation (object.attribute
) and the method call operation (object.method()
), respectively.
Let's see an example. Suppose we have the following list of products:
|
products = [
|
|
{"name": "Mouse", "price": 10},
|
|
{"name": "Keyboard", "price": 20},
|
|
{"name": "Monitor", "price": 60},
|
|
{"name": "Headphones", "price": 5},
|
|
]
|
Each product is represented by a dictionary with two keys: "name"
and "price"
. Now we want to print the information of these products ordered from lowest to highest price, for which we must first sort the list. We can achieve this with the sorted()
standard function:
The lambda product: product["price"]
expression creates a lambda function that gets the price of each product, which is being used as the criteria (key
) to sort the list. This is a typical functional programming code. However, the operator
module relieves us of the need to create a function whose sole purpose is to access the element of a collection by providing the itemgetter()
function. The previous code can be thus simplified:
As you can see, itemgetter("price")
is a shortcut to lambda product: product["price"]
.
What happens if, instead of dictionaries, we have a specific class to store products?
|
from dataclasses import dataclass
|
|
|
|
@dataclass
|
|
class Product:
|
|
name: str
|
|
price: float
|
|
|
|
products = [
|
|
Product("Mouse", 10),
|
|
Product("Keyboard", 20),
|
|
Product("Monitor", 60),
|
|
Product("Headphones", 5),
|
|
]
|
Then the code to sort the products list using a lambda function should look like this:
|
sorted_products = sorted(
|
|
products,
|
|
key=lambda product: product.price
|
|
)
|
|
for product in sorted_products:
|
|
print(f'{product.name} (${product.price})')
|
Again, we have this lambda product: product.price
inline function whose sole purpose is to access the price
attribute of the object passed as argument. We can replace that expression with the attrgetter()
function:
What if Product
has a price_with_tax()
method that returns the after-tax price which we want to use as the criteria for sorting with sorted()
?
Once again, there is no need to create a lambda function since methodcaller()
does the work for us:
Just like sorted()
, there are many functions in the language that allow us to express our algorithms in a functional manner and thus take advantage of the operator
module. For example, if we want to calculate the sum of all the products with taxes included:
total_prices = sum(map(methodcaller("price_with_tax"), products))
Definitely better than: