Suchen Sie ein Objekt in einer Liste, dessen Attribut einem bestimmten Wert entspricht (der eine beliebige Bedingung erfüllt).


221

Ich habe eine Liste von Objekten. Ich möchte ein (erstes oder was auch immer) Objekt in dieser Liste finden, dessen Attribut (oder Methodenergebnis - was auch immer) gleich ist value.

Was ist der beste Weg, um es zu finden?

Hier ist ein Testfall:

  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

Ich denke, Generatoren zu verwenden und reduce()macht keinen Unterschied, weil es immer noch durch die Liste iterieren würde.

ps.: Gleichung zu valueist nur ein Beispiel. Natürlich wollen wir ein Element bekommen, das jede Bedingung erfüllt.


2
Hier ist eine gute Diskussion dieser Frage: tomayko.com/writings/cleanest-python-find-in-list-function
Andrew Hare

Der ursprüngliche Beitrag ist lächerlich veraltet, aber die zweite Antwort entspricht genau meiner einzeiligen Version. Ich bin jedoch nicht davon überzeugt, dass es besser ist als die Basis-Loop-Version.
Agf

Antworten:


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

Dadurch wird das erste Element aus der Liste abgerufen, das der Bedingung entspricht, und es wird zurückgegeben, Nonewenn kein Element übereinstimmt. Es ist meine bevorzugte Form für einen einzelnen Ausdruck.

Jedoch,

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

Die naive Loop-Break-Version ist perfekt pythonisch - sie ist präzise, ​​klar und effizient. Damit es dem Verhalten des Einzeilers entspricht:

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

Dies wird zugewiesen None, xwenn Sie nicht breakaus der Schleife heraus.


72
+1 für die beruhigende "Die naive Loop-Break-Version ist perfekt pythonisch".
LaundroMat

Tolle Lösung, aber wie ändere ich Ihre Zeile so, dass x.value tatsächlich x.fieldMemberName bedeutet, in dem dieser Name im Wert gespeichert ist? field = "name" next ((x für x in test_list wenn x.field == value), None), so dass ich in diesem Fall tatsächlich gegen x.name prüfe, nicht gegen x.field
Stewart Dale

3
@StewartDale Es ist nicht ganz klar, was Sie fragen, aber ich denke, Sie meinen ... if getattr(x, x.fieldMemberName) == value. Dadurch wird das Attribut xmit dem darin gespeicherten Namen abgerufen fieldMemberNameund mit verglichen value.
Agf

1
@ThatTechGuy - Die elseKlausel soll sich in der forSchleife befinden, nicht die if. (Abgelehnte Bearbeitung).
Agf

1
@agf Wow, ich hatte buchstäblich keine Ahnung, dass es das gibt. book.pythontips.com/en/latest/for_-_else.html cool!
ThatTechGuy

25

Da wurde es nicht nur zur Vervollständigung erwähnt. Der gute alte Filter zum Filtern Ihrer zu filternden Elemente.

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

Ich weiß, dass im Allgemeinen in Python Listen Verständnis bevorzugt wird oder zumindest das, was ich lese, aber ich sehe das Problem nicht, um ehrlich zu sein. Natürlich ist Python keine FP-Sprache, aber Map / Reduce / Filter sind perfekt lesbar und der Standard der Standardanwendungsfälle in der funktionalen Programmierung.

Hier bitteschön. Kennen Sie Ihre funktionale Programmierung.

Filterbedingungsliste

Einfacher geht es nicht:

next(filter(lambda x: x.val == value,  my_unfiltered_list)) # Optionally: next(..., None) or some other default value to prevent Exceptions

Ich mag den Stil sehr, aber es gibt zwei mögliche Probleme. 1 : Es funktioniert nur in Python 3; Gibt in Python 2 filtereine Liste zurück, die nicht mit kompatibel ist next. 2 : Es erfordert, dass es eine bestimmte Übereinstimmung gibt, sonst erhalten Sie eine StopIterationAusnahme.
Freethebees

1
1: Python 2 ist mir nicht bekannt. Als ich anfing, Python zu verwenden, war Python 3 bereits verfügbar. Leider habe ich keine Ahnung von den Spezifikationen von Python 2. 2. @freethebees, wie von agf hervorgehoben. Sie können next (..., None) oder einen anderen Standardwert verwenden, wenn Sie kein Fan von Ausnahmen sind. Ich habe es auch als Kommentar zu meinem Code hinzugefügt.
Nima Mousavi

@freethebees Punkt 2 könnte tatsächlich gut sein. Wenn ich ein bestimmtes Objekt in einer Liste benötige, ist es eine gute Sache, schnell zu versagen.
Kap

7

Ein einfaches Beispiel : Wir haben das folgende Array

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

Nun wollen wir das Objekt in dem Array finden, dessen ID gleich 1 ist

  1. Verwenden Sie eine Methode nextmit Listenverständnis
next(x for x in li if x["id"] == 1 )
  1. Verwenden Sie das Listenverständnis und geben Sie das erste Element zurück
[x for x in li if x["id"] == 1 ][0]
  1. Benutzerdefinierte Funktion
def find(arr , id):
    for x in arr:
        if x["id"] == id:
            return x
find(li , 1)

Ausgabe aller oben genannten Methoden ist {'id': 1, 'name': 'ronaldo'}


1

Ich bin gerade auf ein ähnliches Problem gestoßen und habe eine kleine Optimierung für den Fall entwickelt, dass kein Objekt in der Liste die Anforderung erfüllt (für meinen Anwendungsfall führte dies zu einer erheblichen Leistungsverbesserung):

Zusammen mit der Liste test_list behalte ich eine zusätzliche Menge test_value_set, die aus Werten der Liste besteht, nach denen ich filtern muss. Hier wird der andere Teil der Lösung von agf sehr schnell.


1

Sie könnten so etwas tun

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

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

Das ist, was ich benutze, um die Objekte in einer langen Reihe von Objekten zu finden.


Wie unterscheidet sich das von dem, was der Fragesteller bereits versucht hat?
Anum Sheraz

Ich wollte zeigen, wie er das Objekt und die Anordnung von Objekten auf einfachste Weise erhalten kann.
Illud

0

Sie können auch einen umfassenden Vergleich über eine __eq__Methode für Ihre TestKlasse implementieren und den inOperator verwenden. Sie sind sich nicht sicher, ob dies der beste eigenständige Weg ist. Wenn Sie jedoch TestInstanzen vergleichen müssen, die auf einer valueanderen Stelle basieren , kann dies hilfreich sein.

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

Für den folgenden Code ist xGen ein anonomer Generatorausdruck, yFilt ist ein Filterobjekt. Beachten Sie, dass für xGen der zusätzliche Parameter None zurückgegeben wird, anstatt StopIteration auszulösen, wenn die Liste erschöpft ist.

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

Ausgabe:

<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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.