Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic list comprehension

I would like to know if python is able to create a list by comprehension using multiple and optional criteria.

Let's make an example. Considering the following object (partial description):

class Person():
    def __init__(self):
        self.id = <next id of some kind>
        self.name = 'default name'
        self.gender = 'm'
        self.age = 20

<...>

Suppose I created a list of all Persons into world. Then I want to create a GUI which will allow me to browse the collection based on search criteria (the GUI conception is out of scope of the question), e.g name (regex based), id, gender and age (with equal, not equal and greater or lesser than). None of the search criteria are mandatory (we can suppose it's None I guess) and the type do not really matter for this question.

How can I filter the list of Person in a clever python-way?

If I have known criteria I could do a comprehension :

l = [person for person in world if re.search(person.name, '.*Smith') and person.gender = 'm' and person.age < 20]

But as the user is able to choose what he wants, I won't know what criteria to use. I can of course build this as fully fledged function:

l = world
if nameSearch:
    l = [person for person in l if re.search(person.name, nameSearch)]
if genderSearch:
    l = [person for person in l if gender == genderSearch]
<...>
return l

But I feel python would have a way to do it more properly.

like image 409
M'vy Avatar asked Mar 18 '23 08:03

M'vy


1 Answers

Based DCS' comment, here is a example how to use functions as filters. A filter is just a function which returns a boolean (given an instance of Person). For faster processing I suggest you take a look at pandas, which is a very good choice for data filtering/sorting/munging, but this might get you started with a simple solution. The only task that is left to you, is to create the filters based on the user's input.

from random import random

class Person():
    def __init__(self, id):
        self.id = id
        self.name = 'Name{}'.format(id)
        self.gender = 'm' if random() > 0.5 else 'f'
        self.age = int(random() * 10) + 10

    def __repr__(self):
        return 'Person-{} ({}, {}. {})'.format(self.id,
                                               self.name,
                                               self.gender,
                                               self.age)

Setting up some test data:

people = [Person(id) for id in range(10)]

[Person-0 (Name0, f. 15),
 Person-1 (Name1, f. 14),
 Person-2 (Name2, f. 12),
 Person-3 (Name3, f. 18),
 Person-4 (Name4, m. 12),
 Person-5 (Name5, f. 18),
 Person-6 (Name6, f. 15),
 Person-7 (Name7, f. 15),
 Person-8 (Name8, f. 10),
 Person-9 (Name9, m. 16)]

Output:

def by_age(age):
    return lambda person: person.age == age

def by_name(name):
    return lambda person: re.search(person.name, name)

def by_gender(gender):
    return lambda person: person.gender == gender

filters = (by_age(15),
           by_gender('f'))

filtered_people = (p for p in people if all([f(p) for f in filters]))
list(filtered_people)

Which gives us the following filtered list of people:

[Person-0 (Name0, f. 15), Person-6 (Name6, f. 15), Person-7 (Name7, f. 15)]

You could even change the predicate all to any in order select all people which match any of the specified filters.

like image 133
Matt Avatar answered Mar 27 '23 23:03

Matt