Знайдіть об’єкт у списку, який має атрибут, рівний деякому значенню (який відповідає будь-якій умові)


221

У мене є список об’єктів. Я хочу знайти в цьому списку один (перший чи будь-який) об’єкт, який має атрибут (або результат методу - будь-який), рівний 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()не матиме жодних значень, оскільки це все одно буде повторювати список.

пс .: Рівняння до value- лише приклад. Звичайно, ми хочемо отримати елемент, який відповідає будь-якій умові.


2
Ось хороше обговорення цього питання: tomayko.com/writings/cleanest-python-find-in-list-function
Ендрю Заєць

Оригінальний пост смішно застарів, але друга відповідь точно відповідає моїй однорядковій версії. Я не переконаний, що це краще, ніж основна версія циклу.
agf

Відповіді:


433
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

Наївна версія циклу перерви, ідеально піфонічна - це лаконічна, чітка та ефективна. Щоб вона відповідала поведінці однолінійного:

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

Це дозволить призначити Noneдля , xякщо ви не breakз петлі.


72
+1 за запевняючу "Наївну версію прориву циклу, ідеально піфонічну".
LaundroMat

чудове рішення, але як я можу змінити ваш рядок, щоб я міг зробити x.value фактично означає x.fieldMemberName, де це ім’я зберігається у значенні? field = "name" next ((x for x in test_list if x.field == value), None), так що в цьому випадку я фактично перевіряю проти x.name, а не x.field
Stewart Dale

3
@StewartDale Не зовсім зрозуміло, що ти просиш, але я думаю, що ти маєш на увазі ... if getattr(x, x.fieldMemberName) == value. Це дозволить отримати атрибут xз ім'ям, яке зберігається fieldMemberName, і порівняти його value.
agf

1
@ThatTechGuy - elseЗастереження має бути на forциклі, а не на if. (Відхилено редагування).
agf

1
@agf Ого, я буквально не мав уявлення про існування .. book.pythontips.com/en/latest/for_-_else.html класно!
ThatTechGuy

25

Оскільки вона не згадується лише для завершення. Хороший ol 'фільтр для фільтрування ваших елементів, які фільтруються.

Функціональне програмування 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.

Я знаю, що загалом у списку пітонів переважніше розуміння або, принаймні, це те, що я читаю, але я не вважаю проблему чесною. Звичайно, 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 : Він працює лише в Python 3; у Python 2 filterповертає список, який не сумісний з next. 2 : це вимагає наявності певної відповідності, інакше ви отримаєте StopIterationвиняток.
вільнолюби

1
1: Я не знаю про Python 2. Коли я почав використовувати Python, Python 3 вже був доступний. На жаль, я не знаю про специфіку Python 2. 2. @freethebees, як вказував agf. Ви можете використовувати наступне (..., None) або якесь інше значення за замовчуванням, якщо ви не любитель винятків. Я також додав це як коментар до свого коду.
Німа Мусаві

@freethebees Точка 2 насправді може бути хорошою. Коли мені потрібен певний об'єкт у списку, невдача - це добре.
кап

7

Простий приклад : У нас є такий масив

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

Тепер ми хочемо знайти об’єкт у масиві, який має id, рівний 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 стає дуже швидкою.


1

Ви могли б зробити щось подібне

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

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

Ось що я використовую для пошуку об'єктів у довгому масиві об’єктів.


Чим це відрізняється від того, що запитувач вже намагався?
Anum Sheraz

Я хотів показати, як він може найпростішим чином отримати об’єкт та масив об’єктів.
Іллюд

0

Ви також можете реалізувати розширене порівняння за допомогою __eq__методу для свого Testкласу та inоператора використання . Не впевнений, чи це найкращий автономний спосіб, але у випадку, якщо вам потрібно порівняти 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"

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.