Как надежно разбить строку на Python, если она не может содержать шаблон или все n элементов?

В Perl я могу сделать:

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

И будет работать, будет ли строка содержать шаблон.

В Python, однако это не сработает:

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

Каков канонический способ предотвращения ошибок в таких случаях?

Ответ 1

Если вы разделитесь на две части (например, в вашем примере), вы можете использовать str.partition() чтобы получить гарантированный аргумент, распаковывающий размер 3:

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

str.partition() всегда возвращает 3-кортеж, независимо от того, найден ли разделитель или нет.

Другой альтернативой для Python 3.x является использование расширенной итеративной распаковки:

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

Это присваивает первый разделительный элемент a и список оставшихся элементов (если они есть) на b.

Ответ 2

Поскольку вы находитесь на Python 3, это легко. PEP 3132 представил приветственное упрощение синтаксиса при назначении кортежей - расширенная итеративная распаковка. Раньше, при назначении переменных в кортеже, количество элементов слева от присваивания должно быть точно равно числу справа.

В Python 3 мы можем обозначить любую переменную слева как список, предварительно указав звездочку *. Это будет захватывать как можно больше значений, при этом все еще заполняя переменные справа (так что это не должен быть самый правый элемент). Это позволяет избежать множества неприятных фрагментов, когда мы не знаем длину кортежа.

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

дает:

a: foo b: []

ИЗМЕНИТЬ следующие комментарии и обсуждения:

По сравнению с версией Perl это значительно отличается, но это Python (3). По сравнению с версией Perl, re.split() будет более похожим, однако обращение к механизму RE для разделения вокруг одного символа - лишние накладные расходы.

С несколькими элементами в Python:

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

дает:

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

Однако в Perl:

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

дает:

a: hello b: world

Можно видеть, что дополнительные элементы игнорируются или теряются в Perl. Это довольно легко реплицировать в Python, если требуется:

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

Итак, эквивалент a, *b = s.split(":") в Perl был бы

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

NB: мы не должны использовать $a и $b вообще Perl, поскольку они имеют особое значение при использовании с sort. Я использовал их здесь для согласованности с примером Python.

У Python есть дополнительный трюк в рукаве, мы можем распаковать любой элемент в кортеже слева:

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

дает:

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

В то время как в эквиваленте Perl массив (@b) является жадным, а скаляр $c равен 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";

дает:

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

Ответ 3

Вы всегда можете поймать исключение.

Например:

some_string = "foo"

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

Если присваивать всю исходную строку a и пустую строку в b - это желаемое поведение, я бы, вероятно, использовал str.partition(), как предлагает eugene y. Однако это решение дает вам больше контроля над тем, что происходит, когда в строке нет разделителя, что может быть полезно в некоторых случаях.

Ответ 4

split всегда будет возвращать список. a, b = ... всегда будет ожидать, что длина списка будет равна двум. Вы можете использовать что-то вроде l = string.split(':'); a = l[0]; ....

Вот один лайнер: a, b = (string.split(':') + [None]*2)[:2]

Ответ 5

Как использовать регулярные выражения:

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

в 3.X:

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

в 2.X:

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

Таким образом вы также можете использовать регулярные выражения для разделения (i. e.\d)