Регулярное выражение с флагом m в Perl и Python

Я пытаюсь автоматически перевести простой код Perl с регулярным выражением на Python, и у меня возникла проблема. Вот код Perl:

$stamp='[stamp]';
$message = "message\n";
$message =~ s/^/$stamp/gm;
print "$message";
[stamp]message

Вот мой эквивалент Python:

>>> import re
>>> re.sub(re.compile("^", re.M), "[stamp]", "message\n", count=0)
'[stamp]message\n[stamp]'

Обратите внимание, что ответ другой (в конце есть лишний [stamp]). Как сгенерировать код, который имеет такое же поведение для регулярного выражения?

^ с re.M соответствует началу каждой строки, а \n делает пример Python двумя строками, следовательно, двумя заменами.
jonrsharpe 13.02.2023 23:00

Без re.M пример был бы правильным, но я не знаю, как Perl ведет себя на других примерах.

Michael Butscher 13.02.2023 23:00

Похоже, это может быть несовместимость между «многострочными» режимами PCRE и Python RE. С re.M в Python «символ шаблона '^' совпадает в начале строки и в начале каждой строки (сразу после каждой новой строки)». Похоже, что PCRE делает еще один шаг вперед и требует, чтобы ^ находился в начале действительной строки POSIX (т. е. завершался символом новой строки).

Brian 13.02.2023 23:02

Кстати, если вы просто хотите добавить к строке префикс [start], использование регулярного выражения кажется излишним.

jonrsharpe 13.02.2023 23:04

Он добавляет префикс к каждой строке потенциально многострочной строки, и снова я автоматически перевожу код perl на python.

snoopyjc 13.02.2023 23:12

Примечание: re.sub(re.compile(...), ...) — это странный/многословный/избыточный способ написания re.compile(...).sub(...). А count=0 стоит по умолчанию, поэтому передавать его не нужно. Вы можете упростить re.sub(re.compile("^", re.M), "[stamp]", "message\n", count=0) до re.compile("^", re.M).sub("[stamp]", "message\n").

ShadowRanger 13.02.2023 23:16

Re: Примечание: Pythonizer — это автоматический переводчик с perl на python = он генерирует код в определенном порядке для обработки вещей, например, сначала он обрабатывает все флаги, кроме флага G, а затем обрабатывает последний, устанавливая count=0 vs count =1. Я понимаю, что код может выглядеть немного забавно, но он работает.

snoopyjc 13.02.2023 23:19

Похоже, что regex101.com/r/uyT1i2/1 и regex101.com/r/mNTTeP/1 может быть то, что EOS \Z переопределяет ^ в отношении строк, заканчивающихся новой строкой. Вы можете исключить двусмысленность с помощью (?m)^(?!\a|\Z) или (?m)(?!\a|\Z)^

sln 14.02.2023 00:11
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
Потяните за рычаг выброса энергососущих проектов
Потяните за рычаг выброса энергососущих проектов
На этой неделе моя команда отменила проект, над которым я работал. Неделя усилий пошла насмарку.
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
1
8
103
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Многострочный режим Perl не считает пустую строку после последней новой строки отдельной строкой. То есть он рассматривает A\nB и A\nB\n как две строки, а A\nB\nC как три строки. Это отличается от многострочного режима Python, который рассматривает каждую новую строку как начало новой строки:

re.M: если указано, символ шаблона '^' соответствует началу строки и началу каждой строки (сразу после каждой новой строки).

Вы можете сымитировать поведение многострочного режима Perl, добавив предварительное утверждение по крайней мере для одного символа в начале строки:

(?=.|\n)

Обратите внимание, что нам нужно явно разрешить \n с альтернативным шаблоном |, поскольку по умолчанию . не соответствует \n. Без этого шаблон не сможет соответствовать началу строк, за которыми сразу следует \n.

Вот как этот шаблон ведет себя в вашем примере:

>>> re.sub(re.compile(r"^(?=.|\n)", re.M), "[stamp]", "message\n", count=0)
'[stamp]message\n'
>>> re.sub(re.compile(r"^(?=.|\n)", re.M), "[stamp]", "message\nmessage", count=0)
'[stamp]message\n[stamp]message'
>>> re.sub(re.compile(r"^(?=.|\n)", re.M), "[stamp]", "message\nmessage\n", count=0)
'[stamp]message\n[stamp]message\n'
>>> re.sub(re.compile(r"^(?=.|\n)", re.M), "[stamp]", "message\n\n", count=0)
'message\n[stamp]\n'

Это не требует строк POSIX; Perl будет работать с любой непустой последней строкой, например "message\na", который имеет символ, но не завершает новую строку в последней (не POSIX) строке. Похоже, что Perl просто имеет немного другое определение строки (похожее на то, которое используется в методе Python str.splitlines); Perl не рассматривает строку, оканчивающуюся на новую строку, как оканчивающуюся совершенно пустой строкой, в отличие от Python re.

ShadowRanger 13.02.2023 23:19

@ShadowRanger, так можем ли мы каким-то образом изменить положительное опережающее утверждение, просто требуя, чтобы там был хотя бы 1 символ, например, с использованием '.'?

snoopyjc 13.02.2023 23:23

@snoopyjc: Этого было бы недостаточно, потому что . по умолчанию не соответствует новой строке, поэтому "message\n\n" (где вторая строка — это просто новая строка и ничего больше) добавит [stamp] только один раз, а не дважды. Добавление re.S (он же re.DOTALL) к флагам работает (re.M | re.S), но неясно, может ли это привести к странному поведению чего-то еще.

ShadowRanger 13.02.2023 23:28

Спасибо, а это вообще работает? Например. $STRING =~ s/$PATTERN/$REPLACEMENT/gm => re.sub(re.compile(r'PATTERN(?=.)", re.M), REPLACEMENT, STRING), всякий раз, когда используется флаг M Я могу изменить их регулярное выражение следующим образом?

snoopyjc 13.02.2023 23:29

@ShadowRanger Да, добавление флага S в регулярное выражение пользователя небезопасно. Как насчет того, чтобы использовать (?=[.\n]) для просмотра вперед?

snoopyjc 13.02.2023 23:31

@ShadowRanger Хотели бы вы опубликовать ответ, в котором говорится об этом? Я рад интерпретировать ваш отзыв и обновить этот, но на данный момент это действительно ваш ответ, а не мой.

Brian 13.02.2023 23:32

@snoopyjc: На первый взгляд это кажется разумным, но кажется, что это не работает с классом персонажей. r"^(?=.|\n)" работает. пожимает плечами

ShadowRanger 13.02.2023 23:36

@Брайан: Если ты настаиваешь. :-)

ShadowRanger 13.02.2023 23:42

Я только что проверил ваше решение с помощью $message = "message\n\nmes\nmmm"; и теперь он делает то же самое в perl и python. Но мой вопрос остается - как вы думаете, будет ли это работать с любым регулярным выражением, предоставленным пользователем, если у них установлен флаг M?

snoopyjc 13.02.2023 23:42

@snoopyjc: Вы предлагаете заменить ведущий ^ на ^(?=.|\n) в любом пользовательском шаблоне? Он должен работать; ?= имеет нулевую ширину, поэтому остальная часть их регулярного выражения будет совпадать как обычно, но регулярное выражение достаточно странное, поэтому я никогда не даю 100% гарантию для полностью произвольного регулярного выражения.

ShadowRanger 13.02.2023 23:46

@snoopyjc: И да, я придумал способ сломать простой подход «заменить ведущий ^»: r"abc|^". ^ скрыт чередованием, поэтому вы не увидите его в начале узора. Как я уже сказал, регулярное выражение — это странно.

ShadowRanger 13.02.2023 23:58

Для /abc|^/ Считаете ли вы, что r"abc|^(?=.|\n)" является правильным ответом, другими словами, замените все '^' на '^(?=.|\n)', не только ведущий?

snoopyjc 14.02.2023 00:27

Проблема в том, что Perl не считает пустую строку после \n строкой текста, в отличие от Python. Таким образом, код RegEx Perl видит "message\n" как одну строку текста, а код RegEx Python — как две.

Вы можете устранить эту разницу, проверив код Python для окончательного \n. Если он обнаружит его, удалите этот \n перед запуском регулярного выражения, а затем добавьте его обратно после.

Вероятно, вы также захотите проверить все пограничные случаи. Например, как ведет себя код Perl и Python, если само сообщение представляет собой пустую строку? (Будет ли код Perl делать что-нибудь в этом случае? Как насчет кода Python?)

Если все ваши сообщения гарантированно представляют собой текст ненулевой длины, заканчивающийся новой строкой, то вы, вероятно, можете просто удалить последний \n, применить код регулярного выражения Python, а затем добавить этот \n обратно. Но это все равно будет быть хорошим тоном, чтобы рассмотреть все крайние случаи.


ДОБАВЛЕНИЕ:

Хотя заманчиво придумать регулярное выражение в Python, которое точно имитирует выражение в Perl, я бы не обязательно рекомендовал его, особенно если регулярное выражение Python настолько сложное, что трудно сказать, что оно делает в первый или второй раз. взглянуть мельком.

Регулярные выражения не всегда корректно обрабатывают алгоритмическую логику. Поэтому, если вы можете устранить сложность регулярных выражений, введя простую алгоритмическую логику, я бы рекомендовал сделать это вместо этого.

Другими словами, вы не получите никаких наград, используя слишком сложные регулярные выражения, так почему бы вместо этого не использовать простое регулярное выражение в сочетании с простой алгоритмической логикой, не связанной с регулярными выражениями? (Будущие сопровождающие вашего кода будут вам благодарны!)


Вот рекомендация:

import re
message = "message\n"
message = re.sub(r"(?m:^)", "[stamp]", message)
# Remove the final "[stamp]" if it appears alone at the end:
message = message.removesuffix("[stamp]")

Это просто и легко понять, и единственная часть, которая может сбить с толку, — это если вы никогда раньше не видели флаг m, используемый внутри регулярного выражения, такого как (?m:...).

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

См. комментарий @Shadowranger к другому ответу.

snoopyjc 13.02.2023 23:25

@snoopyjc: я не думаю, что мой комментарий имеет отношение к этому ответу; удаление одной завершающей новой строки перед заменой и добавление ее обратно (только если для начала была завершающая новая строка) должно привести к согласию двух языков, это намного более многословно, чем небольшие изменения в регулярном выражении.

ShadowRanger 13.02.2023 23:30

Я имел в виду ваш другой комментарий :-)

snoopyjc 14.02.2023 00:14
Ответ принят как подходящий

Механизмы регулярных выражений Perl и Python немного отличаются в определении «строки»; Perl не считает пустую строку, следующую за символом новой строки в конце входной строки, строкой, в отличие от Python.

Лучшее решение, которое я могу придумать, это изменить "^" на r"^(?=.|\n)" (обратите внимание на префикс r в строке, чтобы сделать ее необработанным литералом; все регулярные выражения должны использовать необработанные литералы). Вы также можете немного упростить, просто вызывая методы в скомпилированном регулярном выражении или вызывая re.sub с нескомпилированным шаблоном, а поскольку count=0 уже используется по умолчанию, вы можете его опустить. Таким образом, окончательный код будет либо:

re.compile(r"^(?=.|\n)", re.M).sub("[stamp]", "message\n")

Или:

re.sub(r"^(?=.|\n)", "[stamp]", "message\n", flags=re.M)

Еще лучше было бы:

start_of_line = re.compile(r"^(?=.|\n)", re.M)  # Done once up front

start_of_line.sub("[stamp]", "message\n")  # Done on demand

Избегать повторной компиляции/перепроверки кэша скомпилированных регулярных выражений каждый раз, создавая скомпилированное регулярное выражение только один раз и повторно используя его.

Альтернативные решения:

  1. Разделите строки таким образом, чтобы они соответствовали определению строки в Perl, затем используйте не-re.MULTILINE версию регулярного выражения для каждой строки, а затем соедините их вместе, например:

    start_of_line = re.compile(r"^")  # Compile once up front without re.M
    
    # Split lines, keeping ends, in a way that matches Perl's definition of a line
    # then substitute on line-by-line basis
    ''.join([start_of_line.sub("[stamp]", line) for line in "message\n".splitlines(keepends=True)])
    
  2. Удалите одну завершающую новую строку, если она существует, заранее, выполните замену регулярных выражений, добавьте новую строку (если применимо):

    message = '...'
    if message.endswith('\n'):
        result = start_of_line.sub("[stamp]", message[:-1]) + '\n'
    else:
        result = start_of_line.sub("[stamp]", message)
    

Ни один из вариантов не является таким кратким/эффективным, как попытка настроить регулярное выражение, но если необходимо обрабатывать произвольное пользовательское регулярное выражение, всегда будет крайний случай, и предварительная обработка чего-то, что устраняет несовместимость Perl/Python, — это много. безопаснее.

Спасибо. Как вы думаете, это решение будет работать с любым регулярным выражением, предоставленным пользователем, с флагом M или просто '^'?

snoopyjc 13.02.2023 23:44

@snoopyjc: он справится с большинством случаев, когда начальный ^ в шаблоне заменяется на ^(?=.|\n). Но если пользователь использовал чередование, чтобы поставить ^ позже в шаблоне, например. r"abc|^", ой, это ^ действительно означало начало строки, и мы его пропустили. Regex сумасшедший.

ShadowRanger 13.02.2023 23:52

@snoopyjc: я только что опубликовал альтернативу, в которой используется str.splitlines, чтобы Python фактически разделялся на отдельные строки, поэтому вы вообще не используете re.M, и проблема исчезнет. Очевидно, что это будет медленнее, но я думаю, что это безопасно для шаблонов регулярных выражений, ориентированных на строки. Однако при наличии регулярных выражений я не могу поклясться ни в чем, это немного безумно.

ShadowRanger 13.02.2023 23:57

О, я просто собирался добавить (?=.|\n) к регулярному выражению пользователя всякий раз, когда он использовал флаг M. Например, если бы они сказали ^m, я бы поставил ^m(?=.|\n). Должен ли я вместо этого изменить это на ^(?=.|\n)m ?? Похоже, мне нужна куча тестовых случаев.

snoopyjc 14.02.2023 00:10

@snoopyjc: ^m(?=.|\n) определенно неверен для такого ввода, как "abc\nm", где ^m будет соответствовать и заменять этот окончательный m (как на Perl, так и на Python), но ^m(?=.|\n) не будет (на любом), потому что после символа нет никакого символа. m. ^(?=.|\n)m отлично сработает в этом случае, но вам придется написать анализатор шаблонов регулярных выражений, чтобы надежно определить правильное место для вставки (?=.|\n), если вы хотите обрабатывать полностью произвольное регулярное выражение, которое в противном случае было бы совместимо с синтаксисом регулярных выражений Perl и Python. . Пограничные случаи в конце концов убьют вас.

ShadowRanger 14.02.2023 00:19

Понял - спасибо! Начну писать эти тесты и посмотрю, что работает.

snoopyjc 14.02.2023 00:24

Другие вопросы по теме

Как выполнить разделение строк, используя регулярное выражение в качестве ссылки, и чтобы часть используемого шаблона разделителя не удалялась из следующей строки?
Правильный шаблон регулярного выражения (re) в python
Как я могу преобразовать двойные косые черты в R, не сталкиваясь с ошибкой, связанной с escape-символами?
JavaScript Буквенно-цифровое регулярное выражение и разрешить звездочку в начале строки, но не разрешать звездочку в последних 4 цифрах строки
Python/regex: соответствует только букве или букве, за которой следует число
Регулярное выражение для форматирования электронной почты без hypen в начале и в конце
Как написать регулярное выражение, которое удаляет любой символ или символ, стоящий перед диапазоном указанных разделителей "(" и ")]"
Поиск по первым буквам фразы - как vscode
Удаление всех экземпляров строки, которая не находится в круглых скобках
Понимание списка с помощью регулярных выражений в текстовом файле Python