Безопасно распаковать пустой массив кортежей - PullRequest
0 голосов
/ 01 февраля 2019

Линия import re; print(re.findall("(.*) (.*)", "john smith")) выводит [("john", "smith")], которая может быть распакована как [(first_name, last_name)] = re.findall(...).Тем не менее, в случае несоответствия (findall возвращение []) эта распаковка генерирует ValueError: not enough values to unpack (expected 1, got 0).

Как правильно безопасно распаковать этот массив кортежей, который будет работать как в случае совпадения ([("john", "smith")]), так и несоответствия ([])?

Ответы [ 4 ]

0 голосов
/ 01 февраля 2019

Это ужасно, так что не делайте этого, но вы можете использовать

first, last = getattr(re.search(r"(.*) (.*)", "john smith"), 'groups', lambda: (None, None))()

, чтобы сделать то, что вы хотите, в качестве однострочного, без использования findall (что может вернуть несколько попаданий, итак что все равно терпите неудачу, или игнорируйте пробелы в зависимости от того, ограничиваете ли вы . до \S).

Учитывая, что ваш шаблон соответствует буквально чему-либо с одним пробелом в нем (захватывая все до последнего пробела, ивсе, что после него), избегайте findall, но вы не получите много, но если вы действительно хотите исключить вещи с более чем одним пробелом или вещи, которые совпадают только частично, вы можете переключить . на \S, ивозможно search до fullmatch:

first, last = getattr(re.fullmatch(r"(\S*) (\S*)", "john smith"), 'groups', lambda: (None, None))()

В любом случае он использует тот факт, что несоответствие возвращает None, у которого нет метода groups, поэтому getattr может вернутьсвязанный метод groups для совпадения или lambda, который возвращает значения по умолчанию в противном случае.В любом случае, вы немедленно звоните и получаете результат groups или lambda в зависимости от ситуации.

Опять же, не делайте этого.Это законно, просто уродливо (и, вероятно, медленнее, чем любой разумный метод).

0 голосов
/ 01 февраля 2019

Поскольку re.findall возвращает пустой список в случае несоответствия, вы можете использовать оператор or для назначения значений по умолчанию first_name и last_name вместо:

[(first_name, last_name)] = re.findall("(.*) (.*)", "johnsmith") or [(None, None)]
0 голосов
/ 01 февраля 2019

Общий ответ - посмотреть, прежде чем прыгнуть:

if result:
    [(first_name, last_name)] = result

или попросить прощения:

try:
    [(first_name, last_name)] = result
except ValueError:
    pass

, но на самом деле вы слишком усложняете вещи, используя re.findall(), чтобы найтиединственный результат.Используйте re.seach() и извлеките ваши подходящие группы :

match = re.search("(.*) (.*)", value)
if match:
    firstname, lastname = match.groups()

или

try:
    firstname, lastname = re.search("(.*) (.*)", value).groups()
except AttributeError:
    # An attribute error is raised when `re.search()` returned None
    pass
0 голосов
/ 01 февраля 2019

Нет ни одного;вы должны явно проверить возвращаемое значение, чтобы увидеть, есть ли на самом деле что-нибудь для распаковки.

x = re.findall(...)
if x:
    [(first_name, last_name)] = x

В Python 3.8 вы сможете немного сжать это:

if x := re.findall(...):
    [(first_name, last_name)] = x
...