m0j0
m0j0

Reputation: 3874

Searching a list of objects in Python

Let's assume I'm creating a simple class to work similar to a C-style struct, to just hold data elements. I'm trying to figure out how to search a list of objects for objects with an attribute equaling a certain value. Below is a trivial example to illustrate what I'm trying to do.

For instance:

class Data:
    pass

myList = []

for i in range(20):
    data = Data()
    data.n = i
    data.n_squared = i * i
    myList.append(data)

How would I go about searching the myList list to determine if it contains an element with n == 5?

I've been Googling and searching the Python docs, and I think I might be able to do this with a list comprehension, but I'm not sure. I might add that I'm having to use Python 2.4.3 by the way, so any new gee-whiz 2.6 or 3.x features aren't available to me.

Upvotes: 151

Views: 333844

Answers (11)

fuzzyTew
fuzzyTew

Reputation: 3778

I'm surprised there's no answer that asserts that only one item matches.

Maybe this does that:

def one_matching(list, test):
  filtered = [item for item in list if test(item)]
  if len(filtered) != 1:
    if len(filtered) == 0:
      raise KeyError("No matching value found.")
    else:
      raise KeyError("Multiple matching values found.", *filtered)
  return filtered[0] 

# prints 2:
print(one_matching([1,2,3], lambda x: x**2 == 4))

Upvotes: 1

Matt Koch
Matt Koch

Reputation: 11

Use the following list comprehension in combination with the index method:

data_n = 30
j = [data.n for data in mylist].index(data_n)
print(mylist[j].data.n == data_n)

Upvotes: 1

SEMICS
SEMICS

Reputation: 441

Another way you could do it is using the next() function.

matched_obj = next(x for x in list if x.n == 10)

Upvotes: 18

Markus Jarderot
Markus Jarderot

Reputation: 89221

[x for x in myList if x.n == 30]               # list of all matches
[x.n_squared for x in myList if x.n == 30]     # property of matches
any(x.n == 30 for x in myList)                 # if there is any matches
[i for i,x in enumerate(myList) if x.n == 30]  # indices of all matches

def first(iterable, default=None):
  for item in iterable:
    return item
  return default

first(x for x in myList if x.n == 30)          # the first match, if any

Upvotes: 62

gahooa
gahooa

Reputation: 137492

Simple, Elegant, and Powerful:

A generator expression in conjuction with a builtin… (python 2.5+)

any(x for x in mylist if x.n == 10)

Uses the Python any() builtin, which is defined as follows:

any(iterable) -> Return True if any element of the iterable is true. Equivalent to:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False

Upvotes: 106

Adam Rosenfield
Adam Rosenfield

Reputation: 400542

You can get a list of all matching elements with a list comprehension:

[x for x in myList if x.n == 30]  # list of all elements with .n==30

If you simply want to determine if the list contains any element that matches and do it (relatively) efficiently, you can do

def contains(list, filter):
    for x in list:
        if filter(x):
            return True
    return False

if contains(myList, lambda x: x.n == 3)  # True if any element has .n==3
    # do stuff

Upvotes: 224

Tom Dunham
Tom Dunham

Reputation: 5907

You can use in to look for an item in a collection, and a list comprehension to extract the field you are interested in. This (works for lists, sets, tuples, and anything that defines __contains__ or __getitem__).

if 5 in [data.n for data in myList]:
    print "Found it"

See also:

Upvotes: 10

dan-gph
dan-gph

Reputation: 16909

Consider using a dictionary:

myDict = {}

for i in range(20):
    myDict[i] = i * i

print(5 in myDict)

Upvotes: 3

Johannes Weiss
Johannes Weiss

Reputation: 54081

You should add a __eq__ and a __hash__ method to your Data class, it could check if the __dict__ attributes are equal (same properties) and then if their values are equal, too.

If you did that, you can use

test = Data()
test.n = 5

found = test in myList

The in keyword checks if test is in myList.

If you only want to a a n property in Data you could use:

class Data(object):
    __slots__ = ['n']
    def __init__(self, n):
        self.n = n
    def __eq__(self, other):
        if not isinstance(other, Data):
            return False
        if self.n != other.n:
            return False
        return True
    def __hash__(self):
        return self.n

    myList = [ Data(1), Data(2), Data(3) ]
    Data(2) in myList  #==> True
    Data(5) in myList  #==> False

Upvotes: 6

vartec
vartec

Reputation: 134671

filter(lambda x: x.n == 5, myList)

Upvotes: 54

Charlie Martin
Charlie Martin

Reputation: 112404

Just for completeness, let's not forget the Simplest Thing That Could Possibly Work:

for i in list:
  if i.n == 5:
     # do something with it
     print "YAY! Found one!"

Upvotes: 58

Related Questions