Как переносимо анализировать символ степени (Unicode) с помощью регулярных выражений?

Я пишу простой анализатор регулярных выражений для вывода утилиты sensors в Ubuntu. Вот пример строки текста, которую я разбираю:

temp1:        +31.0°C  (crit = +107.0°C)

И вот регулярное выражение, которое я использую для соответствия (в Python):

temp_re = re.compile(r'(temp1:)\s+(\+|-)(\d+\.\d+)\W\WC\s+' 
                     r'\(crit\s+=\s+(\+|-)(\d+\.\d+)\W\WC\).*')

Этот код работает, как и ожидалось, и соответствует тексту примера, который я привел выше. Единственные биты, которые меня действительно интересуют, - это числа, поэтому этот бит:

(\+|-)(\d+\.\d+)\W\WC

который начинается с совпадения знака + или - и заканчивается совпадением °C.

Мой вопрос: почему для соответствия ° требуется два символа \W (не буквенно-цифровых), а не один? Не сломается ли код в системах, где Unicode представлен не так, как у меня? Если да, то как я могу сделать его портативным?


person snim2    schedule 21.01.2012    source источник
comment
С флагом re.UNICODE RE не совпадает ни с \W\WC, ни с \WC. Или я вас неправильно понял?   -  person snim2    schedule 21.01.2012
comment
Также есть «», который представляет собой одиночный символ, обозначающий градусы Цельсия. Большое спасибо, Консорциум Unicode!   -  person Donal Fellows    schedule 21.01.2012


Ответы (1)


Возможное портативное решение:

Преобразуйте входные данные в юникод и используйте флаг re.UNICODE в регулярных выражениях.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re


data = u'temp1:        +31.0°C  (crit = +107.0°C)'
temp_re = re.compile(ur'(temp1:)\s+(\+|-)(\d+\.\d+)°C\s+' 
                     ur'\(crit\s+=\s+(\+|-)(\d+\.\d+)°C\).*', flags=re.UNICODE)

print temp_re.findall(data)

Выход

[(u'temp1:', u'+', u'31.0', u'+', u'107.0')]

ИЗМЕНИТЬ

@netvope уже указал на это в комментариях к вопросу.

Обновить

Примечания от Дж.Ф. Себастьян комментирует кодировку ввода:

check_output() возвращает двоичные данные, которые иногда могут быть текстом (в этом случае он должен иметь известную кодировку символов, и вы можете преобразовать его в Unicode). В любом случае ord(u'°') == 176, поэтому его нельзя закодировать с помощью кодировки ASCII.

Итак, чтобы декодировать входные данные в unicode, в основном * вы должны использовать кодирование из локали системы, используя locale.getpreferredencoding(), например:

data = subprocess.check_output(...).decode(locale.getpreferredencoding())

С данными, закодированными правильно:

в этом случае вы получите тот же результат без re.UNICODE.


Почему в основном? Потому что на русской Win7 с cp1251 как preferredencoding, если у нас есть, например, script.py, который декодирует вывод в utf-8:

#!/usr/bin/env python
# -*- coding: utf8 -*-

print u'temp1: +31.0°C  (crit = +107.0°C)'.encode('utf-8')

И нам нужно проанализировать его вывод:

subprocess.check_output(['python', 
                         'script.py']).decode(locale.getpreferredencoding())

приведет к неверным результатам: 'В°' вместо °.

Поэтому в некоторых случаях вам нужно знать кодировку входных данных.

person reclosedev    schedule 21.01.2012
comment
Конечно, но полный рабочий пример для такого рода вещей всегда хорошая идея. Многим программистам сложно правильно работать с Unicode, даже когда доступны все возможности :( - person Karl Knechtel; 21.01.2012
comment
+1: для преобразования входных данных в юникод. Кстати, в этом случае вы получите тот же результат без re.UNICODE. - person jfs; 21.01.2012
comment
Спасибо за это. Я играл с этим. В реальном коде данные фактически поступают из вывода вызова subprocess.check_output, который возвращает свои данные в виде ASCII, а не Unicode, так что это не совсем работает. Возможно, более разумным было бы перейти на Python3, где все в Unicode? Хм. - person snim2; 21.01.2012
comment
@snim2: check_output() возвращает двоичные данные, которые иногда могут быть текстом (у которого в данном случае должна быть известная кодировка символов, и вы можете преобразовать его в Unicode). В любом случае ord(u'°') == 176 его нельзя закодировать, используя кодировку ASCII. Python 3 не может вам помочь, потому что check_output может возвращать данные, которые не имеют смысла в виде текста, например, двоичные данные изображения (вы не можете преобразовать их в Unicode; нет связанной кодировки символов). - person jfs; 21.01.2012
comment
@ J.F.Sebastian Да, извините, вы совершенно правы. Я запутался, так как вызов .split(\n) на выходе check_output прекрасно работает в Python2, поэтому легко забыть, что происходит нечто большее. - person snim2; 21.01.2012
comment
@ snim2, в данный момент у меня нет доступа к машине Ubuntu с sensors, но, вероятно, вам следует декодировать вывод с subprocess.check_output, используя локаль системы. например: предположим, что data является результатом check_output, затем data = data.decode('utf-8') перед выполнением поиска по регулярному выражению. - person reclosedev; 21.01.2012
comment
@reclosedev: вы могли иметь в виду locale.getpreferredencoding(). Он полностью независим от исходной кодировки, используемой для скрипта. - person jfs; 21.01.2012
comment
Хорошо, спасибо всем за помощь. Я создал суть окончательного результата здесь: gist.github.com/1652633 — все еще нужно немного поработать, но синтаксический анализ кажется точным :-) - person snim2; 21.01.2012
comment
@ J.F.Sebastian: Да, спасибо за очень полезные заметки, я добавлю их в ответ. - person reclosedev; 21.01.2012