2017-08-15 41 views
13

Piszę funkcję dzielenia liczb i niektórych innych rzeczy z tekstu w python. Kod wygląda mniej więcej tak:Python 2 i 3 're.sub' niespójność

EN_EXTRACT_REGEX = '([a-zA-Z]+)' 
NUM_EXTRACT_REGEX = '([0-9]+)' 
AGGR_REGEX = EN_EXTRACT_REGEX + '|' + NUM_EXTRACT_REGEX 

entry = re.sub(AGGR_REGEX, r' \1\2', entry) 

Teraz ten kod działa perfekcyjnie w python3, ale to nie działa pod python2 i uzyskać „niezrównany grupa” błąd.

Problem polega na tym, że muszę obsługiwać obie wersje i nie mogłem sprawić, by działało poprawnie w python2, chociaż próbowałem na różne sposoby.

Jestem ciekawy, co może być przyczyną tego problemu i czy istnieje jakieś obejście tego problemu?

Odpowiedz

10

Myślę, że problemem może być to, że wzór regex dopasowuje jeden lub drugiego z podwzorów EN_EXTRACT_REGEX i NUM_EXTRACT_REGEX, ale nie jednocześnie.

Gdy re.sub() pasuje do znaków alfa w pierwszym wzorze, próbuje zastąpić odwołanie do drugiej grupy przez \2, które nie działa, ponieważ tylko pierwsza grupa jest zgodna - nie ma drugiej grupy.

Podobnie, gdy dopasowany jest wzór cyfry, grupa \1 nie jest substytutem, a więc również się nie powiedzie.

Widać, że to jest w przypadku tego testu w Pythonie 2:

>>> re.sub(AGGR_REGEX, r' \1', 'abcd') # reference first pattern 
abcd 
>>> re.sub(AGGR_REGEX, r' \2', 'abcd') # reference second pattern 
Traceback (most recent call last): 
.... 
sre_constants.error: unmatched group 

Różnica musi znajdować się w różnych wersjach silnika regex dla Python 2 i Python 3. Niestety nie mogę zapewnić ostateczne powodem różnicy, jednak nie jest to udokumentowane zmiana w wersji 3.5 dla re.sub() dotyczące grup niedopasowanych:

Zmieniono w wersji 3.5: grupy Niezrównane są zastąpione pustym ciągiem.

wyjaśnia, dlaczego działa w języku Python> = 3.5, ale nie we wcześniejszych wersjach: niedopasowane grupy są zasadniczo ignorowane.


Jako obejście można zmienić wzór obsługiwać oba mecze jako jedna grupa:

import re 

EN_EXTRACT_REGEX = '[a-zA-Z]+' 
NUM_EXTRACT_REGEX = '[0-9]+' 
AGGR_REGEX = '(' + EN_EXTRACT_REGEX + '|' + NUM_EXTRACT_REGEX + ')' 
# ([a-zA-Z]+|[0-9]+) 

for s in '', '1234', 'abcd', 'a1b2c3', 'aa__bb__1122cdef', '_**_': 
    print(re.sub(AGGR_REGEX, r' \1', s)) 

Output

 

1234 
abcd 
a 1 b 2 c 3 
aa__ bb__ 1122 cdef 
_**_ 
+0

Dzięki, to działa dobrze w obu wersjach. :) –