FIRST !


Cherchant un post pas trop long à faire car j’ai été malade comme un chien depuis hier, je m’étais chauffé pour faire une intro au module array. Et puis, changeant d’avis par flemme, j’ouvre le code de batbelt, et je cherche un petit snippet que je n’ai pas présenté.

Bonne pêche !

Voici des fonctions qui s’utilisent sur des itérables, n’importe lequel, même un dont la taille est inconnu ou infini. La première retourne le premier élément, et, si l’itérable est vide, elle retourne une valeur par défaut :

def first(iterable, default=None):
    for x in iterable:
        return x
    return default

C’est une sorte de [0], mais valable pour tous les itérables, par juste les indexables comme les tuples, les strings ou les listes. Et en prime, pas besoin de faire un try / catch dessus puisqu’il permet une valeur par défaut :

>>> first([0, 1, 2, 3])
0
>>> first([], 'flammkuchen')
'flammkuchen'

Le second est aussi sympathique, il fait la même chose, mais retourne l’élément seulement si il est vrai :

def first_true(iterable, key=lambda x: x, default=None):
    for x in iterable:
        if key(x):
            return x
    return default

key fonctionne comme pour la fonction sorted(), à savoir que c’est une injection de dépendance. C’est cette fonction qui va déterminer si l’élément est vrai ou non. Par défaut la fonction retourne l’élément tel quel, et le fait qu’il soit vrai ou non sera donc déterminé par son contexte booléen :

>>> first_true([0, 1, 2, 3])
1
>>> first_true([(0, 1), (2, 3)])
(0, 1)
>>> first_true([(0, 1), (2, 3)], lambda x: x[0])
(2, 3)
>>> first_true([], lambda x: x[0], 'socca')
u'socca'

Ce petit article m’a fait réaliser qu’on pourrait sans problème fusionner les deux en faisant :

def first(iterable, key=lambda x: True, default=None):
    for x in iterable:
        if key(x):
            return x
    return default

Mais j’ai pas la foi de faire un commit ce soir, donc fuck.

9 thoughts on “FIRST !

  • Lyyn

    Oui, fallait qu’un débile le fasse. Pour en revenir à l’article, j’trouve ça cool ! Je testerai sans aucun doute d’ici peu :3

  • Fred

    Mouais. Pas convaincu de l’utilité profonde de ces fonctions.

    first=x[0] if len(x) else "autre chose"

    marche aussi bien…

    Mais c’est bien de faire ce genre d’article. Mieux vaut un article inutile de temps en temps que ne rien faire et laisser passer les trucs fabuleux que j’ai lu ici :)

  • Sam Post author

    x[0] va planter si l’indexable est vide. De plus, tu ne peux pas faire le “autre chose” facilement sur une ligne à par avec un iter(x).next() qui va lever stopiteration si l’itérable est vide. Et il faudra encore du code pour gérer la valeur par défaut.

    Ton cas ne gère pas non plus les hashable (dicos, counter), puisqu’ils ont une taille, mais pas d’ordre, et donc pas d’élément 0. Même si le cas est plus rare car on récupère pas souvent le premier élément d’un non ordonné, on perd donc en prime le duck typing dans le cas de traitement de séquences hétérogènes.

    Enfin ceci ne gère pas le cas de si on veut le premier élément vrai.

    Ce genre de fonction n’est pas indispensable, mais l’utilisation de nombreuses fonctions de ce genre amène à un code simple et élégant.

  • Fred

    Concernant le x[0] qui plante si l’itérable est vide je contredis violemment. Si l’itérable est vide, alors len(x) vaut 0 et dans ce cas c’est “autre chose” (par exemple ‘flammkuchen’) qui ressort.
    Effectivement la fonction marche pour un dico (et pas mon instruction) mais effectivement sortir le premier élément d’un “non ordonné” est un peu abscons.

    Sinon pour sortir le premier élément vrai

    first=[x for x in a if fct(x)][0] if len(a) else "flammkuchen"

    ;)
    Bon je chipote et j’en suis désolé. En plus s’il n’y a pas d’élément vrai ça renverra une exception mais sincèrement je ne vois pas l’utilité de ces fonctions. C’est vrai que être atomique c’est bien mais attention à la démesure (le “trop” est l’ennemi du bien). S’il commence à y avoir trop de fonctions de ce genre, le code “simple et élégant” rebascule dans un code “fouillis” (ah zut, à quoi sert déjà la fonction first_true” ? et “second_false” ? et “third_any_else” ? etc etc etc…)

  • Sam Post author

    Un indexable peut avoir un élément et pas de longeur : c’est le cas de tout objet custo qui définie un index mais pas de methode __len__.

    Mais je crois que le plus gros problème dans ton code, c’est qu’il ne gère pas tous les itérables : il ne gère que les itérables qui ont __len__. Donc il ne gère aucun stream (IOstream, ByteStream, File like objects) et il ne gère aucun objet dont la taille ne peut être définie à l’avance (expression génératrices, générateurs crées avec yield).

    Dans le monde de Python, où tout est itérable, et où tout fonctionne par duck typing, ce sont de grosses lacunes.

  • glickind

    corrections:
    s/La première retourne le premier élément, et, si c’est l’itérable est vide, il retourne une valeur par défaut/La première retourne le premier élément, et, si l’itérable est vide, elle retourne une valeur par défaut/

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.