Question

Je souhaite effectuer 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 reste sera les éléments suivants.

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 retourne une liste et un morceau de code qui fait ce qui suit:

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

Je me suis demandé si je pouvais le faire en une ligne en Python, au lieu de deux.

Était-ce utile?

La solution

Pour autant que je sache, il est impossible d'en faire un one-liner dans le 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écompactage d’arguments deviendront disponibles pour ce type de décompression de séquence générale. Ainsi, dans la version 3.0, vous pourrez écrire:

head, *rest = my_func()

Voir PEP 3132 pour plus de détails.

Autres conseils

Tout d’abord, veuillez noter que le " pattern matching " des langages fonctionnels et l’attribution aux tuples 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 en tête et fin de l'argument de f et renvoyer e en les utilisant, mais cela signifie que si l'argument de f est de la forme x: s (pour certains x et s ), puis f (x: s) est égal à e .

L'affectation de python s'apparente davantage à une affectation multiple (je suppose que c'était son intention initiale). Ainsi, vous écrivez, par exemple, x, y = y, x pour permuter les valeurs de x et de y sans nécessiter de variable temporaire (comme vous le feriez avec une simple déclaration de mission). Cela a peu à voir avec la correspondance des modèles car il s’agit en réalité d’un raccourci pour le paramètre "simultané". exécution de x = y et de 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 utiliseriez habituellement (également dans les langages fonctionnels) une fonction auxiliaire (comme mentionné par d'autres) ou quelque chose de similaire à let ou des constructions (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 que c’est essentiellement la même chose que les 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 distincte.

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

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 n'a qu'un concept très limité de motifs de cette manière - et je suppose que vous pourriez le faire. besoin d’un système de types un peu plus rigide pour mettre en oeuvre ce type de construction (les erlang invitent à ne pas être d'accord ici).

Ce que vous avez est probablement aussi proche de cet idiome, mais vous feriez mieux d'utiliser une compréhension de 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 cela a été déclaré à quelques reprises avant , Python n’est pas réellement un langage fonctionnel. Il emprunte juste des idées du monde de la PF. Tail Récursive n'est pas inhérent à la manière dont vous vous attendriez à être intégrée à l'architecture d'un fonctionnel, de sorte que vous auriez de la difficulté à effectuer ce type d’opération récursive sur un grand ensemble de données sans utiliser beaucoup d’espace de pile.

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

Contrairement à Haskell ou à ML, Python ne possède pas de correspondance de motif intégrée aux 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 les objets avec une indexation par tranche. De plus, si la fonction devient compliquée, quelque chose dans le corps après la ligne tête, queue peut générer IndexError, ce qui entraînera 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 en tant que 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 appropriée 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.

Je travaille sur la 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))

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

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. C’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 concerne les approches basées sur un itérateur, dans lesquelles la liste n'est pas renvoyée, mais un itérateur. Toutefois, 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 du code , si modifiée pour renvoyer le itérateur plutôt que de le convertir en un tuple aura ce comportement. Sinon, 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 que vous ayez encore à envelopper une fonction d’utilité plutôt qu’un bon sucre syntaxique,

il y avait une 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'une décompression d'assignation en raison de la façon dont elle ne respecte pas la trame précédente ... toujours très utile.

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

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

À partir de Python 3.0, comme @bpowah mentionné , 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 == []
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top