web-dev-qa-db-fra.com

Correspondance de modèle de listes en Python

Je veux faire des correspondances de modèle sur des listes en Python. Par exemple, dans Haskell, je peux faire quelque chose comme ce qui suit:

fun (head : rest) = ...

Ainsi, lorsque je passe dans une liste, head sera le premier élément et rest sera le dernier élément.

De même, en Python, je peux décompresser automatiquement les n-uplets:

(var1, var2) = func_that_returns_a_Tuple()

Je veux faire quelque chose de similaire avec les listes en Python. À l'heure actuelle, j'ai une fonction qui renvoie une liste et un morceau de code qui effectue les opérations suivantes:

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

Je me demandais si je pouvais d'une manière ou d'une autre faire cela en une ligne en Python, au lieu de deux.

37
mipadi

Pour autant que je sache, il n’est pas possible d’en faire un one-line 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 la décompression d’arguments deviendront disponibles pour ce type de décompression de séquence générale. Vous pourrez donc écrire en 3.0:

head, *rest = my_func()

Voir PEP 3132 pour plus de détails.

59
James Bennett

Tout d’abord, veuillez noter que le "filtrage par motif" des langages fonctionnels et l’affectation aux n-uplets que vous mentionnez ne sont pas vraiment similaires. Dans les langages fonctionnels, les modèles sont utilisés pour donner des définitions partielles d'une fonction. Donc, f (x : s) = e ne signifie pas prendre la tête et la queue de l'argument de f et renvoyer e en les utilisant, mais cela signifie que if l'argument de f est de la forme x : s (pour certains x et s), then f (x : s) est égal à e.

L'affectation de python s'apparente davantage à une assignation multiple (je suppose que c'était son intention initiale). Ainsi, vous écrivez, par exemple, x, y = y, x pour échanger les valeurs dans 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 recherche de modèle car il s’agit en réalité 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 conseillerais pas d'appeler cette correspondance de modèle. Avec correspondance de modèle, vous vérifiez si quelque chose correspond à un modèle; dans l'affectation python, vous devez vous assurer que les séquences des deux côtés sont identiques.

Pour faire ce que vous semblez vouloir, vous utiliserez 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). Par 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 qu’il s’agit essentiellement des solutions proposées par d’autres avec une fonction auxiliaire, à la différence que c’est le one-liner que vous vouliez. Ce n'est cependant pas nécessairement mieux qu'une fonction séparée.

(Désolé si ma réponse est un peu exagérée. Je pense simplement qu'il est important de bien préciser la distinction.)

32
mweerden

C’est une approche très fonctionnelle et, en tant que telle, est un idiome sensé en Haskell, mais elle n’est probablement pas aussi appropriée pour Python. Python a seulement un concept très limité de patterns de cette façon - et je suppose que vous aurez peut-être besoin d'un système de types un peu plus rigide pour implémenter ce type de construction ( erlang buffs invités à ne pas être d'accord ici. ).

Ce que vous avez est probablement aussi proche que possible de cet idiome, mais vous feriez mieux d'utiliser une compréhension de la liste ou une approche impérative plutôt que d'appeler de manière récursive une fonction avec la queue de la liste. 

Comme il a été déclaréà quelques reprisesavant , Python n'est pas réellement un langage fonctionnel. Il n’emprunte que des idées du monde FP. Ce n'est pas intrinsèquement Tail Recursive de la façon dont vous vous attendez à être intégré dans l'architecture d'un langage fonctionnel. Vous auriez donc de la difficulté à effectuer ce type d'opération récursive sur un jeu de données volumineux sans utiliser beaucoup de l'espace de pile.

la décompression étendue a été introduite dans la version 3.0 http://www.python.org/dev/peps/pep-3132/

3
bpowah

Contrairement à Haskell ou à ML, Python ne possède pas de correspondance de motif intégrée à ses structures. La manière la plus pythonique de faire correspondre les motifs 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 une indexation par tranches. De plus, si la fonction devient compliquée, quelque chose dans le corps après la ligne head, tail peut générer IndexError, ce qui conduira à des bogues 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écursion de la queue est généralement mieux implémentée sous forme de boucle avec un accumulateur, c.-à-d.

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

C'est la seule façon évidente de le faire 99% du temps. Non seulement la lecture est plus claire, mais elle est plus rapide et fonctionne sur des éléments autres que les listes (ensembles, par exemple). S'il y a une exception qui attend, la fonction échouera volontiers et la transmettra dans la chaîne.

3
giltay

Je travaille sur pyfpm , une bibliothèque de correspondance de modèles en Python avec une syntaxe similaire à 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))
3
Martin Blech

Eh bien, pourquoi vous le voulez en 1 ligne en premier lieu? 

Si vous voulez vraiment, vous pouvez toujours faire une astuce comme celle-ci:

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
kender

En plus des autres réponses, notez que l’opération head/tail équivalente en Python, y compris l’extension de la syntaxe * par python3, sera généralement moins efficace que le filtrage par masque de Haskell.

Les listes Python étant implémentées en tant que vecteurs, l'obtention de la queue nécessitera une copie de la liste. Ceci est O(n) par rapport à la taille de la liste, alors qu'une implémentation utilisant des listes chaînées comme Haskell peut simplement utiliser le pointeur arrière, une opération O(1).

La seule exception peut être les approches basées sur un itérateur, où la liste n'est pas renvoyée, mais un itérateur. Cependant, cela peut ne pas s'appliquer à tous les endroits où une liste est souhaitée (par exemple, itérer plusieurs fois).

Par exemple, l'approche de Cipher , si elle est modifiée pour renvoyer l'itérateur au lieu de la convertir en tuple, aura ce comportement. Alternativement, une méthode plus simple comportant uniquement deux éléments ne faisant pas appel au 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]

Bien évidemment, vous devez encore intégrer une fonction d’utilité plutôt qu’il existe un sucre syntaxique de Nice.

2
Brian

il y avait un recette dans le livre de cuisine en 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écompactage d'affectation en raison de la façon dont il ne respecte pas le cadre précédent ... toujours utile.

1
Jake

Pour votre cas d'utilisation spécifique - émuler la fonction fun (head : rest) = ... de Haskell, bien sûr. Les définitions de fonction ont supporté la décompression des paramètres depuis un certain temps:

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

À partir de Python 3.0, comme indiqué par @bpowah mentionne , Python prend également en charge le déballage des tâches:

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
Lyndsy Simon