web-dev-qa-db-fra.com

Pourquoi n'y a-t-il pas de première fonction intégrée (itérable) en Python?

Je me demande s'il y a une raison pour laquelle il n'y a pas de first(iterable) dans les fonctions intégrées Python, quelque peu similaire à any(iterable) et all(iterable) (il peut être caché dans un module stdlib quelque part, mais je ne le vois pas dans itertools). first effectuerait une évaluation du générateur de court-circuit de sorte que inutile (et un un nombre potentiellement infini) d’opérations peut être évité, c’est-à-dire.

def identity(item):
    return item

def first(iterable, predicate=identity):
    for item in iterable:
        if predicate(item):
            return item
    raise ValueError('No satisfactory value found')

De cette façon, vous pouvez exprimer des choses comme:

denominators = (2, 3, 4, 5)
lcd = first(i for i in itertools.count(1)
    if all(i % denominators == 0 for denominator in denominators))

De toute évidence, vous ne pouvez pas faire list(generator)[0] dans ce cas, car le générateur ne se termine pas.

Ou si vous avez un tas de regex à comparer (utile quand ils ont tous la même interface groupdict):

match = first(regex.match(big_text) for regex in regexes)

Vous économisez beaucoup de traitement inutile en évitant list(generator)[0] et en court-circuitant sur une correspondance positive.

65
cdleary

Si vous avez un itérateur, vous pouvez simplement appeler sa méthode next. Quelque chose comme:

In [3]: (5*x for x in xrange(2,4)).next()
Out[3]: 10
47
liori

Il y a un paquet Pypi appelé "premier" qui fait ceci:

>>> from first import first
>>> first([0, None, False, [], (), 42])
42

Voici comment utiliser pour renvoyer le premier nombre impair, par exemple:

>> first([2, 14, 7, 41, 53], key=lambda x: x % 2 == 1)
7

Si vous souhaitez simplement renvoyer le premier élément de l'itérateur, qu'il soit vrai ou non, procédez comme suit:

>>> first([0, None, False, [], (), 42], key=lambda x: True)
0

C'est un tout petit paquet: il ne contient que cette fonction, il n'a pas de dépendances, et il fonctionne sur Python 2 et 3. C'est un seul fichier, donc vous n'avez même pas besoin de l'installer pour l'utiliser.

En fait, voici presque tout le code source (de la version 2.0.1, par Hynek Schlawack, publié sous la licence MIT license):

def first(iterable, default=None, key=None):
    if key is None:
        for el in iterable:
            if el:
                return el
    else:
        for el in iterable:
            if key(el):
                return el
    return default
13
Flimm

J'ai posé une question similaire récemment (il a été marqué comme doublon de cette question maintenant). Ma préoccupation était également que j'aurais aimé utiliser les fonctions intégrées uniquement pour résoudre le problème de la recherche de la première vraie valeur d'un générateur. Ma propre solution était alors la suivante:

x = next((v for v in (f(x) for x in a) if v), False)

Pour l'exemple de recherche de la première correspondance d'expression rationnelle (pas le premier modèle de correspondance!), Cela ressemblerait à ceci:

patterns = [ r'\d+', r'\s+', r'\w+', r'.*' ]
text = 'abc'
firstMatch = next(
  (match for match in
    (re.match(pattern, text) for pattern in patterns)
   if match),
  False)

Il n'évalue pas le prédicat deux fois (comme vous auriez à le faire si seulement le modèle était retourné) et il n'utilise pas de hacks comme les locaux dans les compréhensions.

Mais il a deux générateurs imbriqués où la logique dicterait d'en utiliser un seul. Une meilleure solution serait donc Nice.

10
Alfe

Il y a un itérateur "slice" dans itertools. Il émule les opérations de tranche que nous connaissons en python. Ce que vous recherchez est quelque chose de similaire à ceci:

myList = [0,1,2,3,4,5]
firstValue = myList[:1]

L'équivalent utilisant itertools pour les itérateurs:

from itertools import islice
def MyGenFunc():
    for i in range(5):
        yield i

mygen = MyGenFunc()
firstValue = islice(mygen, 0, 1)
print firstValue 
6
Zoran Pavlovic

Il y a une certaine ambiguïté dans votre question. Votre définition de first et l'exemple regex impliquent qu'il existe un test booléen. Mais l'exemple des dénominateurs a explicitement une clause if; donc ce n'est qu'une coïncidence que chaque entier soit vrai.

Il ressemble à la combinaison de next et itertools.ifilter vous donnera ce que vous voulez.

match = next(itertools.ifilter(None, (regex.match(big_text) for regex in regexes)))
6
A. Coady

Haskell utilise ce que vous venez de décrire, comme la fonction take (ou comme la fonction partielle take 1, techniquement). Python Cookbook a écrit des wrappers de générateur qui exécutent les mêmes fonctionnalités que take, takeWhile et drop dans Haskell.

Mais pour savoir pourquoi ce n'est pas intégré, votre supposition est aussi bonne que la mienne.

4
Mark Rushakoff