re_single_quote = r "'[^'\\]*(?:\\.[^'\\]*)*'"
Во-первых, обратите внимание, что ответ MizardX точен на 100%. Я просто хотел бы добавить некоторые дополнительные рекомендации относительно эффективности. Во-вторых, я хотел бы отметить, что эта проблема была решена и оптимизирована давно - см .: Освоение регулярных выражений (3-е издание) (которая подробно описывает эту конкретную проблему - очень рекомендуется).
Сначала давайте посмотрим на подвыражение, чтобы соответствовать одной строке в кавычках, которая может содержать экранированные одинарные кавычки. Если вы собираетесь разрешить экранированные одинарные кавычки, лучше хотя бы разрешить экранированные экранирования (что и делает ответ Дугласа Лидера). Но до тех пор, пока вы это делаете, его так же легко позволить избежать чего-либо еще. С этими требованиями. MizardX - единственный, кто получил правильное выражение. Здесь он представлен как в коротком, так и в длинном формате (и я позволил себе написать это в режиме VERBOSE
, с большим количеством описательных комментариев - что вы должны всегда делать для нетривиальных регулярных выражений):
# MizardX's correct regex to match single quoted string:
re_sq_short = r"'((?:\\.|[^\\'])*)'"
re_sq_long = r"""
' # Literal opening quote
( # Capture group $1: Contents.
(?: # Group for contents alternatives
\\. # Either escaped anything
| [^\\'] # or one non-quote, non-escape.
)* # Zero or more contents alternatives.
) # End $1: Contents.
'
"""
Это работает и правильно сопоставляет все следующие строковые тесты:
text01 = r"out1 'escaped-escape: \\ ' out2"
test02 = r"out1 'escaped-quote: \' ' out2"
test03 = r"out1 'escaped-anything: \X ' out2"
test04 = r"out1 'two escaped escapes: \\\\ ' out2"
test05 = r"out1 'escaped-quote at end: \'' out2"
test06 = r"out1 'escaped-escape at end: \\' out2"
Хорошо, теперь давайте начнем улучшать это. Во-первых, порядок альтернатив имеет значение, и всегда следует ставить наиболее вероятную альтернативу на первое место. В этом случае не экранированные символы более вероятны, чем экранированные, поэтому изменение порядка немного улучшит эффективность регулярного выражения следующим образом:
# Better regex to match single quoted string:
re_sq_short = r"'((?:[^\\']|\\.)*)'"
re_sq_long = r"""
' # Literal opening quote
( # $1: Contents.
(?: # Group for contents alternatives
[^\\'] # Either a non-quote, non-escape,
| \\. # or an escaped anything.
)* # Zero or more contents alternatives.
) # End $1: Contents.
'
"""
"разворачивая-The-Loop":
Это немного лучше, но может быть еще более улучшено (значительно), применяя эффективность разматывания Джеффри Фридла * (от MRE3 ). Вышеупомянутое регулярное выражение не является оптимальным, потому что оно должно тщательно применять звездный квантификатор к группе без захвата из двух альтернатив, каждая из которых использует только один или два символа за раз. Это чередование может быть полностью устранено путем признания того, что один и тот же шаблон повторяется снова и снова, и что эквивалентное выражение может быть создано для того же действия без чередования. Вот оптимизированное выражение для соответствия одной строке в кавычках и захвата ее содержимого в группу $1
:
# Better regex to match single quoted string:
re_sq_short = r"'([^'\\]*(?:\\.[^'\\]*)*)'"
re_sq_long = r"""
' # Literal opening quote
( # $1: Contents.
[^'\\]* # {normal*} Zero or more non-', non-escapes.
(?: # Group for {(special normal*)*} construct.
\\. # {special} Escaped anything.
[^'\\]* # More {normal*}.
)* # Finish up {(special normal*)*} construct.
) # End $1: Contents.
'
"""
Это выражение поглощает все не-кавычки, не-обратные слеши (подавляющее большинство большинства строк) за один "глоток", что резко сокращает объем работы, которую должен выполнять механизм регулярных выражений. Насколько лучше ты спрашиваешь? Итак, я ввел каждое из регулярных выражений, представленных в этом вопросе, в RegexBuddy и измерил, сколько шагов потребовалось механизму регулярных выражений для завершения сопоставления в следующей строке (что все решения правильно сопоставляют):
'This is an example string which contains one \'internally quoted\' string.'
Вот результаты тестов для приведенной выше тестовой строки:
r"""
AUTHOR SINGLE-QUOTE REGEX STEPS TO: MATCH NON-MATCH
Evan Fosmark '(.*?)(?<!\\)' 374 376
Douglas Leeder '(([^\\']|\\'|\\\\)*)' 154 444
cletus/PEZ '((?:\\'|[^'])*)(?<!\\)' 223 527
MizardX '((?:\\.|[^\\'])*)' 221 369
MizardX(improved) '((?:[^\\']|\\.)*)' 153 369
Jeffrey Friedl '([^\\']*(?:\\.[^\\']*)*)' 13 19
"""
Это количество шагов, необходимое для сопоставления тестовой строки с использованием функции отладчика RegexBuddy. Столбец «NON-MATCH» - это количество шагов, необходимых для объявления ошибки совпадения, когда закрывающая кавычка удаляется из тестовой строки. Как видите, разница значима как для совпадающих, так и для несоответствующих случаев. Также обратите внимание, что эти улучшения эффективности применимы только к механизму NFA, который использует возврат (например, Perl, PHP, Java, Python, Javascript, .NET, Ruby и большинство других.) Механизм DFA не увидит никакого повышения производительности с помощью этой техники ( См .: Сопоставление регулярных выражений может быть простым и быстрым ).
На полное решение:
Цель исходного вопроса (моя интерпретация) состоит в том, чтобы выбрать подстроки в одинарных кавычках (которые могут содержать экранированные кавычки) из строки большего размера. Если известно, что текст вне заключенных в кавычки подстрок никогда не будет содержать escape-одиночные кавычки, приведенное выше регулярное выражение выполнит эту работу. Тем не менее, чтобы правильно сопоставить подстроки в одинарных кавычках в море текста, плавающем с кавычками-кавычками и escape-кавычками и escaped-everything-elses (такова моя интерпретация того, что следует за автором), требует анализа с начала строки Нет, (это то, что я изначально думал), но это не так - это может быть достигнуто с помощью очень умного (?<!\\)(?:\\\\)*
выражения MizardX. Вот несколько тестовых строк для реализации различных решений:
text01 = r"out1 'escaped-escape: \\ ' out2"
test02 = r"out1 'escaped-quote: \' ' out2"
test03 = r"out1 'escaped-anything: \X ' out2"
test04 = r"out1 'two escaped escapes: \\\\ ' out2"
test05 = r"out1 'escaped-quote at end: \'' out2"
test06 = r"out1 'escaped-escape at end: \\' out2"
test07 = r"out1 'str1' out2 'str2' out2"
test08 = r"out1 \' 'str1' out2 'str2' out2"
test09 = r"out1 \\\' 'str1' out2 'str2' out2"
test10 = r"out1 \\ 'str1' out2 'str2' out2"
test11 = r"out1 \\\\ 'str1' out2 'str2' out2"
test12 = r"out1 \\'str1' out2 'str2' out2"
test13 = r"out1 \\\\'str1' out2 'str2' out2"
test14 = r"out1 'str1''str2''str3' out2"
Принимая во внимание эти тестовые данные, давайте посмотрим, как работают различные решения ('p' == pass, 'XX' == fail):
r"""
AUTHOR/REGEX 01 02 03 04 05 06 07 08 09 10 11 12 13 14
Douglas Leeder p p XX p p p p p p p p XX XX XX
r"(?:^|[^\\])'(([^\\']|\\'|\\\\)*)'"
cletus/PEZ p p p p p XX p p p p p XX XX XX
r"(?<!\\)'((?:\\'|[^'])*)(?<!\\)'"
MizardX p p p p p p p p p p p p p p
r"(?<!\\)(?:\\\\)*'((?:\\.|[^\\'])*)'"
ridgerunner p p p p p p p p p p p p p p
r"(?<!\\)(?:\\\\)*'([^'\\]*(?:\\.[^'\\]*)*)'"
"""
Рабочий тестовый скрипт:
import re
data_list = [
r"out1 'escaped-escape: \\ ' out2",
r"out1 'escaped-quote: \' ' out2",
r"out1 'escaped-anything: \X ' out2",
r"out1 'two escaped escapes: \\\\ ' out2",
r"out1 'escaped-quote at end: \'' out2",
r"out1 'escaped-escape at end: \\' out2",
r"out1 'str1' out2 'str2' out2",
r"out1 \' 'str1' out2 'str2' out2",
r"out1 \\\' 'str1' out2 'str2' out2",
r"out1 \\ 'str1' out2 'str2' out2",
r"out1 \\\\ 'str1' out2 'str2' out2",
r"out1 \\'str1' out2 'str2' out2",
r"out1 \\\\'str1' out2 'str2' out2",
r"out1 'str1''str2''str3' out2",
]
regex = re.compile(
r"""(?<!\\)(?:\\\\)*'([^'\\]*(?:\\.[^'\\]*)*)'""",
re.DOTALL)
data_cnt = 0
for data in data_list:
data_cnt += 1
print ("\nData string %d" % (data_cnt))
m_cnt = 0
for match in regex.finditer(data):
m_cnt += 1
if (match.group(1)):
print(" quoted sub-string%3d = \"%s\"" %
(m_cnt, match.group(1)))
Уф!
p.s. Спасибо MizardX за очень классное выражение (?<!\\)(?:\\\\)*
. Узнай что-то новое каждый день!