Почему компиляция python regex в Linux, но не в Windows?
У меня есть регулярное выражение для обнаружения недопустимых символов xml 1.0 в строке юникода:
bad_xml_chars = re.compile(u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]', re.U)
В Linux/python2.7 это работает отлично. В окнах вызывается следующее:
File "C:\Python27\lib\re.py", line 190, in compile
return _compile(pattern, flags)
File "C:\Python27\lib\re.py", line 242, in _compile
raise error, v # invalid expression
sre_constants.error: bad character range
Любые идеи, почему это не компилируется в Windows?
Ответы
Ответ 1
У вас есть узкая сборка Python в Windows, поэтому Unicode использует UTF-16. Это означает, что символы Unicode выше \uFFFF
будут представлять собой два отдельных символа в строке Python. Вы должны увидеть что-то вроде этого:
>>> len(u'\U00010000')
2
>>> u'\U00010000'[0]
u'\ud800'
>>> u'\U00010000'[1]
u'\udc00'
Вот как движок regex попытается интерпретировать вашу строку в узких строках:
[^\x09\x0A\x0D\u0020-\ud7ff\ue000-\ufffd\ud800\udc00-\udbff\udfff]
Здесь вы можете видеть, что \udc00-\udbff
- это сообщение с недопустимым диапазоном.
Ответ 2
Это не работает, потому что версия Python для Windows использует 16 бит для представления символов Unicode, закодированных как UTF-16. Точки кода 10000
и выше представлены как два блока кода в UTF-16, и это смущает представление диапазона re
, которое ожидает один символ по обе стороны от -
.
Вот как строка, которую вы передаете в re.compile
, разбивается на символы:
>>> [x for x in u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]']
[u'[', u'^', u'\t', u'\n', u'\r', u' ', u'-', u'\ud7ff', u'\ue000', u'-',
u'\ufffd', u'\ud800', u'\udc00', u'-', u'\udbff', u'\udfff', u']']
Обратите внимание, что \U00010000-\U0010FFFF
представляется в виде 5 символов:
u'\ud800', u'\udc00', u'-', u'\udbff', u'\udfff'
Внутри набора символов [...]
, re.compile
интерпретирует это как символы u'\ud800'
и u'\udfff'
, а диапазон u'\udc00' - u'\udbff'
. Этот диапазон недопустим, поскольку его конец меньше его начала, что вызывает ошибку.
Ответ 3
В стандартной библиотеке есть раздел, в котором используются неправильные диапазоны символов (Lib/sre_compile.py:450
):
if code1[0] != LITERAL or code2[0] != LITERAL:
raise error, "bad character range"
lo = code1[1]
hi = code2[1]
if hi < lo:
raise error, "bad character range"
Когда он сравнивает литералы lo
и hi
вашего диапазона символов \U00010000-\U0010FFFF
, они выходят как ординалы 56320
и 56319
соответственно (что, конечно же, не выполняется, поскольку диапазон представляется в обратном направлении).
Как говорили другие, это связано с тем, что Python обрабатывает ваши 8-символьные символы Unicode как два отдельных символа.