Formatowanie łańcuchów znaków w Pythonie
W języku Python istnieją cztery podstawowe metody formatowania łańcuchów znaków (od wersji 3.6 wzwyż). W niniejszym wpisie przedstawię te metody, omówię ich wady i zalety, a w podsumowaniu udzielę przydatnych wskazówek, którą z tych metod (i w jakiej sytuacji) najlepiej stosować.
Zanim przejdę do przedstawienia szczegółów, chciałbym pokazać pewną sytuację. Załóżmy, żę mamy następujące zmienne z przypisanymi wartościami:
naszym zadaniem jest wygenerować poniższy łańcuch znaków w formie komunikatu:
Zrealizujemy to zadanie wszystkimi czterema metodami, tak aby te metody lepiej poznać.
1. Formatowanie łańcuchów “starą” metodą
Łańcuchy znaków w Pythonie są reprezentowane przez obiekty klasy str. Tworzymy je poprzez umieszczenie napisu w cudzysłowach:
- pojedynczych: ‘this is a string’
- podwójnych: “this is a string”
- potrójnych: ‘'’this is a string’’’ lub “"”this is a string”””
Potrójne cudzysłowy pozwalają także na tworzenie łańcuchów wielolinijkowych. Łańcuchy znaków w Pythonie są niezmienne (ang. immutable), co oznacza, że nie możemy ich samych w sobie zmieniać.
Na łańcuchach znaków w Pythonie możemy wykonywać specjalne działanie z wykorzystaniem operatora %. Dzięki temu operatorowi w łatwy sposób mamy możliwość formatowania wynikowej zawartości. To działanie jest bardzo podobne do działania funkcji printf z języka C. Zobaczmy to na przykładzie:
Rezultatem wykonania tego wyrażenia jest łańcuch znaków, w którym specjalna sekwencja formatująca %s została zastąpiona wartością znajdującą się w zmiennej user_name.
Do dyspozycji mamy wiele specyfikatorów formatu, np. dla liczb całkowitych użyjemy %d, a dla zmiennoprzecinkowych %f. Po szczegóły dotyczące dostępnych specyfikatorów i ich sposobów użycia odsyłam do dokumentacji języka Python.
W sytuacji gdy potrzebujemy zastosować więc niż jeden specyfikator formatu musimy zastosować nieco inną składnię:
Możemy też tworzyć nazwane specyfikatory formatu i jawnie przekazywać im pożądane wartości za pomocą słownika:
To ostatnie rozwiązanie jest bardziej elastyczne, gdyż umożliwia łatwiejsze rozbudowanie takiego łańcucha w przyszłości, a także nie wymusza na nas dbania o prawidłową kolejność przekazywanych zmiennych.
2. Formatowanie łańcuchów “nową” metodą
Wraz z wersją Pythona z linii 3 twórcy języka wprowadzili “nową” metodę do formatowania łańcuchów znaków. Ta “nowa” metoda pozwala nam zrezygnować z używania operatora %, przez co składnia naszego kodu staje się bardziej przejrzysta i czytelniejsza. Do klasy str dodano dodatkową metodę format(), dzięki wywołaniu której możemy wpływać na wynikową postać sformatowanego łańcucha. Wspomnieć tutaj należy, że metoda ta została później backportowana do Pythona 2 aby i w tej wersji języka można było z niej korzystać. Zobaczmy na prostym przykładzie jak wygląda teraz formatowanie:
Tworzymy obiekt klasy str i wywołujemy jego metodę format(), której zadaniem jest w tym przypadku, zastąpienie sekwencji {} wartością argumentu metody format. Wracając do naszego zadania, kod programu mógłby wyglądać następująco :
Kolejne wystąpienia sekwencji {}, są odpowiednio zastępowane wartościami argumentów przekazywanych do metody format. Możemy skorzystać, tak jak w przypadku starej metody, z podstawiania zmiennych, aby nie musieć martwić się o kolejność argumentów. W łatwy też sposób możemy zamieniać kolejność wyświetlanych elementów, bez zmiany kolejności argumentów w metodzie format():
W przypadku tej metody nie mogło zabraknąć też możliwości określania sposobu wyświetlania wartości w sekwencji formatującej. Aby wyświetlić np. liczbę całkowita jako jej reprezentację szesnastkową napiszemy:
Składnia sekwencji formatującej jest dużo bardziej rozbudowana i daje większe możliwości niż w starej metodzie ale jednocześnie jest łatwa do używania w prostszych przypadkach. W celu zgłębienia szczegółów zachęcam do zapoznania się z dokumentacją.
Pisząc programy w Pythonie 3 oficjalna dokumentacja zaleca korzystać z nowszych metod formatowania łańcuchów zamiast starej metody. Stara metoda nadal może być wykorzystywana, nadal jest (i będzie) wspierana w kolejnych wydaniach Pythona.
3. Interpolacja łańcuchów
Interpolacja łańcuchów znaków (f-strings) została wprowadzona po raz pierwszy w Pythonie wraz z wersją 3.6. Pisząc krótko, ten nowy sposób pozwala nam osadzać wyrażenia Pythona wewnątrz stałych łańcuchów znaków. Zobaczmy jak wygląda najprostszy przykład:
Jak widać na powyższym - nowością jest literka f przed łańcuchem znaków (stąd nazwa f-strings). Jest to bardzo elastyczny sposób formatowania, choć niosący pewne ograniczenia. Przykładem elastyczności jest fakt, że można w łańcuchach znaków osadzać wyrażenia. Możemy dzięki temu wykonywać operacje arytmetyczne wewnątrz:
Oprócz wyrażeń możemy także bezpośrednio wywoływać funkcje/metody:
Korzystając z f-stringów możemy używać też obiektów utworzonych z klas. Weźmy dla przykładu następującą prostą klasę:
I na jej podstawie napiszemy:
Magiczne metody __str__() i __repr__() odpowiadają za reprezentację łańcuchową obiektu. Przynajmniej jedną z nich powinniśmy umieścić w definicji klasy. Jeśli musiałbyś wybierać którą z tych dwóch metod implementować, to wybierz ___repr()___, gdyż może ona być używana w zastępstwie ___str__()_.
Łańcuch znaków zwracany przez metodę __str__() jest nieformalną (informacyjną) reprezentacją znakową obiektu i powinien być czytelny dla nas programistów. Łańcuch znaków zwracany przez metodę __repr__() jest to formalna (oficjalna) reprezentacja napisowa obiektu i powinna być jednoznaczna, a otrzymany łańcuch znaków powinien być poprawnym wyrażeniem w Pythonie. W metodzie interpolacji łańcuchów domyślnie używana jest metoda __str__() (podobnie ma to miejsce w przypadku format()), aby używać metody __repr__() przy formatowaniu musimy jawnie wyspecyfikować flagę konwersji !r:
W tym miejscu każdy z Was powinien już umieć napisać kod realizujący nasze zadanie za pomocą f-stringów. Dla formalności zobaczmy jak to powinno wyglądać:
Prawda, że proste?
Dla osób czujących potrzebę zanurkowania głębiej podaję link do szczegółów specyfikacji.
4. Szablony
W języku Python istnieje jeszcze jedna metoda na formatowanie łańcuchów znaków - są to szablony. Jest to prosta i mniej elastyczna metoda, jednak w pewnych przypadkach może być bardzo pomocna. Popatrzmy na prosty fragment kodu aby przybliżyć sobie zasady jakie panują dla tej metody:
Na początku importujemy klasę Template z wbudowanego w język Python modułu string, następnie tworzymy obiekt tej klasy przekazując do inicjalizera łańcuch znaków będący szablonem, a następnie dokonać podstawienia wartości. W tej metodzie, w odróżnieniu od pozostałych, nie występują specyfikatory formatów, dlatego musimy wcześniej sami zatroszczyć się o odpowiednie konwersje i przekazać przygotowane już wartości w zmiennych do podstawienia.
Rodzi się zatem pytanie, kiedy używać tej metody w programach? W mojej opinii - wszędzie tam gdzie przetwarzamy łańcuchy znaków otrzymane od użytkowników programu. Z racji ograniczonej funkcjonalności wykorzystywanie szablonów w tym wypadku będzie najbezpieczniejszym wyborem. Jak się okazuje bardziej rozbudowane metody formatowania mogą w takiech sytuacjach stanowić luki bezpieczeństwa w aplikacji. Złośliwy użytkownik może tak przygotować łańcuch znaków do formatowania aby wykraść nam jakieś wrażliwe dane. Zobaczmy jakby to mogło wyglądać:
Ups.. Potencjalny atakujący może dostać sie do słownika ___globals___ poprzez ciąg formatujący. Powtórzmy teraz atak dla szablonów:
Nic z tego, nie udało nam się wykraść tajnych danych tym sposobem.
No dobrze, to której metody mam używać?
Poznaliśmy już dostępne metody formatowania łańcuchów i zasady ich działania w Pythonie. Teraz przyszła pora na podsumowanie. Wobec mnogości dostępnych metod mogą pojawić się wątpliwości co do tego, której metody używać. O części zasad jakimi się kierować przy wyborze metody już wspomniałem wyżej, ale chciałbym jeszcze pokazać to w formie jednej praktycznej porady:
Jeśli formatowane łańcuchy pochodzą od użytkowników używamy szablonów (#4) ze względów bezpieczeństwa. W przeciwnym wypadku używamy Interpolacji łańcuchów (f-strings) (#3) jeśli piszemy kod dla Pythona 3.6+, albo “nowej” metody (#2) dla starszych wersji języka.
Na zakończenie przyjrzyjmy się jeszcze wydajności prezentowanych metod. Do zbadania i porównania wydajności napiszemy prosty program, który zmierzy czasy formatowania łańcuchów znaków dla każdej z metod. Skorzystamy tutaj z modułu timeit i przy jego pomocy będziemy uruchamiać metody określoną ilość razy.
Wykonaliśmy dla każdej metody 5 serii po 5 milionów podstawień i otrzymaliśmy następujące wyniki:
Same wartości czasowe dla poszczególnych metod nie są o tyle istotne, co różnice między nimi. Widać wyraźnie, że pod względem wydajnościowym najlepiej wypada metoda #3 interpolacji łańcuchów, a metoda z wykorzystaniem szablonów jest zdecydowanie najmniej wydajna szybkościowo. Metoda .format() jest nieco mniej wydajna w porównaniu do operatora ‘’%’’.
Zostaw komentarz