Przeszukiwanie listy obiektów w Pythonie

94

Załóżmy, że tworzę prostą klasę, która będzie działać podobnie do struktury w stylu C, aby po prostu przechowywać elementy danych. Próbuję dowiedzieć się, jak przeszukać listę obiektów dla obiektów z atrybutem równym określonej wartości. Poniżej znajduje się trywialny przykład ilustrujący, co próbuję zrobić.

Na przykład:

class Data:
    pass

myList = []

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

Jak mam przejść do przeszukiwania listy myList, aby określić, czy zawiera ona element z n == 5?

Szukałem w Google i przeszukiwałem dokumentację Pythona i myślę, że mógłbym to zrobić ze zrozumieniem listy, ale nie jestem pewien. Mógłbym dodać, że przy okazji muszę używać Pythona 2.4.3, więc żadne nowe funkcje gee-whiz 2.6 lub 3.x nie są dla mnie dostępne.

m0j0
źródło
Być może niezamierzone dziwactwo twojego przykładu: myList = [Data (). N == 0, Data (). N = 1, ...], gdzie data.n zostanie przypisana przez range (), a data.n będzie indeks do myList. W związku z tym umożliwiając pobranie dowolnej instancji Data () po prostu odwołując się do myList za pomocą wartości indeksu. Oczywiście możesz później zmodyfikować myList [0] .n = 5.2 czy coś. A przykład był być może nadmiernie uproszczony.
DevPlayer

Odpowiedzi:

139

Możesz uzyskać listę wszystkich pasujących elementów ze zrozumieniem listy:

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

Jeśli chcesz po prostu określić, czy lista zawiera dowolny element, który pasuje i zrobić to (względnie) wydajnie, możesz to zrobić

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
Adam Rosenfield
źródło
25
lub dowolna (custom_filter (x) for x w myList, jeśli xn == 30), która jest po prostu twoją funkcją „zawiera” jako wbudowaną.
nosklo
Błąd składni w nosklo - potrzebny jest dodatkowy zestaw () wokół generatora.
gahooa
Bynajmniej. Spróbuj i zobacz.
Robert Rossney
1
dobrze byłoby połączyć tę odpowiedź z gahooa ( stackoverflow.com/a/598602/2349267 ).
Roman Hwang,
77

Prosty, elegancki i potężny:

Wyrażenie generatora w połączeniu z wbudowanym… (python 2.5+)

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

Używa any()wbudowanego języka Python , który jest zdefiniowany w następujący sposób:

any (iterowalne) -> Zwraca True, jeśli jakikolwiek element iterowalny jest prawdziwy. Równoważny:

def any(iterable):
    for element in iterable:
        if element:
            return True
    return False
gahooa
źródło
Ładny. FYI, możesz zrobić dowolne (x dla x w mylist, jeśli xn == 10), aby zapisać kilka par (również == not =).
Jacob Gabrielson
Wolę używać, any(x for x in mylist if x['n'] == 10)ale to dobry pomysł
Alex Montoya,
47

Dla kompletności nie zapominajmy o najprostszej rzeczy, która mogłaby zadziałać:

for i in list:
  if i.n == 5:
     # do something with it
     print "YAY! Found one!"
Charlie Martin
źródło
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
Markus Jarderot
źródło
1
Jest to dobra odpowiedź ze względu na metodę „pierwszą”, która jest prawdopodobnie najczęstszym przypadkiem użycia.
galarant
wielkie dzięki! indeksy meczowe były tym, czego szukałem. Czy istnieje skrót do bezpośredniego indeksowania listy w celu uzyskania dostępu do innego pola? Teraz otrzymuję listę wpisów na liście (jest tylko jeden wpis, więc jest to lista z jednym elementem). Aby uzyskać indeks, muszę wykonać wynik [0], zanim będę mógł go użyć do indeksowania listy. Z przykładu pytania chcę uzyskać dostęp do n_squared z konkretnego n: myList [indeks myList.n == 5] .n_squared
Frieke
32
filter(lambda x: x.n == 5, myList)
vartec
źródło
25
dla kogoś, kto chce się uczyć Pythona, zrozumienie lambdy jest podstawą.
vartec
2
Cóż, tak i nie - dzięki składaniu list i sortowaniu kluczowych funkcji, takich jak operator.attrgetter, prawie nigdy nie używam lambdas.
Ben Hoyt,
9

Możesz użyć indo wyszukania elementu w kolekcji i listy złożonej, aby wyodrębnić pole, które Cię interesuje. To (działa dla list, zbiorów, krotek i wszystkiego, co definiuje __contains__lub __getitem__).

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

Zobacz też:

Tom Dunham
źródło
4

Powinieneś dodać a __eq__i __hash__metodę do swojej Dataklasy, może ona sprawdzić, czy __dict__atrybuty są równe (te same właściwości), a następnie, czy ich wartości są równe.

Jeśli to zrobiłeś, możesz użyć

test = Data()
test.n = 5

found = test in myList

Słowo inkluczowe sprawdza, czy testjest w myList.

Jeśli chcesz mieć tylko nnieruchomość w domu, Datamożesz użyć:

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
Johannes Weiss
źródło
3

Rozważ użycie słownika:

myDict = {}

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

print(5 in myDict)
dan-gph
źródło
Lub: d = dict ((i, i * i) for i in range (20))
hughdbrown
Rozwiązuje trywialny problem, którego użyłem do zilustrowania mojego pytania, ale tak naprawdę nie rozwiązał mojego głównego pytania. Odpowiedzią, której szukałem (ponad 5 lat temu) było zrozumienie listy. :)
m0j0
1

Innym sposobem, w jaki możesz to zrobić, jest użycie funkcji next ().

matched_obj = next(x for x in list if x.n == 10)
Oliver Breeden
źródło