395

I've got a list of objects. I want to find one (first or whatever) object in this list that has an attribute (or method result - whatever) equal to value.

What's the best way to find it?

Here's a test case:

class Test:
    def __init__(self, value):
        self.value = value

import random

value = 5

test_list = [Test(random.randint(0,100)) for x in range(1000)]

# that I would do in Pascal, I don't believe it's anywhere near 'Pythonic'
for x in test_list:
    if x.value == value:
        print "i found it!"
        break

I think using generators and reduce() won't make any difference because it still would be iterating through the list.

ps.: Equation to value is just an example. Of course, we want to get an element that meets any condition.

xxx
  • 1,153
  • 1
  • 11
  • 23
seler
  • 8,803
  • 4
  • 41
  • 54
  • 3
    Here's a good discussion of this question: http://tomayko.com/writings/cleanest-python-find-in-list-function – Andrew Hare Aug 19 '11 at 17:54
  • The original post is __ridiculously__ out of date, but the 2nd response matches my one-line version exactly. I'm not convinced it's better than the basic loop version though. – agf Aug 19 '11 at 18:07

10 Answers10

754
next((x for x in test_list if x.value == value), None)

This gets the first item from the list that matches the condition, and returns None if no item matches. It's my preferred single-expression form.

However,

for x in test_list:
    if x.value == value:
        print("i found it!")
        break

The naive loop-break version, is perfectly Pythonic -- it's concise, clear, and efficient. To make it match the behavior of the one-liner:

for x in test_list:
    if x.value == value:
        print("i found it!")
        break
else:
    x = None

This will assign None to x if you don't break out of the loop.

Tropicalrambler
  • 619
  • 1
  • 9
  • 15
agf
  • 171,228
  • 44
  • 289
  • 238
  • 141
    +1 for the reassuring "The naive loop-break version, is perfectly Pythonic". – Mathieu Dhondt Aug 20 '11 at 21:06
  • great solution, but how do i modify your line so that I can make x.value actually mean x.fieldMemberName where that name is stored in value? field = "name" next((x for x in test_list if x.field == value), None) so that in this case, i am actually checking against x.name, not x.field – Stewart Dale Jul 15 '15 at 18:46
  • 4
    @StewartDale It's not totally clear what you're asking, but I think you mean `... if getattr(x, x.fieldMemberName) == value`. That will fetch the attribute from `x` with the name stored in `fieldMemberName`, and compare it to `value`. – agf Jul 15 '15 at 20:19
  • 4
    @ThatTechGuy -- The `else` clause is meant to be on the `for` loop, not the `if`. (Rejected Edit). – agf Feb 02 '18 at 05:53
  • 4
    @agf Wow I literally had no idea that existed.. http://book.pythontips.com/en/latest/for_-_else.html cool! – ThatTechGuy Feb 04 '18 at 18:50
  • @Skalex see the earlier comments -- the `else` is in the right place. Also, the `i`s weren't capitalized in the original code, please don't fix it here as it's meant to match exactly. – agf Sep 02 '19 at 19:17
  • How would you do this in a Django Jinja template if you were looking for the same thing? {% if (object with attribute) in object.all() %} – Zack Plauché Apr 21 '20 at 17:10
39

Since it has not been mentioned just for completion. The good ol' filter to filter your to be filtered elements.

Functional programming ftw.

####### Set Up #######
class X:

    def __init__(self, val):
        self.val = val

elem = 5

my_unfiltered_list = [X(1), X(2), X(3), X(4), X(5), X(5), X(6)]

####### Set Up #######

### Filter one liner ### filter(lambda x: condition(x), some_list)
my_filter_iter = filter(lambda x: x.val == elem, my_unfiltered_list)
### Returns a flippin' iterator at least in Python 3.5 and that's what I'm on

print(next(my_filter_iter).val)
print(next(my_filter_iter).val)
print(next(my_filter_iter).val)

### [1, 2, 3, 4, 5, 5, 6] Will Return: ###
# 5
# 5
# Traceback (most recent call last):
#   File "C:\Users\mousavin\workspace\Scripts\test.py", line 22, in <module>
#     print(next(my_filter_iter).value)
# StopIteration


# You can do that None stuff or whatever at this point, if you don't like exceptions.

I know that generally in python list comprehensions are preferred or at least that is what I read, but I don't see the issue to be honest. Of course Python is not an FP language, but Map / Reduce / Filter are perfectly readable and are the most standard of standard use cases in functional programming.

So there you go. Know thy functional programming.

filter condition list

It won't get any easier than this:

next(filter(lambda x: x.val == value,  my_unfiltered_list)) # Optionally: next(..., None) or some other default value to prevent Exceptions
Nima Mousavi
  • 1,601
  • 2
  • 21
  • 30
  • 1
    I quite like the style of this but there are two potential issues. **1**: It works in Python 3 only; in Python 2, `filter` returns a list which is not compatible with `next`. **2**: it requires that there is a definite match, else you will get a `StopIteration` exception. – freethebees Jan 17 '18 at 12:22
  • 2
    1: I'm not aware of Python 2. When I started using Python, Python 3 was already available. Unfortunately I'm clueless about the specifcs of Python 2. 2. @freethebees as pointed out by agf. You can use next(..., None) or some other default value, if you are no fan of exceptions. I also added it as a comment to my code. – Nima Mousavi Feb 15 '18 at 09:36
  • 1
    @freethebees Point 2 might actually be good. When I require a certain object in a list, failing fast is a good thing. – kap Jan 06 '20 at 15:03
  • 2
    @freethebees you shouldn't use python 2 after 01.01.2020 since it was end of support for it. –  vrnvorona Feb 04 '21 at 07:24
  • 3
    @vrnvorona My comment was made in 2018 – freethebees Feb 04 '21 at 10:18
37

A simple example: We have the following array

li = [{"id":1,"name":"ronaldo"},{"id":2,"name":"messi"}]

Now, we want to find the object in the array that has id equal to 1

  1. Use method next with list comprehension
next(x for x in li if x["id"] == 1 )
  1. Use list comprehension and return first item
[x for x in li if x["id"] == 1 ][0]
  1. Custom Function
def find(arr , id):
    for x in arr:
        if x["id"] == id:
            return x
find(li , 1)

Output all the above methods is {'id': 1, 'name': 'ronaldo'}

Mohammad Nazari
  • 2,535
  • 1
  • 18
  • 29
4

Old question but I use this quite frequently (for version 3.8). It's a bit of syntactic salt, but it has the advantage over the top answer in that you could retrieve a list of results (if there are multiple) by simply removing the [0] and it still defaults to None if nothing is found. For any other condition, simply change the x.value==value to what ever you're looking for.

_[0] if (_:=[x for x in test_list if x.value==value]) else None
Colin Hicks
  • 348
  • 1
  • 3
  • 13
  • What does _[0] and _: mean? – Phatmandrake Oct 04 '22 at 13:33
  • @Phatmandrake `_` is just a variable name, it could be `foo` or any other allowed name. `[0]` is an index. `:=` is a walrus operator (in short -- it is the form of assignment that could be used within list comprehension) – the_joric Jan 03 '23 at 17:09
3

You could do something like this

dict = [{
   "id": 1,
   "name": "Doom Hammer"
 },
 {
    "id": 2,
    "name": "Rings ov Saturn"
 }
]

for x in dict:
  if x["id"] == 2:
    print(x["name"])

Thats what i use to find the objects in a long array of objects.

Illud
  • 319
  • 1
  • 4
  • 12
2

You could also implement rich comparison via __eq__ method for your Test class and use in operator. Not sure if this is the best stand-alone way, but in case if you need to compare Test instances based on value somewhere else, this could be useful.

class Test:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        """To implement 'in' operator"""
        # Comparing with int (assuming "value" is int)
        if isinstance(other, int):
            return self.value == other
        # Comparing with another Test object
        elif isinstance(other, Test):
            return self.value == other.value

import random

value = 5

test_list = [Test(random.randint(0,100)) for x in range(1000)]

if value in test_list:
    print "i found it"
tm-
  • 333
  • 1
  • 3
  • 10
1

I just ran into a similar problem and devised a small optimization for the case where no object in the list meets the requirement.(for my use-case this resulted in major performance improvement):

Along with the list test_list, I keep an additional set test_value_set which consists of values of the list that I need to filter on. So here the else part of agf's solution becomes very-fast.

user1578297
  • 159
  • 1
  • 2
0

For below code, xGen is an anonomous generator expression, yFilt is a filter object. Note that for xGen the additional None parameter is returned rather than throwing StopIteration when the list is exhausted.

arr =((10,0), (11,1), (12,2), (13,2), (14,3))

value = 2
xGen = (x for x in arr if x[1] == value)
yFilt = filter(lambda x: x[1] == value, arr)
print(type(xGen))
print(type(yFilt))

for i in range(1,4):
    print('xGen: pass=',i,' result=',next(xGen,None))
    print('yFilt: pass=',i,' result=',next(yFilt))

Output:

<class 'generator'>
<class 'filter'>
xGen: pass= 1  result= (12, 2)
yFilt: pass= 1  result= (12, 2)
xGen: pass= 2  result= (13, 2)
yFilt: pass= 2  result= (13, 2)
xGen: pass= 3  result= None
Traceback (most recent call last):
  File "test.py", line 12, in <module>
    print('yFilt: pass=',i,' result=',next(yFilt))
StopIteration
JayS
  • 2,057
  • 24
  • 16
0

If you are looking for an object in an array in Python. You can use the if conditional.

 model_t1 = [0,1,2,3,4]
 model_t2 = [7,8,9,15,14]
 _data = model_t1

 for md in model_t2:
     _data.append(md)

 for p_data in _data:
     if len(p_data['Property']) == 'Value':
        print(json(p_data))
Onur Dikmen
  • 339
  • 1
  • 6
  • 17
0

Many great answers already, all dealing with single item lookup, as requested by the OP.

However, if you need to look up multiple items from the list, and your lookup values are unique, e.g. unique ids, it could pay off considerably to convert the list to a dict.

For example:

# dummy data
my_items = [{'id': i, ...} for i in range(1000)]

# convert to dict
my_items_dict = {item['id']: item for item in my_list}

# lookup multiple items
for item_id in long_list_of_ids:
    item = my_items_dict.get(item_id)
    ...
djvg
  • 11,722
  • 5
  • 72
  • 103