web-dev-qa-db-fra.com

Comment puis-je scinder de manière fiable une chaîne en Python, quand elle peut ne pas contenir le motif, ou tous les n éléments?

En Perl je peux faire:

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

Et cela fonctionnera que la chaîne contienne ou non le motif.

En Python, cependant, cela ne fonctionnera pas:

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

Quelle est la manière canonique de prévenir les erreurs dans de tels cas?

74
planetp

Si vous vous divisez en seulement deux parties (comme dans votre exemple), vous pouvez utiliser str.partition() pour obtenir un argument garanti déballant la taille de 3:

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

str.partition() renvoie toujours un 3-Tuple, que le séparateur soit trouvé ou non.

Une autre alternative pour Python 3.x est d'utiliser décompression itérative étendue :

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

Cela affecte le premier élément divisé à a et la liste des éléments restants (le cas échéant) à b.

111
Eugene Yarmash

Puisque vous êtes sur Python 3, c'est facile. PEP 3132 a introduit une simplification bienvenue de la syntaxe lors de l'attribution aux tuples - Déballage itératif étend. Dans le passé, si en affectant des variables dans un Tuple, le nombre d'éléments à gauche de l'affectation doit être exactement égal à celui de droite.

Dans Python 3, nous pouvons désigner n'importe quelle variable à gauche comme une liste en préfixant avec un astérisque *. Cela récupérera autant de valeurs que possible, tout en remplissant les variables à sa droite (donc ce n'est pas nécessairement l'élément le plus à droite. Cela évite de nombreuses tranches désagréables lorsque nous ne connaissons pas la longueur d'un tuple.

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

Donne:

a: foo b: []

MODIFIER les commentaires et la discussion suivants:

Par rapport à la version Perl, c'est considérablement différent, mais c'est la manière Python (3). En comparaison avec la version Perl, re.split() serait plus similaire, cependant, invoquer le moteur RE pour le fractionnement autour d'un seul caractère est une surcharge inutile.

Avec plusieurs éléments en Python:

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

donne:

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

Cependant en Perl:

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

donne:

a: hello b: world

On peut voir que des éléments supplémentaires sont ignorés ou perdus dans Perl. C'est assez facile à répliquer en Python si nécessaire:

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

Donc, a, *b = s.split(":") équivalent en Perl serait

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

NB: nous ne devons pas utiliser $a Et $b En général Perl car ils ont une signification particulière lorsqu'ils sont utilisés avec sort. Je les ai utilisés ici pour la cohérence avec l'exemple Python.

Python a une astuce supplémentaire dans sa manche, nous pouvons décompresser vers n'importe quel élément du tuple à gauche:

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

Donne:

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

Alors que dans l'équivalent Perl, le tableau (@b) Est gourmand, et le scalaire $c Est 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";

Donne:

Use of uninitialized value $c in concatenation (.) or string at gash.pl line 8.
a: one b: two three four c: 
60
cdarke

Vous êtes toujours libre d'attraper l'exception.

Par exemple:

some_string = "foo"

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

Si l'attribution de toute la chaîne d'origine à a et une chaîne vide à b est le comportement souhaité, j'utiliserais probablement str.partition() comme le suggère eugene y. Cependant, cette solution vous donne plus de contrôle sur ce qui se passe exactement quand il n'y a pas de séparateur dans la chaîne, ce qui peut être utile dans certains cas.

22
Philippe Aubertin

split renverra toujours une liste. a, b = ... S'attendra toujours à une longueur de liste de deux. Vous pouvez utiliser quelque chose comme l = string.split(':'); a = l[0]; ....

Voici une ligne: a, b = (string.split(':') + [None]*2)[:2]

17
Aaron Schif

Que diriez-vous d'utiliser des expressions régulières:

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

en 3.X:

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

en 2.X:

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

De cette façon, vous pouvez également utiliser des expressions régulières pour fractionner (i. E.\D)

3
Cheyn Shmuel