Zanurkuj w Pythonie/Analiza przypadku: Przetwarzanie numerów telefonów: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Piotr (dyskusja | edycje)
poprawki
Piotr (dyskusja | edycje)
poprawki
Linia 27:
{{samp|('800', '555', '1212')}}
>>> phonePattern.search('800-555-1212-1234') #(3)
>>>
 
# Zawsze odczytujemy wyrażenie regularne od lewej do prawej. Tutaj dopasowujemy początek łańcucha znaków, potem <tt>(\d{3})</tt>. Co to takiego te <tt>(\d{3})</tt>? <tt>{3}</tt> oznacza "dopasuj dokładnie 3 wystąpienia" (jest to wariacja składni <tt>{n, m}</tt>). <tt>\d</tt> oznacza "jakakolwiek cyfra" (od 0 do 9). Umieszczenie ich w nawiasach oznacza "dopasuj dokładnie 3 cyfry, i zapamiętaj je jako grupę, o którą można zapytać później". Następnie mamy dopasować myślnik. Dalej dopasuj kolejną grupę dokładnie trzech cyfr, a następnie kolejny myślnik, i ostatnią grupę tym razem czterech cyfr. Na koniec dopasuje koniec łańcucha znaków.
Linia 42 ⟶ 43:
>>>
>>> phonePattern.search('800-555-1212') #(4)
>>>
 
# To wyrażenie regularne jest praktycznie identyczne z wcześniejszym. Tak jak wcześniej, dopasowujemy początek łańcucha, potem zapamiętywaną grupę trzech cyfr, myślnik, zapamiętywaną grupę trzech cyfr, myślnik i zapamiętywaną grupę czterech cyfr. Nową częścią jest kolejny myślnik i zapamiętywana grupa jednej lub więcej cyfr. Na końcu jak w poprzednim przykładzie dopasowujemy koniec łańcucha.
Linia 51 ⟶ 53:
Następny przykład pokazuje wyrażenie regularne, które obsługuje różne separatory między częściami numeru telefonu.
 
'''{{Python/Przykład
|7.12. |Obsługa różnych separatorów'''
|tekst=
 
>>> phonePattern = re.compile(r'^(\d{3})\D+(\d{3})\D+(\d{4})\D+(\d+)$') #(1)
>>> phonePattern.search('800 555 1212 1234').groups() #(2)
{{samp|('800', '555', '1212', '1234')}}
>>> phonePattern.search('800-555-1212-1234').groups() #(3)
{{samp|('800', '555', '1212', '1234')}}
>>> phonePattern.search('80055512121234') #(4)
>>>
>>> phonePattern.search('800-555-1212') #(5)
>>>
 
# Teraz dopasowujemy początek łańcucha, grupę trzech cyfr, potem <tt>\D+</tt>... zaraz, zaraz, co to jest? <tt>\D</tt> dopasowuje dowolny znak, który nie jest cyfrą, a + oznacza "jeden lub więcej". Więc <tt>\D+</tt> dopasowuje jeden lub więcej znaków nie będących cyfrą. Korzystamy z niego, aby dopasować różne separatory, nie tylko myślniki.
# Korzystanie z <tt>\D+</tt> zamiast z <tt>-</tt> pozwala na dopasowywanie numerów telefonów ze spacjami w roli separatora części.
# Oczywiście myślniki też działają.
# Niestety, to dalej nie jest ostateczna wersja, ponieważ nie obsługuje ona braku jakichkolwiek separatorów.
# No i dalej nie rozwiązany pozostał problem możliwości braku numeru wewnętrznego. Mamy więc dwa problemy do rozwiązania, ale w obu przypadkach rozwiążemy problem tą samą techniką.
}}
 
Następny przykład pokazuje wyrażenie regularne pasujące także do numeru bez separatorów.
 
'''{{Python/Przykład
|7.13. |Obsługa numerów bez separatorów'''
|tekst=
>>> phonePattern = re.compile(r'^(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') #(1)
>>> phonePattern.search('80055512121234').groups() #(2)
{{samp|('800', '555', '1212', '1234')}}
>>> phonePattern.search('800.555.1212 x1234').groups() #(3)
{{samp|('800', '555', '1212', '1234')}}
>>> phonePattern.search('800-555-1212').groups() #(4)
{{samp|<nowiki>('800', '555', '1212', '')</nowiki>}}
>>> phonePattern.search('(800)5551212 x1234') #(5)
>>>
 
# Jedyna zmiana jakiej dokonaliśmy od ostatniego kroku to zamiana wszystkich <tt>+</tt> na <tt>*</tt>. Zamiast <tt>\D+</tt> pomiędzy częściami numeru telefonu dopasowujemy teraz <tt>\D*</tt>. Pamiętasz, że <tt>+</tt> oznacza "1 lub więcej"? <tt>*</tt> oznacza "0 lub więcej". Tak więc teraz jesteśmy w stanie przetworzyć numer nawet bez separatorów.
# Nareszcie działa! Dlaczego? Dopasowany został początek łańcucha, grupa 3 cyfr (800), potem zero znaków nienumerycznych, potem znowu zapamiętywana grupa 3 cyfr (555), znowu zero znaków nienumerycznych, zapamiętywana grupa 4 cyfr (1212), zero znaków nienumerycznych, numer wewnętrzny (1234) i nareszcie koniec łańcucha.
# Inne odmiany też działają np. numer rozdzielony kropkami ze spacją i <tt>x</tt>-em przed numerem wewnętrznym.
# Wreszcie udało się też rozwiązać problem z brakiem numeru wewnętrznego. Tak czy siak <tt>groups()</tt> zwraca nam krotkekrotkę z 4 elementami, ale ostatni jest tutaj pusty.
# Nie cierpię przekazywać złych wieści, aleNiestety jeszcze nie skończyliśmy. Co tutaj jest nie tak? Przed numerem kierunkowym znajduje się dodatkowy znak - <tt>"("</tt>, a nasze wyrażenie zakłada, że numer kierunkowy znajduje się na samym przodzie. Nie ma problemu, możemy zastosować samą metodę co do znaków rozdzielających.
}}
 
Następny przykład pokazuje jak sobie radzić ze znakami wiodącymi w numerach telefonów.
 
'''{{Python/Przykład
|7.14. |Obsługa znaków na początku numeru telefonu'''
|tekst=
 
>>> phonePattern = re.compile(r'^\D*(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') #(1)
>>> phonePattern.search('(800)5551212 ext. 1234').groups() #(2)
{{samp|('800', '555', '1212', '1234')}}
>>> phonePattern.search('800-555-1212').groups() #(3)
{{samp|<nowiki>('800', '555', '1212', '')</nowiki>}}
>>> phonePattern.search('work 1-(800) 555.1212 #1234') #(4)
>>>
 
# Wzorzec w tym przykładzie jest taki sam jak w poprzednim, z wyjątkiem tego, że teraz na początku łańcucha dopasowujemy <tt>\D*</tt> przed pierwszą zapamiętywaną grupą (numerem kierunkowym). Zauważ że tych znaków nie zapamiętujemy (nie są one w nawiasie). Jeśli je napotkamy, to ignorujemy je i przechodzimy do numeru kierunkowego.
# Teraz udało się przetworzyć numer telefonu z nawiasem otwierającym na początku. (Zamykający był już wcześniej obsługiwany; był traktowany jako nienumeryczny znak pasujący do teraz drugiego <tt>\D*</tt>.)
# Tak na wszelki wypadek sprawdzamy czy nie popsuliśmy czegoś. Jako, że początkowy znak jest całkowicie opcjonalny, następuje dopasowanie w dokładnie taki sam sposób jak w poprzednim przykładzie.
# W tym miejscu wyrażenia regularne sprawiają, że chce się człowiekowi rozbić bardzo dużym młotem monitor. Dlaczego to nie pasuje? Wszystko za sprawą 1 przed numerem kierunkowym (numer kierunkowy USA), a przecież przyjęliśmy, że na początku mogą być tylko nienumeryczne znaki. Ech...
}}
 
Cofnijmy się na chwilę. Jak naraziena razie wszystkie wyrażenia dopasowywaliśmy od początku łańcucha. Ale teraz widać wyraźnie, że na początku naszego łańcucha mamy nieokreśloną liczbę znaków których kompletnie nie potrzebujemy. Po co mamy więc dopasowywać początek łańcucha? Jeśli tego nie zrobimy, to przecież pominie on tyle znaków ile mu się uda, a przecież o to nam chodzi. Takie podejście prezentuje następny przykład.
 
'''Przykład 7.15. Numerze telefonu, znajdę cię gdziekolwiek jesteś!'''
 
{{Python/Przykład
'''Przykład |7.15. |Numerze telefonu, znajdę cię gdziekolwiek jesteś!'''
|tekst=
>>> phonePattern = re.compile(r'(\d{3})\D*(\d{3})\D*(\d{4})\D*(\d*)$') #(1)
>>> phonePattern.search('work 1-(800) 555.1212 #1234').groups() #(2)
{{samp|('800', '555', '1212', '1234')}}
>>> phonePattern.search('800-555-1212') #(3)
{{samp|<nowiki>('800', '555', '1212', '')</nowiki>}}
>>> phonePattern.search('80055512121234') #(4)
{{samp|('800', '555', '1212', '1234')}}
 
# Zauważ, że brakuje <tt>^</tt> w tym wyrażeniu regularnym, Teraz już nie dopasowujemy początku łańcucha, bo przecież nikt nie powiedział, że wyrażenie musi pasować do całego łańcucha, a nie do fragmentu. Mechanizm wyrażeń regularnych sam zadba o namierzenie miejsca do którego ono pasuje (o ile w ogóle).
# Teraz nareszcie pasuje numer ze znakami na początku (w tym cyframi) i dowolnymi, jakimikolwiek separatorami w środku.
# Na wszelki wypadek sprawdzamy i to. Działa!
# To też działa.
}}
 
WidziszWidzimy, jak szybko wyrażenia regularne wymykają się spod kontroli? RzućRzućmy okiem na jedną z poprzednich iteracjiprzykładów. WidziszWidzimy różnice pomiędzy niąnim i następnąnastępnym?
 
Póki jeszcze rozumiemy to co napisaliśmy, rozpiszmy to jako rozwlekłe wyrażenie regularne, żeby nie zapomnieć, co jest co i dlaczego.
 
'''{{Python/Przykład
|7.16. |Przetwarzanie numerów telefonu (wersja finalna)'''
|tekst=
 
<nowiki>>>> <nowiki>phonePattern = re.compile(r'''
# nie dopasowuj początku łańcucha, numer może się zacząć gdziekolwiek
(\d{3}) # numer kierunkowy - 3 cyfry (np. '800')
Linia 138 ⟶ 150:
(\d*) # numer wewnętrzny jest opcjonalny i może mieć dowolną długość
$ # koniec łańcucha
''', re.VERBOSE)</nowiki>
>>> phonePattern.search('work 1-(800) 555.1212 #1234').groups() #(1)
{{samp|('800', '555', '1212', '1234')}}
>>> phonePattern.search('800-555-1212') #(2)
{{samp|<nowiki>('800', '555', '1212', '')</nowiki>}}</nowiki>
 
# Pomijając fakt, że jest ono podzielone na wiele linii, to wyrażenie jest dokładnie takie samo jak po ostatnim kroku, więc nie jest niespodzianką, że dalej działa jak powinno.
# Jeszcze jedna próba. Tak, działa! Skończone!
}}
 
<noinclude>
{{Nawigacja|Zanurkuj w Pythonie|