23

lets assume the following simple Object:

class Mock:
    def __init__(self, name, age):
        self.name = name
        self.age = age

then I have a list with some Objects like this:

myList = [Mock("Dan", 34), Mock("Jack", 30), Mock("Oli", 23)...]

Is there some built-in feature where I can get all Mocks with an age of ie 30? Of course I can iterate myself over them and compare their ages, but something like

find(myList, age=30)

would be nice. Is there something like that?

marshall.ward
  • 6,758
  • 8
  • 35
  • 50
Rafael T
  • 15,401
  • 15
  • 83
  • 144

4 Answers4

36

You might want to pre-index them -

from collections import defaultdict

class Mock(object):
    age_index = defaultdict(list)

    def __init__(self, name, age):
        self.name = name
        self.age = age
        Mock.age_index[age].append(self)

    @classmethod
    def find_by_age(cls, age):
        return Mock.age_index[age]

Edit: a picture is worth a thousand words:

enter image description here

X axis is number of Mocks in myList, Y axis is runtime in seconds.

  • red dots are @dcrooney's filter() method
  • blue dots are @marshall.ward's list comprehension
  • green dots hiding behind the X axis are my index ;-)
Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99
  • Thanks so much for this answer. I was about to reimplement something similar manually. – xlash Apr 20 '16 at 18:04
  • 3
    Thanks. And nice diagram. But it would helpful to discuss the various tradeoffs, e.g. extra startup time to build the index, and the space required for it. – nealmcb Nov 28 '17 at 00:13
  • 1
    This is a good solution, but you do run the risk of: 1. Higher memory usage 2. More CPU usage for the time to precompute the list. This will likely be prohibitive for large lists. – d0m1n0 Feb 28 '20 at 14:10
35

You could try a filter():

filter(lambda x: x.age == 30, myList)

This would return a list with only those objects satisfying the lambda expression.

dckrooney
  • 3,041
  • 3
  • 22
  • 28
  • 3
    And if you plan on doing a lookup by this attribute often, you may want to maintain a `dict` based on the attribute. – Joel Cornett Jun 01 '12 at 23:43
  • 2
    At least for Python 3.5, you need `list(filter(lambda x: x.age == 30, myList))` to get a list. – stefanbschneider Oct 17 '16 at 13:42
  • 1
    Using `filter()` is the old classic answer, but list comprehensions are faster and more portable across versions of Python, as discussed in other answers. – nealmcb Nov 28 '17 at 00:16
21

List comprehensions can pick these up:

new_list = [x for x in myList if x.age == 30]
marshall.ward
  • 6,758
  • 8
  • 35
  • 50
  • nice solution, too, but @dcrooney answer fits more the function. Did anyone compare whats faster? – Rafael T Jun 01 '12 at 23:48
  • 1
    `timeit` gives about 0.30 usec for the list comprehension and 0.58 for the lambda filter on my machine. But they are essentially equivalent here, so you should use the one that you prefer. – marshall.ward Jun 02 '12 at 00:09
  • 2
    Some good discussion of the issue here as well: http://stackoverflow.com/a/1247490/317172 – marshall.ward Jun 02 '12 at 00:18
5

List comprehensions are almost always the faster way to do these things (2x as fast here), though as mentioned earlier indexing is even faster if you're concerned about speed.

~$ python -mtimeit -s"from mock import myList" "filter(lambda x: x.age==21, myList)"
1000000 loops, best of 3: 1.34 usec per loop
~$ python -mtimeit -s"from mock import myList" "[x for x in myList if x.age==21]"
1000000 loops, best of 3: 0.63 usec per loop

For file mock.py in current directory:

class Mock:
    def __init__(self, name, age):
        self.name = name
        self.age = age

myList = [Mock('Tom', 20), Mock('Dick', 21), Mock('Harry', 21), Mock('John', 22)]
beardc
  • 20,283
  • 17
  • 76
  • 94