#python #регулярные_выражения #кодировка #python_2x #unicode
Поясню сразу: я знаю, что python 2 требует явного объявления строк как юникодовых. Понимаю, что это и не должно работать корректно. Мне интересна "анатомия" поломки. Что именно внутри re.compile() и regex.search() производит такой результат? Судя по нижеприведённому коду, диапазон 'а-яё' не включает в себя диапазон 'р-ю', зато диапазон 'р-ю' включает 'ё'. mcve.py: # coding=utf-8 import re # Это панграмма, она содержит все буквы алфавита test = 'широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства' regex1 = re.compile('[а-яА-ЯёЁ\s]+') regex2 = re.compile('[а-яА-ЯёЁшьрэтфцюыхущчъ\s]+') regex3 = re.compile('[а-яёшьрэтфцюыхущчъс\s]+') regex4 = re.compile('[а-яр-ю\s]+') print regex1.search(test).group() print regex2.search(test).group() print regex3.search(test).group() print regex4.search(test).group() Результат: � широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства Я убедился, что все буквы алфавита от "А" до "Я" и от "а" до "я" идут в Юникоде подряд, кроме "Ёё", которые явным образом добавлены в регулярное выражение. Постепенно добавляя буквы, на которых прерывается поиск по первому выражению, я пришёл к диапазону а-яА-ЯёЁшьрэтфцюыхущчъ. Если отсортировать добавленные буквы, получается почти сплошной интервал: "ртуфхцчшщъыьэю". Если убрать заглавные буквы, т.е. "[А-ЯЁ]", то неожиданным образом поиск прерывается на "с". Интервал становится сплошным: с "р" и до "ю". Это regex3. И наконец оказывается, что теперь интервал можно свернуть и даже убрать "ё" (regex4). Что вообще происходит? python --version Python 2.7.6 Если явным образом сделать юникодовую строку и регулярное выражение, то всё работает как должно. Но ведь как-то работает и без этого. Объясните, как? test2 = u'широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства' regex5 = re.compile(u'[а-яА-ЯёЁ\s]+')
Ответы
Ответ 1
Самый простой способ посмотреть, что же не так с регуляркой - поставить debug флаг. Причем в таком случае абсолютно нет нужды лезть в потроха - они покажут то же самое. В этом можно убедиться, если залезть руками в %python_folder%/Lib/sre_parse.py и добавить там пару print-ов на строку 438 - выглядит она как-то тактак: elif this == "[": # character set set = [] setappend = set.append ## if sourcematch(":"): ## pass # handle character classes if sourcematch("^"): setappend((NEGATE, None)) # check remaining characters start = set[:] while 1: this = sourceget() if len(this) == 1: # Вот ровно в этом месте парсер перебирает содержимое [] print(source.tell(), "ORD: ", ord(this)) if this == "]" and set != start: break Так вот этот print покажет все абсолютно то же самое, что и debug - то что буковки на самом деле не буковки. regex1 = re.compile('[а-яА-ЯёЁ\s]+', re.DEBUG) max_repeat 1 2147483647 in literal 208 range (176, 209) literal 143 literal 208 range (144, 208) literal 175 literal 209 literal 145 literal 208 literal 129 category category_space Сразу видно, что что-то совсем нечисто, потому что первым должен быть range(ord('a')-ord('я'), а вместо него чепуха какая-то. А все от того, что строки в кодировке UTF8 (указана явно в файле), а тип у них - байты. Они могут отображаться нормально в терминале, если кодировки совпадают. Я пользуюсь Pycharm и его консоль в UTF8. Но если бы я запустил то же самое на стандартном терминале винды, то отобразилась бы натурально каша (типа такой - ╨Я╤А╨╕╨▓╨╡╤В), ибо кодировка CP866. Например, print("Привет") # НО! for char in "Привет": print(ord(char), repr(char)) В debug выводе число 208 - это первая половинка UTF8 символа 'а' - 176 - вторая половинка, затем следует дефис и снова первая половинка символа 'я' - в этом можно убедиться, открыв исходник в любом HEX редакторе. В итоге неверный интервал. Соответственно, когда парсер перебирает содержимое квадратных скобок он натыкается не на буквы, а на байты, а точнее на половинки букв в кодировке UTF8. Можно симулировать поведение регулярки таким кодом: reg_min = 144 reg_max = 209 result = [] for char in test: ordedr = ord(char) if ordedr >= reg_min and ordedr <= reg_max: result.append(char) else: break print(ord(regex1.search(test).group())) print(list(map(ord, result))) >>> 209 >>> [209]Ответ 2
Нативная поддержка Unicode в Python - только с третьей версии. Используйте ее. Обратите еще внимание на первые две строчки исправленного файла #!/usr/bin/python3 # -*- coding: utf-8 -*- import re # Это панграмма, она содержит все буквы алфавита test = 'широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства' regex1 = re.compile('[а-яА-ЯёЁ\s]+') regex2 = re.compile('[а-яА-ЯёЁшьрэтфцюыхущчъ\s]+') regex3 = re.compile('[а-яёшьрэтфцюыхущчъс\s]+') regex4 = re.compile('[а-яр-ю\s]+') print(regex1.search(test).group()) print(regex2.search(test).group()) print(regex3.search(test).group()) print(regex4.search(test).group()) Вывод: gaal@linux-t420:~/WORK/test> ./1.py широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства широкая электрификация южных губерний даст мощный толчок подъёму сельского хозяйства широкая электрификация южных губерний даст мощный толчок подъ
Комментариев нет:
Отправить комментарий