2008-10-26 22 views
34

Ich möchte einige Mustervergleiche für Listen in Python durchführen. Zum Beispiel in Haskell, kann ich so etwas wie die folgenden tun:Mustererkennung von Listen in Python

fun (head : rest) = ... 

Also, wenn ich in einer Liste übergeben, head wird das erste Element sein, und rest wird die hinteren Elemente sein.

Ebenso in Python, ich kann automatisch Tupel auszupacken:

(var1, var2) = func_that_returns_a_tuple() 

ich etwas ähnliches mit Listen in Python tun möchten. Gerade jetzt, ich habe eine Funktion, die eine Liste zurückgibt, und ein Stück Code, die die folgenden:

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

Ich fragte mich, ob ich irgendwie, dass in einer Zeile in Python tun könnte, statt zwei.

Antwort

55

So weit ich weiß, gibt es keine Möglichkeit, es einen Einzeiler in aktuellem Python zu stellen, ohne ein weitere Funktion zur Einführung, zum Beispiel:

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

jedoch in Python 3.0 die spezielle Syntax variadische Argument Signaturen und Argument auspacken wird für diese Art der allgemeinen Sequenz als auch das Auspacken verfügbar werden, so in 3.0 können Sie schreiben:

head, *rest = my_func() 

Siehe PEP 3132 für weitere Einzelheiten.

+0

Natürlich können Sie diese Lambda auf der gleichen Linie mit allem anderen setzen kann: Kopf, Rest = (lambda lst: (lst [0], lst [1:])) (my_func()) –

+11

Ja, aber das beginnt an der Verschleierung zu beginnen. –

+1

+1 für die neue Funktion Python 3 und Verknüpfung mit dem PEP. – fossilet

4

Das ist ein sehr "rein funktionaler" Ansatz und als solcher ist eine vernünftige Sprache in Haskell, aber es ist wahrscheinlich nicht so passend zu Python. Python hat nur ein sehr begrenztes Konzept von patterns auf diese Weise - und ich vermute, dass Sie vielleicht ein etwas starreres System benötigen, um diese Art von Konstrukt zu implementieren (erlang Buffs eingeladen, hier zu widersprechen).

Was Sie haben, ist wahrscheinlich so nah wie Sie zu diesem Idiom, aber Sie sind wahrscheinlich besser dran mit einem Listenverständnis oder imperativen Ansatz statt rekursiv aufrufen eine Funktion mit dem Ende der Liste.

Wie schon statedon a few occasionsbefore ist Python keine funktionale Sprache. Es entlehnt nur Ideen aus der FP-Welt. Es ist nicht inhärent Tail Recursive in der Art und Weise, wie Sie erwarten würden, in die Architektur einer funktionalen Sprache eingebettet zu sein, so dass Sie Schwierigkeiten haben würden, diese Art von rekursiver Operation auf einem großen Datensatz ohne viel Stapelspeicher zu verwenden.

2

Nun, warum Sie es in erster Linie in 1-Linie wollen?

Wenn Sie wirklich wollen, können Sie immer einen Trick wie folgt tun:

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

Meistens weil - vorausgesetzt, es gibt eine "nette" Syntax - es wäre einfacher zu lesen. – mipadi

+0

Auch weil 'Kopf' und 'Schwanz' sind Standardlisten Idiome. Sie werden in allen Datenstrukturen und Algorithmen aus einem bestimmten Grund verwendet. Python, das keine eingebaute 'head (my_list)' Funktion hat, ist das eigentliche Problem. –

32

Zunächst einmal eingeführt wurde, beachten Sie bitte, dass die „Pattern Matching“ von funktionalen Sprachen und die Zuordnung zu Tupeln, die Sie erwähnen, sind nicht wirklich so ähnlich. In funktionalen Sprachen werden die Muster verwendet, um partielle Definitionen einer Funktion zu geben.So f (x : s) = e bedeutet nicht, den Kopf nehmen und Schwanz des Arguments von f und zurück e sie verwenden, aber es bedeutet, dass wenn das Argument der f der Form ist x : s (für einige x und s), dannf (x : s) ist gleich e.

Die Zuordnung von Python ist eher wie eine Mehrfachbelegung (ich vermute, dass das seine ursprüngliche Absicht war). Sie schreiben also beispielsweise x, y = y, x, um die Werte in x und y zu tauschen, ohne eine temporäre Variable zu benötigen (wie bei einer einfachen Zuweisungsanweisung). Dies hat wenig mit dem Mustervergleich zu tun, da es im Grunde eine Kurzform für die "gleichzeitige" Ausführung von x = y und y = x ist. Obwohl Python beliebige Sequenzen anstelle von durch Kommas getrennten Listen erlaubt, würde ich nicht empfehlen, dieses Muster als übereinstimmend zu bezeichnen. Bei der Mustererkennung prüfen Sie, ob etwas mit einem Muster übereinstimmt oder nicht; In der Python-Zuweisung sollten Sie sicherstellen, dass die Sequenzen auf beiden Seiten gleich sind.

zu tun, was Sie scheinen Sie in der Regel (in funktionalen Sprachen) entweder eine Hilfsfunktion verwenden würde zu wollen (wie von anderen erwähnt) oder etwas ähnliches zu let oder where Konstrukte (die Sie als mit einem anonymen Funktionen betrachten kann). Zum Beispiel:

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

Oder in tatsächlichem Python:

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

Beachten Sie, dass dies im Wesentlichen der gleichen wie die von anderen mit einer Hilfsfunktion, außer dass diese gegebenen Lösungen ist der Einzeiler wollten Sie . Es ist jedoch nicht unbedingt besser als eine separate Funktion.

(Sorry, wenn meine Antwort ein wenig übertrieben ist. Ich denke einfach, ist es wichtig, den Unterschied deutlich zu machen.)

1

gibt es eine reciepe in dem Python-Kochbuch, dies zu tun. ich kann es jetzt scheinen zu finden, aber hier ist der Code (i es leicht modifizierte)


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 

aber Sie sollten beachten, dass die nur funktionieren, wenn wegen der Art und Weise einer Zuordnung auspacken mit dem vorherigen Rahmen inespects ... immer noch ziemlich nützlich.

2

Weiter zu den anderen Antworten, beachten Sie, dass die äquivalente head/tail Operation in Python, einschließlich python3's Erweiterung der * Syntax, im Allgemeinen weniger effizient sein wird als Haskells Mustervergleich.

Python-Listen werden als Vektoren implementiert. Um den Tail zu erhalten, muss eine Kopie der Liste erstellt werden. Dies ist O (n) in Bezug auf die Größe der Liste, während eine Implementierung, die verkettete Listen wie Haskell verwendet, lediglich den Tail-Zeiger, eine O (1) -Operation, verwenden kann.

Die einzige Ausnahme können iteratorbasierte Ansätze sein, bei denen die Liste nicht tatsächlich zurückgegeben wird, sondern ein Iterator. Dies ist jedoch möglicherweise nicht überall anwendbar, wo eine Liste gewünscht wird (z. B. mehrere Wiederholungen).

Zum Beispiel, Cipher's Ansatz, wenn geändert, um den Iterator zurückzugeben, anstatt es in ein Tupel zu konvertieren, wird dieses Verhalten haben.Alternativ kann eine einfache 2-Punkt einzige Methode nicht auf dem Bytecode unter Berufung wäre:

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] 

Offensichtlich wenn Sie noch in einer Nutzenfunktion zu wickeln haben, anstatt es schön syntaktischer Zucker für sie zu sein.

2

Im Gegensatz zu Haskell oder ML verfügt Python über keinen integrierten Mustervergleich von Strukturen. Die Pythonic Art und Weise zu tun, Pattern-Matching ist mit einem try-except-Block:

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 

Beachten Sie, dass dies nur bei Objekten mit Slice Indizierung funktioniert. Auch wenn die Funktion kompliziert wird, könnte etwas im Körper nach die head, tail Zeile IndexError auslösen, was zu subtilen Bugs führen wird. Dies ist jedoch Sie Dinge wie zu tun erlaubt:

In Python wird Endrekursion der Regel besser als eine Schleife mit einem Speicher implementiert, das heißt:

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

Dies ist eine offensichtliche, rechts 99% der Zeit. Es ist nicht nur klarer zu lesen, es ist schneller und es funktioniert auch mit anderen Dingen als Listen (zum Beispiel Sets). Wenn dort eine Ausnahme wartet, wird die Funktion glücklicherweise fehlschlagen und sie in der Kette ausgeben.

2

Ich arbeite an pyfpm, eine Bibliothek für Mustererkennung in Python mit einer Scala-ähnlichen Syntax. Sie können es verwenden, um Objekte wie diese entpacken:

from pyfpm import Unpacker 

unpacker = Unpacker() 

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

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

oder in einer Funktion arglist:

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

sehr, sehr unpythonisch :) –