Filtrage de listes en Python

Je veux faire une correspondance de modèle sur les listes en Python. Par exemple, dans Haskell, je peux faire quelque chose comme ceci:

fun (head : rest) = ...

, Donc quand je passe dans une liste, head sera le premier élément, et rest sera la fuite des éléments.

De même, en Python, je peux décompresser automatiquement les tuples:

(var1, var2) = func_that_returns_a_tuple()

Je veux faire quelque chose de similaire avec des listes en Python. En ce moment, j'ai une fonction qui renvoie une liste, et un morceau de code qui fait ce qui suit:

ls = my_func()
(head, rest) = (ls[0], ls[1:])

Je je me demandais si je pouvais en quelque sorte le faire en une ligne en Python, au lieu de deux.

35
demandé sur abatishchev 2008-10-26 17:50:40

10 réponses

Pour autant que je sache, il n'y a aucun moyen d'en faire un one-liner dans Python actuel sans introduire une autre fonction, par exemple:

split_list = lambda lst: (lst[0], lst[1:])
head, rest = split_list(my_func())

Cependant, dans Python 3.0, la syntaxe spécialisée utilisée pour les signatures d'arguments variadiques et le déballage d'arguments deviendra également disponible pour ce type de déballage de séquence générale, donc dans 3.0 vous pourrez écrire:

head, *rest = my_func()

Voir PEP 3132 pour plus de détails.

59
répondu James Bennett 2015-07-17 19:26:01

Tout d'abord, veuillez noter que le "pattern matching" des langages fonctionnels et l'affectation aux tuples que vous mentionnez ne sont pas vraiment similaires. Dans les langages fonctionnels, les motifs sont utilisés pour donner des définitions partielles d'une fonction. Donc f (x : s) = e ne veut pas dire prendre la tête et la queue de l'argument de f et retour e utiliser, mais cela signifie que si l'argument de f est de la forme x : s (pour certains x et s), alors f (x : s) est égal à e.

L'affectation de python ressemble plus à une affectation multiple (je soupçonne que c'était son intention initiale). Si vous écrivez, par exemple, x, y = y, x pour permuter les valeurs de x et y sans avoir besoin d'une variable temporaire (comme vous le feriez avec une simple instruction d'affectation). Cela a peu à voir avec la correspondance de motifs car il s'agit essentiellement d'un raccourci pour l'exécution "simultanée" de x = y et y = x. Bien que python autorise des séquences arbitraires au lieu de listes séparées par des virgules, Je ne suggérerait pas d'appeler cette correspondance de modèle. Avec pattern matching, vous vérifiez si quelque chose correspond ou non à un pattern; dans l'affectation python, vous devez vous assurer que les séquences des deux côtés sont les mêmes.

Pour faire ce que vous semblez vouloir, vous utiliseriez généralement (également dans les langages fonctionnels) une fonction auxiliaire (comme mentionné par d'autres) ou quelque chose de similaire aux constructions let ou where (que vous pouvez considérer comme utilisant des fonctions anonymes). Pour exemple:

(head, tail) = (x[0], x[1:]) where x = my_func()

Ou, en Python réel:

(head, tail) = (lambda x: (x[0], x[1:]))(my_func())

Notez que c'est essentiellement la même chose que les solutions données par d'autres avec une fonction auxiliaire sauf que c'est le One-liner que vous vouliez. Il est, cependant, pas nécessairement mieux qu'une fonction distincte.

(désolé si ma réponse est un peu exagérée. Je pense juste qu'il est important de faire la distinction claire.)

32
répondu mweerden 2008-10-26 16:05:12

C'est une approche très "fonctionnelle pure" et en tant que telle est un idiome sensible dans Haskell mais ce n'est probablement pas si approprié pour Python. Python n'a qu'un concept très limité de patterns de cette façon-et je soupçonne que vous pourriez avoir besoin d'un système de type un peu plus rigide pour implémenter ce genre de construction (Erlang buffs invités à ne pas être d'accord ici).

Ce que vous avez est probablement aussi proche que vous obtiendrez à cet idiome, mais vous êtes probablement mieux d'utiliser une liste compréhension ou approche impérative plutôt que d'appeler récursivement une fonction avec la queue de la liste.

Que a a déclaré à quelques reprises avant de, Python n'est pas réellement un langage fonctionnel. Il emprunte juste des idées du monde FP. Ce N'est pas intrinsèquement queue récursive de la façon dont vous vous attendez à voir intégré dans l'architecture d'un langage fonctionnel, donc vous auriez du mal à faire ce genre d'opération récursive sur un grand ensemble de données sans utiliser beaucoup d'espace de pile.

4
répondu ConcernedOfTunbridgeWells 2008-10-26 17:44:08

Contrairement à Haskell ou ML, Python n'a pas de correspondance de modèles de structures intégrée. La façon la plus pythonique de faire la correspondance des modèles est avec un bloc try-except:

def recursive_sum(x):
    try:
        head, tail = x[0], x[1:]
        return head + recursive-sum(tail)
    except IndexError:  # empty list: [][0] raises IndexError
        return 0

Notez que cela ne fonctionne qu'avec des objets avec indexation de tranche. De plus, si la fonction se complique, quelque chose dans le corps après la ligne head, tail peut déclencher IndexError, ce qui entraînera des bugs subtils. Cependant, cela vous permet de faire des choses comme:

for frob in eggs.frob_list:
    try:
        frob.spam += 1
    except AttributeError:
        eggs.no_spam_count += 1

En Python, la récursivité de la queue est généralement mieux implémenté comme une boucle avec un accumulateur, c'est-à-dire:

def iterative_sum(x):
    ret_val = 0
    for i in x:
        ret_val += i
    return ret_val

C'est la seule façon évidente et correcte de le faire 99% du temps. Non seulement il est plus clair à lire, c'est plus rapide et cela fonctionnera sur d'autres choses que les listes (ensembles, par exemple). S'il y a une exception qui attend de se produire là-dedans, la fonction échouera heureusement et la livrera dans la chaîne.

3
répondu giltay 2008-10-27 14:15:34

Eh Bien, pourquoi vous le voulez en 1-en premier lieu?

Si vous voulez vraiment, vous pouvez toujours faire un tour comme ceci:

def x(func):
  y = func()
  return y[0], y[1:]

# then, instead of calling my_func() call x(my_func)
(head, rest) = x(my_func) # that's one line :)
2
répondu kender 2008-10-26 15:03:08

Le déballage étendu A été introduit dans 3.0 http://www.python.org/dev/peps/pep-3132/

2
répondu 2008-10-26 15:40:59

Suite aux autres réponses, notez que l'opération head / tail équivalente en Python, y compris l'extension de la syntaxe * de python3, sera généralement moins efficace que la correspondance de modèle de Haskell.

Les listes Python sont implémentées en tant que vecteurs, donc l'obtention de la queue devra prendre une copie de la liste. C'est O (n) wrt la taille de la liste, alors qu'une implémentation utilisant des listes liées comme Haskell peut simplement utiliser le pointeur de queue, une opération O(1).

Le seul l'exception peut être des approches basées sur un itérateur, où la liste n'est pas réellement retournée, mais un itérateur l'est. Cependant, cela peut ne pas être applicable tous les endroits où une liste est souhaitée (par exemple. itérer plusieurs fois).

Par exemple, l'approche du chiffrement, si elle est modifiée pour renvoyer l'itérateur plutôt que de le convertir en un tuple, aura ce comportement. Alternativement, une méthode 2-item seulement plus simple ne reposant pas sur le bytecode serait:

def head_tail(lst):
    it = iter(list)
    yield it.next()
    yield it

>>> a, tail = head_tail([1,2,3,4,5])
>>> b, tail = head_tail(tail)
>>> a,b,tail
(1, 2, <listiterator object at 0x2b1c810>)
>>> list(tail)
[3, 4]

Évidemment, bien que vous ayez encore à envelopper dans une fonction utilitaire plutôt que d'y avoir du sucre syntaxique agréable pour cela.

2
répondu Brian 2017-05-23 11:54:34

Je travaille sur pyfpm , une bibliothèque pour la correspondance de modèles en Python avec une syntaxe de type Scala. Vous pouvez l'utiliser pour décompresser des objets comme ceci:

from pyfpm import Unpacker

unpacker = Unpacker()

unpacker('head :: tail') << (1, 2, 3)

unpacker.head # 1
unpacker.tail # (2, 3)

Ou dans l'arglist d'une fonction:

from pyfpm import match_args

@match_args('head :: tail')
def f(head, tail):
    return (head, tail)

f(1)          # (1, ())
f(1, 2, 3, 4) # (1, (2, 3, 4))
2
répondu Martin Blech 2012-07-20 23:18:17

Il y avait un reciepe dans le livre de cuisine python pour le faire. Je ne peux pas sembler le trouver maintenant Mais voici le code (je l'ai légèrement modifié)


def peel(iterable,result=tuple):
    '''Removes the requested items from the iterable and stores the remaining in a tuple
    >>> x,y,z=peel('test')
    >>> print repr(x),repr(y),z
    't' 'e' ('s', 't')
    '''
    def how_many_unpacked():
        import inspect,opcode
        f = inspect.currentframe().f_back.f_back
        if ord(f.f_code.co_code[f.f_lasti])==opcode.opmap['UNPACK_SEQUENCE']:
            return ord(f.f_code.co_code[f.f_lasti+1])
        raise ValueError("Must be a generator on RHS of a multiple assignment!!")
    iterator=iter(iterable)
    hasItems=True
    amountToUnpack=how_many_unpacked()-1
    next=None
    for num in xrange(amountToUnpack):
        if hasItems:        
            try:
                next = iterator.next()
            except StopIteration:
                next = None
                hasItems = False
        yield next
    if hasItems:
        yield result(iterator)
    else:
        yield None

Cependant, vous devez noter que cela ne fonctionne que lors de l'utilisation d'un déballage d'affectation en raison de la façon dont il inestime l'image précédente... encore son très utile.

1
répondu Jake 2008-10-26 16:15:17

Pour votre cas d'utilisation spécifique-émuler fun (head : rest) = ... de Haskell, bien sûr. Les définitions de fonction ont pris en charge le déballage des paramètres pendant un certain temps:

def my_method(head, *rest):
    # ...

À partir de Python 3.0, comme @ bpowah mentionné , Python prend également en charge le déballage lors de l'affectation:

my_list = ['alpha', 'bravo', 'charlie', 'delta', 'echo']
head, *rest = my_list
assert head == 'alpha'
assert rest == ['bravo', 'charlie', 'delta', 'echo']

Notez que l'astérisque (le "splat") signifie "le reste de l'itérable", pas "jusqu'à la fin". Ce qui suit fonctionne bien:

first, *middle, last = my_list
assert first == 'alpha'
assert last == 'echo'
assert middle == ['bravo', 'charlie', 'delta']

first, *middle, last = ['alpha', 'bravo']
assert first == 'alpha'
assert last == 'bravo'
assert middle == []
0
répondu Lyndsy Simon 2018-08-29 16:31:58