Як надійно розділити рядок у Python, коли він може не містити шаблон або всі n елементів?


77

У Perl я можу робити:

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

І це спрацює незалежно від того, чи рядок містить шаблон.

Однак у Python це не спрацює:

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

Який канонічний спосіб запобігти помилкам у таких випадках?


4
Що робити $xі $yотримувати в Perl, якщо рядок не містить шаблон? Чи їм обом присвоєно весь рядок, чи $yнуль чи щось інше?
Не панікуйте

6
@ Don'tPanic: $xотримує цілий рядок, $yє undef(що схоже на None, але тонко відрізняється).
cdarke

4
@ jpmc26: у Perl він буде ігнорувати додаткові значення. Але я сумніваюся, що ми дійсно хочемо написати емулятор Perl на python.
cdarke

7
@cdarke Звичайно, ми не пишемо емулятор Perl, але ми не можемо відповісти на питання, не знаючи, якою є бажана поведінка. Важливим аспектом коду OP, який вони опускають із питання, є те, що версія Python також зазнала б невдачі, якби рядок містив кілька двокрапок. У будь-якому випадку, документація, здається, суперечить вам. Схоже, Perl повертає список, розбиваючись при кожному появі шаблону, так само, як і splitфункція Python . Також виявляється, що Perl's splitприймає регулярний вираз.
jpmc26

2
@ jpmc26 Список Perl - це не те саме, що список python, він ближчий до кортежу, за винятком того, що у вас не може бути змінної типу type в perl, і ви також не можете мати посилання на нього. Список на Perl - це насправді лише синтаксичний пристрій. Тут ведеться дискусія: friedo.com/blog/2013/07/arrays-vs-lists-in-perl . Так, Perl's splitближче, re.splitкрім хіба що є додаткова магія для пробілів.
cdarke

Відповіді:


111

Якщо ви ділитесь лише на дві частини (як у вашому прикладі), ви можете використовувати 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.


59

Оскільки ви працюєте на 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;

Примітка: ми не повинні використовувати $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: 

Як би це працювало, якщо ви поставите змінну праворуч від b?
Panzercrisis

3
@Panzercrisis він надійний - a,*b,c = "foo:bar:baz:last".split(":")дає РЕДАКТУВАТИ a="foo" b=["bar","baz"] c="last": Він помре, якщо ви не надасте йому достатньо значень для певних речей, тобто той самий вислів із "foo"розщепленням будеValueError: not enough values to unpack (expected at least 2, got 1)
Деліот

1
@magu_ Це робить інше. str.partitionвиконує лише один спліт. Тож це як проходження maxsplit=1.
Бакуріу

1
@ jpmc26: Не зовсім. У Perl, якщо ви присвоюєте результат split()двом скалярам, ​​ви отримуєте або два рядки, або рядок і an undef, але ніколи не посилання на рядок і масив.
Євген Ярмаш

2
@magu_ це тому, що Python 3 є більш пітонічним, ніж Python 2 :)
user3351605

21

Ви завжди можете зловити виняток.

Наприклад:

some_string = "foo"

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

Якщо присвоєння цілому оригінальному рядку aта порожньому рядку bє бажаною поведінкою, я б, мабуть, використав, str.partition()як пропонує Євгеній У. Однак це рішення дає вам більше контролю над тим, що саме відбувається, коли в рядку немає роздільника, що може бути корисним у деяких випадках.


3
Це не спрацювало б, якщо рядок містив кілька розділювачів, наприклад,'a:b:c:d:e'
jpmc26

17

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

Ось один вкладиш: a, b = (string.split(':') + [None]*2)[:2]


4

Як щодо використання регулярних виразів:

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

Таким чином, ви також можете використовувати регулярні вирази для розділення (тобто \ d)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.