Как да анализирам преносимо (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 Consortium!   -  person Donal Fellows    schedule 21.01.2012


Отговори (1)


Възможно преносимо решение:

Преобразувайте входните данни в unicode и използвайте флага 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 вече посочи това в коментари за въпрос.

Актуализация

Бележки от J.F. Себастиан коментира кодирането на входа:

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: за конвертиране на входни данни в unicode. Между другото, в този случай ще получите същия резултат без 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'), преди да извършите търсене с regexp. - 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