Почему компиляция 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 как два отдельных символа.