Combine Multiple Filter Conditions in Python
Janne Kemppainen |This problem occurred when I was trying to think of an alternative way for returning the first item from a list that matches given constraints. Using if statements inside a for loop is the obvious way to comb through the items but the built-in filter()
function provides an interesting alternative.
So let’s say you have a list of products with different properties and you want to get the first product that matches the criteria passed to the function. These can be a minimum or maximum price, name containing a certain string, matching product category, etc. The function implementation could look something like this:
def get_first_match(
products,
name_contains=None,
min_price=None,
max_price=None,
product_categories=None,
):
filters = []
if name_contains:
filters.append(lambda p: name_contains in p["name"])
if min_price:
filters.append(lambda p: p["price"] >= min_price)
if max_price:
filters.append(lambda p: p["price"] <= max_price)
if product_categories:
filters.append(lambda p: p["category"] in product_categories)
try:
return next(filter(lambda p: all(f(p) for f in filters), products))
except StopIteration:
return None
First we define an array to hold the filters and add only those arguments that have been provided to the function. No filters are applied by default.
The built in filter()
function loops through an iterable, in this case a list, and returns an iterator. Since we want the first item instead of an iterator we need to call the next()
function to get the first result. If there are no matching items, calling the next function would throw a StopIteration
exception. Therefore we need to wrap the function call in a try..catch
statement.
The actual filter call contains two parameters: the lambda function that is used to evaluate each item and the iterable that we want to go through. The all()
function collects the results of the enabled filters and returns true only if all of them evaluate to True
.
You might wonder what happens when the filter list is empty. This edge case is automatically taken care of since calling all()
on an empty list is always True
. Therefore the filtering lambda function is also always True
, and the first item will be returned.
Let’s test the function with some dummy data:
products = [
{"name": "Race car", "category": "Toys", "price": 120},
{"name": "T-shirt", "category": "Clothing", "price": 20},
{"name": "Tricycle", "category": "Toys", "price": 240},
{"name": "LED bulb", "category": "Home", "price": 13},
{"name": "Jeans", "category": "Clothing", "price": 80},
]
You can see that the function works as expected:
>>> get_first_match(products)
{'name': 'Race car', 'category': 'Toys', 'price': 120}
>>> get_first_match(products, min_price=150)
{'name': 'Tricycle', 'category': 'Toys', 'price': 240}
>>> get_first_match(products, min_price=50, max_price=100)
{'name': 'Jeans', 'category': 'Clothing', 'price': 80}
>>> get_first_match(products, name_contains="cycle")
{'name': 'Tricycle', 'category': 'Toys', 'price': 240}
>>> get_first_match(products, product_categories=["Home", "Clothing"])
{'name': 'T-shirt', 'category': 'Clothing', 'price': 20}
>>> get_first_match(products, min_price=50, product_categories=["Clothing"])
{'name': 'Jeans', 'category': 'Clothing', 'price': 80}
>>> get_first_match(products, min_price=50, product_categories=["Unknown"])
None
If there are no items that match all enabled filters the function returns None
.
I hope you found this quick tutorial helpful. See you in the next one!
Next post
Implement v-model in Vue.js