Rechercher un objet dans la liste dont l'attribut est égal à une valeur (qui remplit n'importe quelle condition)


221

J'ai une liste d'objets. Je veux trouver un (premier ou autre) objet dans cette liste dont l'attribut (ou le résultat de la méthode - peu importe) est égal à value.

Quelle est la meilleure façon de le trouver?

Voici le cas de test:

  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

Je pense qu'en utilisant des générateurs et reduce() ne fera aucune différence, car il serait toujours en train de parcourir la liste.

ps .: L'équation de valuen'est qu'un exemple. Bien sûr, nous voulons obtenir un élément qui répond à toutes les conditions.


2
Voici une bonne discussion de cette question: tomayko.com/writings/cleanest-python-find-in-list-function
Andrew Hare

Le message d'origine est ridiculement obsolète, mais la 2e réponse correspond exactement à ma version à une ligne. Je ne suis pas convaincu que ce soit mieux que la version de boucle de base.
agf

Réponses:


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

Cela obtient le premier élément de la liste qui correspond à la condition et retourne Nonesi aucun élément ne correspond. C'est ma forme d'expression unique préférée.

cependant,

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

La version naïve de rupture de boucle, est parfaitement Pythonic - elle est concise, claire et efficace. Pour l'adapter au comportement du monoplace:

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

Cela sera attribué Noneà xsi vous ne breaksortez pas de la boucle.


72
+1 pour le rassurant "La version naïve du break-break, est parfaitement Pythonique".
LaundroMat

excellente solution, mais comment puis-je modifier votre ligne afin que je puisse faire x.value signifie réellement x.fieldMemberName où ce nom est stocké en valeur? field = "name" next ((x pour x dans test_list if x.field == value), None) de sorte que dans ce cas, je vérifie réellement contre x.name, pas x.field
Stewart Dale

3
@StewartDale Ce n'est pas totalement clair ce que vous demandez, mais je pense que vous voulez dire ... if getattr(x, x.fieldMemberName) == value. Cela récupérera l'attribut xavec le nom stocké dans fieldMemberNameet le comparera à value.
agf

1
@ThatTechGuy - La elseclause est censée être sur la forboucle, pas la if. (Modification rejetée).
agf

1
@agf Wow, je n'avais littéralement aucune idée de ce qui existait. book.pythontips.com/en/latest/for_-_else.html cool!
ThatTechGuy

25

Puisqu'il n'a pas été mentionné juste pour l'achèvement. Le bon vieux filtre pour filtrer vos éléments à filtrer.

Programmation fonctionnelle 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.

Je sais qu'en général, dans les listes de python, les compréhensions sont préférées ou du moins c'est ce que je lis, mais je ne vois pas le problème pour être honnête. Bien sûr, Python n'est pas un langage FP, mais Map / Reduce / Filter sont parfaitement lisibles et sont le plus standard des cas d'utilisation standard en programmation fonctionnelle.

Alors voilà. Connaissez votre programmation fonctionnelle.

liste des conditions de filtrage

Ce ne sera pas plus simple que ça:

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

J'aime bien le style de cela, mais il y a deux problèmes potentiels. 1 : Il fonctionne uniquement en Python 3; en Python 2, filterretourne une liste qui n'est pas compatible avec next. 2 : il faut qu'il y ait une correspondance définitive, sinon vous obtiendrez une StopIterationexception.
freethebees

1
1: Je ne connais pas Python 2. Lorsque j'ai commencé à utiliser Python, Python 3 était déjà disponible. Malheureusement, je ne sais rien des spécifications de Python 2. 2. @freethebees comme l'a souligné agf. Vous pouvez utiliser next (..., None) ou une autre valeur par défaut, si vous n'êtes pas fan d'exceptions. Je l'ai également ajouté en tant que commentaire à mon code.
Nima Mousavi

@freethebees Point 2 pourrait en fait être bon. Lorsque j'ai besoin d'un certain objet dans une liste, échouer rapidement est une bonne chose.
kap

7

Un exemple simple : nous avons le tableau suivant

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

Maintenant, nous voulons trouver l'objet dans le tableau dont l'id est égal à 1

  1. Utiliser la méthode nextavec la compréhension de la liste
next(x for x in li if x["id"] == 1 )
  1. Utilisez la compréhension de la liste et renvoyez le premier élément
[x for x in li if x["id"] == 1 ][0]
  1. Fonction personnalisée
def find(arr , id):
    for x in arr:
        if x["id"] == id:
            return x
find(li , 1)

La sortie de toutes les méthodes ci-dessus est {'id': 1, 'name': 'ronaldo'}


1

Je viens de rencontrer un problème similaire et j'ai conçu une petite optimisation pour le cas où aucun objet de la liste ne répond à l'exigence (pour mon cas d'utilisation, cela a entraîné une amélioration des performances majeure):

Avec la liste test_list, je garde un ensemble supplémentaire test_value_set qui se compose des valeurs de la liste sur laquelle je dois filtrer. Ici, le reste de la solution d'agf devient très rapide.


1

Vous pourriez faire quelque chose comme ça

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

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

C'est ce que j'utilise pour trouver les objets dans un long tableau d'objets.


En quoi est-ce différent de ce que le questionneur a déjà essayé?
Anum Sheraz

Je voulais montrer comment il peut obtenir l'objet et le tableau d'objets de la manière la plus simple.
Illud

0

Vous pouvez également implémenter une comparaison riche via une __eq__méthode pour votre Testclasse et utiliser l' inopérateur. Je ne sais pas si c'est la meilleure façon autonome, mais au cas où vous auriez besoin de comparer des Testinstances basées valueailleurs, cela pourrait être utile.

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

Pour le code ci-dessous, xGen est une expression de générateur anonome, yFilt est un objet filtre. Notez que pour xGen, le paramètre supplémentaire None est renvoyé plutôt que de lancer StopIteration lorsque la liste est épuisée.

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

Production:

<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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.