Поиск по списку объектов в Python

94

Предположим, я создаю простой класс для работы, аналогичный структуре в стиле C, просто для хранения элементов данных. Я пытаюсь понять, как искать в списке объектов объекты с атрибутом, равным определенному значению. Ниже приведен простой пример, иллюстрирующий то, что я пытаюсь сделать.

Например:

class Data:
    pass

myList = []

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

Как мне выполнить поиск в списке myList, чтобы определить, содержит ли он элемент с n == 5?

Я гуглил и искал документы Python, и я думаю, что смогу сделать это с пониманием списка, но я не уверен. Я мог бы добавить, что мне, кстати, приходится использовать Python 2.4.3, поэтому какие-либо новые функции gee-whiz 2.6 или 3.x мне недоступны.

m0j0
источник
Возможно, непреднамеренная причуда вашего примера: myList = [Data (). N == 0, Data (). N = 1, ...], где data.n будет назначен range (), а data.n будет index в myList. Таким образом, вы можете получить любой экземпляр Data (), просто ссылаясь на myList по значению индекса. Конечно, позже вы можете изменить myList [0] .n = 5.2 или что-то в этом роде. И пример, возможно, был слишком упрощен.
DevPlayer

Ответы:

139

Вы можете получить список всех совпадающих элементов с пониманием списка:

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

Если вы просто хотите определить, содержит ли список какой-либо элемент, который соответствует, и сделать это (относительно) эффективно, вы можете сделать

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
Адам Розенфилд
источник
25
или любой (custom_filter (x) для x в myList, если xn == 30), который является просто вашей встроенной функцией "содержит".
nosklo
Ошибка синтаксиса на носкло - нужен дополнительный набор () вокруг генератора.
gahooa
Не так. Попробуйте и убедитесь.
Роберт Россни, 01
1
было бы хорошо объединить этот ответ вместе с gahooa ( stackoverflow.com/a/598602/2349267 ).
Роман Хван
77

Простой, элегантный и мощный:

Выражение генератора в сочетании со встроенным… (Python 2.5+)

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

Использует any()встроенный Python , который определяется следующим образом:

any (iterable) -> Возвращает True, если какой-либо элемент итерации истинен. Эквивалентен:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False
гахуа
источник
Ницца. К вашему сведению, вы можете сделать любое (x вместо x в mylist, если xn == 10), чтобы сохранить некоторые скобки (также == not =).
Джейкоб Гэбриелсон
Я предпочитаю использовать, any(x for x in mylist if x['n'] == 10)но это хорошая идея
Алекс Монтойя
48

Для полноты картины давайте не будем забывать о простейшей вещи, которая могла бы работать:

for i in list:
  if i.n == 5:
     # do something with it
     print "YAY! Found one!"
Чарли Мартин
источник
39
[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
Маркус Жардерот
источник
1
Это хороший ответ, так как "первый" метод, вероятно, является наиболее распространенным вариантом использования.
galarant
большое спасибо! индексы матчей были тем, что я искал. Есть ли ярлык для прямого индексирования списка для доступа к другому полю? Теперь я получаю список записей списка (там всего одна запись, поэтому это список с одним элементом). Чтобы получить индекс, мне нужно выполнить result [0], прежде чем я смогу использовать его для индексации списка. Из примера вопрос, я хочу получить доступ n_squared от конкретного п: MyList [индекс myList.n == 5] .n_squared
Frieke
32
filter(lambda x: x.n == 5, myList)
Vartec
источник
25
для тех, кто хочет изучить Python, понимание лямбда является основным.
vartec
2
Что ж, да и нет - с создателями списков и сортировки ключевых функций, таких как operator.attrgetter, я почти никогда не использую lambdas.
Бен Хойт
9

Вы можете использовать inдля поиска элемента в коллекции и понимание списка для извлечения интересующего вас поля. Это (работает для списков, наборов, кортежей и всего, что определяет __contains__или __getitem__).

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

Смотрите также:

Том Данэм
источник
4

Вы должны добавить в свой класс метод __eq__и __hash__метод Data, он может проверить __dict__, равны ли атрибуты (одинаковые свойства), а затем, равны ли их значения.

Если вы это сделали, вы можете использовать

test = Data()
test.n = 5

found = test in myList

В inключевых словах проверяет, testнаходится в myList.

Если вам нужно только nсвойство, Dataвы можете использовать:

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
Йоханнес Вайс
источник
3

Подумайте об использовании словаря:

myDict = {}

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

print(5 in myDict)
дан-гф
источник
Или: d = dict ((i, i * i) for i in range (20))
hughdbrown 01
Он решает тривиальную проблему, которую я использовал для иллюстрации своего вопроса, но на самом деле не решил мой основной вопрос. Ответ, который я искал (5+ лет назад), был составлением списка. :)
m0j0
1

Другой способ сделать это - использовать функцию next ().

matched_obj = next(x for x in list if x.n == 10)
Оливер Бриден
источник