Wie teile ich eine Zeichenfolge in Python zuverlässig auf, wenn sie möglicherweise nicht das Muster oder alle n Elemente enthält?

77

In Perl kann ich:

my ($x, $y) = split /:/, $str;

Und es wird funktionieren, ob die Zeichenfolge das Muster enthält oder nicht.

In Python funktioniert dies jedoch nicht:

a, b = "foo".split(":")  # ValueError: not enough values to unpack

Was ist der kanonische Weg, um Fehler in solchen Fällen zu vermeiden?

planetp
quelle
4
Was tun $xund $yerhalten Sie in Perl, wenn die Zeichenfolge das Muster nicht enthält? Werden beiden die gesamte Zeichenfolge zugewiesen oder wird $ynull oder so?
Keine Panik
6
@ Don'tPanic: Ruft $xden gesamten String ab, $yist undef(ähnlich None, aber subtil unterschiedlich).
Cdarke
4
@ jpmc26: In Perl werden zusätzliche Werte ignoriert. Aber ich bezweifle, dass wir wirklich einen Perl-Emulator in Python schreiben wollen.
Cdarke
7
@cdarke Natürlich schreiben wir keinen Perl-Emulator, aber wir können die Frage nicht beantworten, ohne zu wissen, was das gewünschte Verhalten ist. Ein wichtiger Aspekt des OP-Codes, den sie in der Frage weglassen, ist, dass die Python-Version auch fehlschlagen würde, wenn die Zeichenfolge mehrere Doppelpunkte enthalten würde. Wie auch immer, die Dokumentation scheint Ihnen zu widersprechen. Es sieht so aus, als würde Perl eine Liste zurückgeben, die bei jedem Auftreten des Musters aufgeteilt wird, genau wie Pythons splitFunktion. Es scheint auch, dass Perl's spliteinen regulären Ausdruck akzeptiert.
jpmc26
2
@ jpmc26 Eine Perl-Liste ist nicht dasselbe wie eine Python-Liste, sie ist näher an einem Tupel, außer dass Sie keine Variable vom Typ Liste in Perl haben können und auch keinen Verweis auf eine. Eine Liste in Perl ist eigentlich nur ein syntaktisches Gerät. Hier gibt es eine Diskussion: friedo.com/blog/2013/07/arrays-vs-lists-in-perl . Ja, Perl splitist näher dran, re.splitaußer dass es zusätzliche Magie für Leerzeichen gibt.
Cdarke

Antworten:

111

Wenn Sie sich in nur zwei Teile aufteilen (wie in Ihrem Beispiel), können str.partition()Sie ein garantiertes Argument erhalten, das die Größe 3 entpackt:

>>> a, sep, b = 'foo'.partition(':')
>>> a, sep, b
('foo', '', '')

str.partition() Gibt immer ein 3-Tupel zurück, unabhängig davon, ob das Trennzeichen gefunden wurde oder nicht.

Eine andere Alternative für Python 3.x ist das erweiterte iterierbare Entpacken :

>>> a, *b = 'foo'.split(':')
>>> a, b
('foo', [])

Dadurch wird das erste geteilte Element aund die Liste der verbleibenden Elemente (falls vorhanden) zugewiesen b.

Eugene Yarmash
quelle
59

Da Sie auf Python 3 sind, ist es einfach. PEP 3132 führte eine willkommene Vereinfachung der Syntax beim Zuweisen zu Tupeln ein - Erweitertes iterierbares Entpacken . In der Vergangenheit muss bei der Zuweisung zu Variablen in einem Tupel die Anzahl der Elemente links von der Zuweisung genau der Anzahl rechts entsprechen.

In Python 3 können wir jede Variable auf der linken Seite als Liste festlegen, indem wir ein Sternchen * voranstellen. Dadurch werden so viele Werte wie möglich erfasst, während die Variablen rechts davon ausgefüllt werden (es muss also nicht das Element ganz rechts sein). Dies vermeidet viele böse Scheiben, wenn wir die Länge eines Tupels nicht kennen.

a, *b = "foo".split(":")  
print("a:", a, "b:", b)

Gibt:

a: foo b: []

BEARBEITEN Sie folgende Kommentare und Diskussionen:

Im Vergleich zur Perl-Version ist dies erheblich anders, aber es ist die Python (3) -Methode. Im Vergleich zur Perl-Version re.split()wäre dies ähnlicher, jedoch ist das Aufrufen der RE-Engine zum Aufteilen um ein einzelnes Zeichen ein unnötiger Aufwand.

Mit mehreren Elementen in Python:

s = 'hello:world:sailor'
a, *b = s.split(":")
print("a:", a, "b:", b)

gibt:

a: hello b: ['world', 'sailor']

Jedoch in Perl:

my $s = 'hello:world:sailor';
my ($a, $b) = split /:/, $s;
print "a: $a b: $b\n";

gibt:

a: hello b: world

Es ist ersichtlich, dass zusätzliche Elemente in Perl ignoriert werden oder verloren gehen. Das ist bei Bedarf in Python ziemlich einfach zu replizieren:

s = 'hello:world:sailor'
a, *b = s.split(":")
b = b[0]
print("a:", a, "b:", b)

Also, a, *b = s.split(":")Äquivalent in Perl wäre

my ($a, @b) = split /:/, $s;

NB: Wir sollten verwenden nicht $aund $bim Allgemeinen Perl , da sie eine besondere Bedeutung haben , wenn verwendet mit sort. Ich habe sie hier verwendet, um die Konsistenz mit dem Python-Beispiel zu gewährleisten.

Python hat einen zusätzlichen Trick im Ärmel, wir können zu jedem Element im Tupel auf der linken Seite entpacken:

s = "one:two:three:four"
a, *b, c = s.split(':')
print("a:", a, "b:", b, "c:", c)

Gibt:

a: one b: ['two', 'three'] c: four

Während in der äquivalenten Perl, das Array ( @bIS) gierig, und die skalare $cist undef:

use strict;
use warnings;

my $s = 'one:two:three:four';
my ($a, @b, $c) = split /:/, $s;
print "a: $a b: @b c: $c\n";

Gibt:

Use of uninitialized value $c in concatenation (.) or string at gash.pl line 8.
a: one b: two three four c: 
cdarke
quelle
Wie würde es funktionieren, wenn Sie eine Variable rechts von setzen b?
Panzercrisis
3
@Panzercrisis es ist robust - a,*b,c = "foo:bar:baz:last".split(":")gibt a="foo" b=["bar","baz"] c="last"EDIT: Es wird sterben, wenn Sie es nicht genug Werte für die bestimmten Dinge geben, dh die gleiche Aussage mit "foo"geteilt wirdValueError: not enough values to unpack (expected at least 2, got 1)
Delioth
1
@magu_ Es macht eine andere Sache. str.partitionführt nur einen Split durch. Es ist also wie vorbei maxsplit=1.
Bakuriu
1
@ jpmc26: Nicht genau. Wenn Sie in Perl das Ergebnis split()zwei Skalaren zuweisen, erhalten Sie entweder zwei Zeichenfolgen oder eine Zeichenfolge und eine undef, aber niemals eine Zeichenfolge und eine Array-Referenz.
Eugene Yarmash
2
@magu_ das liegt daran, dass Python 3 pythonischer ist als Python 2 :)
user3351605
21

Sie können die Ausnahme jederzeit abfangen.

Zum Beispiel:

some_string = "foo"

try:
    a, b = some_string.split(":")
except ValueError:
    a = some_string
    b = ""

Wenn das Zuweisen der gesamten ursprünglichen Zeichenfolge aund einer leeren Zeichenfolge bdas gewünschte Verhalten ist, würde ich wahrscheinlich verwenden, str.partition()wie es eugene y vorschlägt. Mit dieser Lösung haben Sie jedoch mehr Kontrolle darüber, was genau passiert, wenn die Zeichenfolge kein Trennzeichen enthält. Dies kann in einigen Fällen hilfreich sein.

Philippe Aubertin
quelle
3
Dies würde nicht funktionieren, wenn die Zeichenfolge mehrere Trennzeichen enthält, z. B.'a:b:c:d:e'
jpmc26
17

splitgibt immer eine Liste zurück. a, b = ...erwartet immer eine Listenlänge von zwei. Sie können so etwas wie verwenden l = string.split(':'); a = l[0]; ....

Hier ist ein Einzeiler: a, b = (string.split(':') + [None]*2)[:2]

Aaron Schif
quelle
4

Wie wäre es mit regulären Ausdrücken:

import re 
string = 'one:two:three:four'

in 3.X:

a, *b = re.split(':', string)

in 2.X:

a, b = re.split(':', string)[0], re.split(':', string)[1:]

Auf diese Weise können Sie auch reguläre Ausdrücke zum Teilen verwenden (dh \ d).

Cheyn Shmuel
quelle