Найти объект в списке с атрибутом, равным некоторому значению (удовлетворяющему любому условию)

222

У меня есть список объектов. Я хочу найти один (первый или любой другой) объект в этом списке, у которого атрибут (или результат метода - что угодно) равен value.

Какой самый лучший способ найти его?

Вот тестовый пример:

  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 isn't anywhere near 'Pythonic'
  for x in test_list:
      if x.value == value:
          print "i found it!"
          break

Я думаю, что использовать генераторы и reduce()не будет иметь никакого значения, потому что он все еще будет перебирать список.

PS .: Уравнение valueявляется лишь примером. Конечно, мы хотим получить элемент, который удовлетворяет любым условиям.

Селер
источник
2
Вот хорошее обсуждение этого вопроса: tomayko.com/writings/cleanest-python-find-in-list-function
Andrew Hare
Оригинальный пост смехотворно устарел, но второй ответ точно соответствует моей однострочной версии. Я не уверен, что это лучше, чем базовая версия цикла.
AGF

Ответы:

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

Это получает первый элемент из списка, который соответствует условию, и возвращает None если ни один элемент не соответствует. Это моя любимая форма с одним выражением.

Тем не мение,

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

Наивная версия с петлевым разрывом, совершенно Pythonic - она ​​лаконична, понятна и эффективна. Чтобы он соответствовал поведению однострочника:

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

Это позволит назначить Noneдля , xесли вы не breakиз петли.

AGF
источник
73
+1 за заверение "Наивная версия с разрывом петли, совершенно Pythonic".
LaundroMat
отличное решение, но как мне изменить вашу строку, чтобы я мог сделать x.value на самом деле означать x.fieldMemberName, где это имя хранится в значении? field = "name" next ((x для x в test_list, если x.field == value), None), так что в этом случае я фактически проверяю x.name, а не x.field
Стюарт Дейл
3
@ StewartDale Не совсем понятно, о чем вы спрашиваете, но я думаю, что вы имеете в виду ... if getattr(x, x.fieldMemberName) == value. Это приведет к получению атрибута xс сохраненным именем fieldMemberNameи его сопоставлению value.
AGF
1
@ThatTechGuy - elseпредложение должно быть в forцикле, а не if. (Отклонено Редактировать).
августа
1
@agf Wow Я буквально понятия не имел, что существует .. book.pythontips.com/en/latest/for_-_else.html круто!
ThatTechGuy
25

Так как это не было упомянуто только для завершения. Хороший старый фильтр для фильтрации ваших фильтруемых элементов.

Функциональное программирование

####### 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.

Я знаю, что обычно в списках Python предпочтение отдается предпочтениям или, по крайней мере, это то, что я читаю, но я не вижу в этом проблемы, если честно. Конечно, Python не является языком FP, но Map / Reduce / Filter отлично читаются и являются наиболее стандартными из стандартных вариантов использования в функциональном программировании.

Итак, поехали. Знай свое функциональное программирование.

список условий фильтра

Это не станет легче, чем это:

next(filter(lambda x: x.val == value,  my_unfiltered_list)) # Optionally: next(..., None) or some other default value to prevent Exceptions
Нима Мусави
источник
1
Мне очень нравится стиль этого, но есть две потенциальные проблемы. 1 : работает только в Python 3; в Python 2 filterвозвращает список, который не совместим с next. 2 : требуется наличие определенного совпадения, иначе вы получите StopIterationисключение.
freethebees
1
1: я не знаю Python 2. Когда я начал использовать Python, Python 3 уже был доступен. К сожалению, я ничего не понимаю о спецификациях Python 2. 2. @freethebees, на что указывает agf. Вы можете использовать next (..., None) или другое значение по умолчанию, если вы не являетесь поклонником исключений. Я также добавил это как комментарий к своему коду.
Нима Мусави
@freethebees Точка 2 может быть хорошей. Когда мне требуется определенный объект в списке, быстрый провал - это хорошо.
Кап
8

Простой пример : у нас есть следующий массив

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

Теперь мы хотим найти объект в массиве с идентификатором, равным 1

  1. Используйте метод nextс пониманием списка
next(x for x in li if x["id"] == 1 )
  1. Используйте понимание списка и верните первый элемент
[x for x in li if x["id"] == 1 ][0]
  1. Пользовательская функция
def find(arr , id):
    for x in arr:
        if x["id"] == id:
            return x
find(li , 1)

Вывод всех вышеперечисленных методов есть {'id': 1, 'name': 'ronaldo'}

Мухаммед Назари
источник
1

Я просто столкнулся с подобной проблемой и разработал небольшую оптимизацию для случая, когда ни один объект в списке не отвечает требованию (для моего варианта использования это привело к значительному повышению производительности):

Наряду со списком test_list, я сохраняю дополнительный набор test_value_set, который состоит из значений списка, по которому мне нужно отфильтровать. Так что здесь остальная часть решения agf становится очень быстрой.

user1578297
источник
1

Вы могли бы сделать что-то вроде этого

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

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

Вот что я использую, чтобы найти объекты в длинном массиве объектов.

Illud
источник
Чем это отличается от того, что спрашивающий уже пробовал?
Анум Шераз
Я хотел показать, как он может получить объект и массив объектов самым простым способом.
Illud
0

Вы также можете реализовать расширенное сравнение с помощью __eq__метода для вашего Testкласса и inоператора use . Не уверен, что это лучший автономный способ, но в случае, если вам нужно сравнить Testэкземпляры, основанные на valueдругом месте, это может быть полезно.

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-
источник
0

Для приведенного ниже кода xGen - это выражение автономного генератора, yFilt - объект фильтра. Обратите внимание, что для xGen возвращается дополнительный параметр None, а не выбрасывает StopIteration, когда список исчерпан.

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))

Вывод:

<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
EdW
источник