Юникод, регулярные выражения и PyPy

Я написал программу для добавления (ограниченного) поддержки unicode в регулярные выражения Python, и пока он отлично работает на CPython 2.5.2, он не работает над PyPy ( 1.5.0-alpha0 1.8.0, реализующий Python 2.7.1 2.7.2), оба выполняются в Windows XP (Edit: как видно из комментариев, @dbaupp может запускать его отлично работает в Linux). Я понятия не имею, почему, но я подозреваю, что это имеет какое-то отношение к моим применениям u" и ur". Полный источник здесь, а соответствующие биты:

# -*- coding:utf-8 -*-
import re

# Regexps to match characters in the BMP according to their Unicode category.
# Extracted from Unicode specification, version 5.0.0, source:
# http://unicode.org/versions/Unicode5.0.0/
unicode_categories = {
    ur'Pi':ur'[\u00ab\u2018\u201b\u201c\u201f\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c]',
    ur'Sk':ur'[\u005e\u0060\u00a8\u00af\u00b4\u00b8\u02c2-\u02c5\u02d2-\u02df\u02...',
    ur'Sm':ur'[\u002b\u003c-\u003e\u007c\u007e\u00ac\u00b1\u00d7\u00f7\u03f6\u204...',
    ...
    ur'Pf':ur'[\u00bb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d]',
    ur'Me':ur'[\u0488\u0489\u06de\u20dd-\u20e0\u20e2-\u20e4]',
    ur'Mc':ur'[\u0903\u093e-\u0940\u0949-\u094c\u0982\u0983\u09be-\u09c0\u09c7\u0...',
}

def hack_regexp(regexp_string):
    for (k,v) in unicode_categories.items():
        regexp_string = regexp_string.replace((ur'\p{%s}' % k),v)
    return regexp_string

def regex(regexp_string,flags=0):
    """Shortcut for re.compile that also translates and add the UNICODE flag

    Example usage:
        >>> from unicode_hack import regex
        >>> result = regex(ur'^\p{Ll}\p{L}*').match(u'áÇñ123')
        >>> print result.group(0)
        áÇñ
        >>> 
    """
    return re.compile(hack_regexp(regexp_string), flags | re.UNICODE)

(в PyPy нет совпадения в "примере использования", поэтому result есть None)

Повторяя, программа работает нормально (на CPython): данные Юникода кажутся правильными, замена работает по назначению, пример использования работает нормально (оба через doctest и непосредственно вводя его в командной строке). Исходная кодировка файлов также правильна, и директива coding в заголовке, по-видимому, распознается Python.

Любые идеи о том, что PyPy делает "разные", которые нарушают мой код? Множество вещей пришло мне в голову (непризнанный заголовок coding, разные кодировки в командной строке, разные интерпретации r и u), но, насколько мои тесты идут, как CPython, так и PyPy, похоже, ведут себя одинаково, поэтому я "Не знаю, что попробовать дальше".

Ответ 1

Кажется, что PyPy имеет некоторые проблемы с кодированием, как при чтении исходного файла (непризнанный заголовок coding, может быть), так и при вводе/выводе в командной строке. Я заменил код примера следующим образом:

>>> from unicode_hack import regex
>>> result = regex(ur'^\p{Ll}\p{L}*').match(u'áÇñ123')
>>> print result.group(0) == u'áÇñ'
True
>>>

И он продолжал работать на CPython и терпеть неудачу на PyPy. Замена "á" для его экранированных символов - u'\xe1\xc7\xf1' - OTOH сделал трюк:

>>> from unicode_hack import regex
>>> result = regex(ur'^\p{Ll}\p{L}*').match(u'\xe1\xc7\xf1123')
>>> print result.group(0) == u'\xe1\xc7\xf1'
True
>>>

Это хорошо работало на обоих. Я считаю, что проблема ограничивается этими двумя сценариями (загрузка источника и командная строка), так как попытка открыть файл UTF-8 с помощью codecs.open отлично работает. Когда я пытаюсь ввести строку "ñ" в командной строке или когда я загружаю исходный код "unicode_hack.py" с помощью codecs, я получаю тот же результат на CPython:

>>> u'áÇñ'
u'\xe1\xc7\xf1'
>>> import codecs
>>> codecs.open('unicode_hack.py','r','utf8').read()[19171:19174]
u'\xe1\xc7\xf1'

но разные результаты на PyPy:

>>>> u'áÇñ'
u'\xa0\u20ac\xa4'
>>>> import codecs
>>>> codecs.open('unicode_hack.py','r','utf8').read()[19171:19174]
u'\xe1\xc7\xf1'

Обновление: Issue1139, отправленное в систему отслеживания ошибок PyPy, посмотрим, как это получится...

Ответ 2

Почему вместо этого вы просто используете рекомендованный Matthew Barnetts модуль regexp?

Он работает как с Python 3, так и с унаследованным Python 2, является заменой на re, обрабатывает все файлы Unicode, которые вы могли бы захотеть, и многое другое.