Domanda

Voglio fare alcuni pattern matching sugli elenchi in Python. Ad esempio, in Haskell, posso fare qualcosa del tipo:

fun (head : rest) = ...

Quindi quando passo in un elenco, head sarà il primo elemento e rest saranno gli elementi finali.

Allo stesso modo, in Python, posso decomprimere automaticamente le tuple:

(var1, var2) = func_that_returns_a_tuple()

Voglio fare qualcosa di simile con le liste in Python. In questo momento, ho una funzione che restituisce un elenco e un pezzo di codice che procede come segue:

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

Mi chiedevo se potevo in qualche modo farlo in una riga in Python, anziché in due.

È stato utile?

Soluzione

Per quanto ne so, non c'è modo di renderlo un singolo elemento nell'attuale Python senza introdurre un'altra funzione, ad esempio:

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

Tuttavia, in Python 3.0 la sintassi specializzata utilizzata per le firme degli argomenti variadici e la decompressione degli argomenti diventerà disponibile anche per questo tipo di decompressione della sequenza generale, quindi in 3.0 sarai in grado di scrivere:

head, *rest = my_func()

Vedi PEP 3132 per dettagli.

Altri suggerimenti

Prima di tutto, tieni presente che la corrispondenza del modello " " dei linguaggi funzionali e l'assegnazione alle tuple che menzioni non sono poi così simili. Nei linguaggi funzionali gli schemi sono usati per dare definizioni parziali di una funzione. Quindi f (x: s) = e non significa prendere la testa e la coda dell'argomento di f e restituire e usandoli, ma significa che if l'argomento di f ha la forma x: s (per alcuni x e s ), quindi f (x: s) è uguale a e .

L'assegnazione di Python è più simile a un'assegnazione multipla (sospetto che fosse la sua intenzione originale). Quindi scrivi, ad esempio, x, y = y, x per scambiare i valori in x e y senza bisogno di una variabile temporanea (come lo faresti con una semplice dichiarazione di incarico). Questo ha poco a che fare con la corrispondenza dei modelli in quanto è sostanzialmente una scorciatoia per la "simultanea" esecuzione di x = y e y = x . Sebbene Python consenta sequenze arbitrarie anziché elenchi separati da virgole, non suggerirei di chiamare questo pattern matching. Con la corrispondenza dei motivi controlli se qualcosa corrisponde o meno a un motivo; nell'assegnazione python dovresti assicurarti che le sequenze su entrambi i lati siano uguali.

Per fare ciò che sembri desiderare di solito (anche nei linguaggi funzionali) utilizzare una funzione ausiliaria (come menzionato da altri) o qualcosa di simile a let o dove costrutti (che puoi considerare come usare funzioni anonime). Ad esempio:

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

Oppure, in vero pitone:

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

Nota che questo è essenzialmente lo stesso delle soluzioni fornite da altri con una funzione ausiliaria tranne per il fatto che questa è la linea che volevi. Tuttavia, non è necessariamente migliore di una funzione separata.

(Mi dispiace se la mia risposta è un po 'esagerata. Penso solo che sia importante chiarire la distinzione.)

Questo è un approccio "puro funzionale" e come tale è un linguaggio sensato in Haskell, ma probabilmente non è così appropriato per Python. Python ha solo un concetto molto limitato di pattern in questo modo - e sospetto che potresti bisogno di un sistema di tipo un po 'più rigido per implementare quel tipo di costrutto ( erlang gli appassionati invitati a non essere d'accordo qui).

Quello che hai è probabilmente il più vicino possibile a quel linguaggio, ma probabilmente stai meglio usando una comprensione dell'elenco o un approccio imperativo piuttosto che chiamare ricorsivamente una funzione con la coda dell'elenco.

Come è stato dichiarato in alcune occasioni prima , Python non è in realtà un linguaggio funzionale. Prende semplicemente in prestito idee dal mondo FP. Non è intrinsecamente Tail Recursive nel modo in cui ti aspetteresti di vedere incorporato nell'architettura di un linguaggio funzionale, quindi avresti qualche difficoltà a fare questo tipo di operazione ricorsiva su un set di dati di grandi dimensioni senza usare molto spazio nello stack.

la decompressione estesa è stata introdotta in 3.0 http://www.python.org/dev/peps/pep-3132/

A differenza di Haskell o ML, Python non ha un pattern-matching incorporato delle strutture. Il modo più Pythonic di fare il pattern matching è con un blocco try-tranne:

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

Nota che funziona solo con oggetti con indicizzazione delle sezioni. Inoltre, se la funzione diventa complicata, qualcosa nel corpo dopo la riga testa, coda potrebbe sollevare IndexError, il che porterà a bug sottili. Tuttavia, questo ti consente di fare cose come:

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

In Python, la ricorsione della coda è generalmente meglio implementata come un ciclo con un accumulatore, ovvero:

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

Questo è l'unico modo ovvio e giusto per farlo il 99% delle volte. Non solo è più chiaro da leggere, è più veloce e funzionerà su cose diverse dalle liste (set, per esempio). Se c'è un'eccezione in attesa che accada, la funzione fallirà felicemente e la consegnerà alla catena.

Sto lavorando su pyfpm , una libreria per la corrispondenza dei pattern in Python con una sintassi simile a Scala . Puoi usarlo per decomprimere oggetti in questo modo:

from pyfpm import Unpacker

unpacker = Unpacker()

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

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

O nell'arlista di una funzione:

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

Beh, perché lo vuoi in 1 riga in primo luogo?

Se vuoi davvero, puoi sempre fare un trucco come questo:

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

Oltre alle altre risposte, si noti che l'operazione testa / coda equivalente in Python, inclusa l'estensione della sintassi * di python3, sarà generalmente meno efficiente della corrispondenza del modello di Haskell.

Gli elenchi di Python sono implementati come vettori, quindi per ottenere la coda dovrai prendere una copia dell'elenco. Questo è O (n) wrt la dimensione della lista, mentre un implementaion che usa liste collegate come Haskell può semplicemente usare il puntatore di coda, un'operazione O (1).

L'unica eccezione potrebbe essere rappresentata dagli approcci basati sull'iteratore, in cui l'elenco non viene effettivamente restituito, ma è un iteratore. Tuttavia, ciò potrebbe non essere applicabile in tutti i luoghi in cui si desidera un elenco (ad es. Ripetendo più volte).

Ad esempio, l'approccio Cipher , se modificato per restituire il metodo iteratore anziché convertirlo in una tupla avrà questo comportamento. In alternativa, un metodo a 2 elementi più semplice che non si basa sul bytecode sarebbe:

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]

Ovviamente devi comunque avvolgere una funzione di utilità piuttosto che esserci un buon zucchero sintattico per essa.

c'era una ricevuta nel ricettario di Python per farlo. non riesco a trovarlo ora ma ecco il codice (l'ho modificato leggermente)


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

tuttavia dovresti notare che funziona solo quando usi un compito unpack a causa del modo in cui integra il frame precedente ... è comunque abbastanza utile.

Per il tuo caso d'uso specifico: emulare il divertimento di Haskell (head: rest) = ... , certo. Le definizioni delle funzioni supportano la decompressione dei parametri da un bel po 'di tempo:

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

A partire da Python 3.0, come @bpowah menzionato , Python supporta anche il disimballaggio durante l'assegnazione:

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

Nota che l'asterisco (il simbolo "splat") significa "il resto dell'iterabile", non "fino alla fine". Il seguente funziona bene:

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 == []
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top