F-strings to intuicyjny i prosty sposób na formatowanie łańcuchów znaków w Pythonie. Ucząc się programowania przez ostatnie kilka lat bez trudu mogłem zauważyć tendencję do odchodzenia od dawniejszych metod formatowania łańcuchów znaków właśnie na rzecz f-strings. Nie twierdzę, że pozostałe metody czyli formatowanie łańcuchów znaków przy pomocy operatora „%” oraz str.format() stały się nieistotne – wręcz przeciwnie, przy ich użyciu napisano wiele gigabajtów dokumentacji i ich znajomość jest niezbędna, jednak w praktyce te wcześniej stosowane sposoby formatowania wydają się wychodzić z użycia. Temat ten zaciekawił mnie na tyle, że postanowiłem przeszukać Internet, by dowiedzieć się, czy mimo wszystko wciąż występują sytuacje, w których f-strings powinniśmy unikać.
Zacznę od podstaw – załóżmy, że chcemy wyświetlić komunikat, w którym będą zawierały się jakieś zmienne z uprzednio przypisanymi wartościami i chcemy do tego celu wykorzystać f-strings:
from datetime import date
def count_age(birthday):
today = date.today()
age = today.year - birthday.year - ((today.month, today.day) < (birthday.month, birthday.day))
return age
person = "Andrzej"
birthday = date(1980, 5, 20)
print(f"{person} urodził/a się {birthday.day}.{birthday.month}.{birthday.year} r., a zatem ma {count_age(birthday)} lat/a.")
A teraz spójrzmy jak wyglądałoby to przy użyciu poprzednich metod:
print("%s urodził/a się %d.%d.%d r., a zatem ma %d lat/a." % (person, birthday.day, birthday.month, birthday.year, count_age(birthday)))
print("{} urodził/a się {}.{}.{} r., a zatem ma {} lat/a.".format(person, birthday.day, birthday.month, birthday.year, count_age(birthday)))
Co może zwrócić naszą uwagę? Przede wszystkim czytelność i brak powtórzeń przy f-strings. Kod z f-strings już teraz jest krótszy – różnica może zwiększać się lub zmniejszać w zależności od ilości zmiennych, z których będziemy korzystać.
Jak wynika z powyższego przykładu przy pomocy f-strings można nie tylko wyświetlać proste zmienne, ale również wywoływać funkcje. Zresztą możliwości jest więcej – np. obliczanie wyrażeń matematycznych.
Przejdę teraz do wyjątków.
1.
Co do zasady powinniśmy unikać jakiegokolwiek formatowania łańcuchów znaków używając SQL z Pythonem, aby uniknąć ryzyka SQL-injection.
Za przykład niech posłuży PostgreSQL. Sposób działania mamy opisany w dokumentacji biblioteki psycopg2, która stanowi ponoć najbardziej popularny adapter dla tej bazy danych.
https://www.psycopg.org/docs/usage.html#query-parameters
from psycopg2 import sql
cur.execute(
sql.SQL("insert into {} values (%s, %s)")
.format(sql.Identifier('my_table')),
[10, 20])
Sam kod zadziałałby również z f-strings i jeżeli jesteśmy na 100% pewni, że ze względu na ograniczony sposób jego wykorzystania nie powinien być narażony na żaden atak możemy zdecydować się na użycie formatowania łańcuchów znaków. Zasadniczo jednak, jeżeli osoby tworzące dokumentację zalecają używać narzędzi w określony sposób, to robią tak z jakiegoś istotnego powodu i lepiej ich posłuchać.
2.
Teraz dwa linki ze stackoverflow. Autor pierwszej odpowiedzi (Dimitris Fasarakis Hilliard) zauwazył, że zgodnie z dokumentacją f-strings, wewnątrz nawiasów klamrowych nie można używać ukośników wstecznych („\”). W sytuacji gdy są one dla nas konieczne, wygodniejsze może być użycie str.format() albo próba „obejścia systemu” poprzez przypisanie wyrażenia z ukośnikiem wstecznym do zmiennej i wprowadzenie dopiero tej zmiennej do nawiasu klamrowego. W odpowiedziach na wyżej zalinkowane pytanie znajdują się jeszcze inne możliwe rozwiązania, warto się zapoznać.
Przy okazji mogę dodać, że zgodnie z dokumentacją, w nawiasach klamrowych f-strings nie można również używać znaku „#”.
3.A.
Tworzenie logów. Nie mam doświadczenia w tej materii, przytoczę zatem tylko, odpowiedź jednego z autorów (luk2302), który twierdzi, że jeżeli rejestrator nie jest skonfigurowany do tworzenia logów INFO, używanie f-strings będzie skutkowało potencjalnie kosztowną interpolacją argumentów na łańcuchy znaków.
3.B.
Pod powyższym linkiem znajduje się więcej odpowiedzi, autor drugiej (Pedro Maia) przytomnie twierdzi, że w sytuacji gdy mamy jakiś szablon złożony z łańcuchów znaków i chcemy wykorzystywać go na wiele różnych sposobów lepiej będzie zastosować formatowanie za pomocą operatora %.
3.C.
Autor(ka?) trzeciej odpowiedzi („user2357112 supports Monica”) napisał(a?) z kolei,że f-strings nie są dobrym pomysłem w sytuacji gdy dane oprogramowanie ma mieć więcej wersji językowych. Łańcuch znaków f-strings jest zakodowany w kodzie źródłowym programu i nie ma możliwości, aby dokonać jego dynamicznej zamiany na przetłumaczony szablon na podstawie ustawień językowych użytkownika.
Jeżeli znane są komuś jakieś inne wyjątki to bardzo proszę o komentarz.
Z powyższego wynika, że odstępstwa lub problemy z f-strings mogą dotyczyć zarówno kwestii drobnych jak i całkiem poważnych. Wiele zależy od tego jakie funkcje ma spełniać nasz kod. Warto pamiętać o dawnych metodach formatowania znaków, ale o ile nie spotykamy się, z którymś z opisanych wyżej zagadnień najłatwiej będzie jednak używać f-strings.
PEP 498 czyli propozycja ulepszenia Pythona nr 498 znajduje się pod poniższym linkiem:
https://peps.python.org/pep-0498/
Polecam przynajmniej przejrzenie tej dokumentacji, bo doskonale pokazuje sposób myślenia jaki towarzyszy zmianom dokonywanym w tym języku programowania, przykład f-strings jest o tyle symptomatyczny, że używając Pythona z formatowaniem łańcuchów znaków spotykamy się bardzo często.
Dla mnie osobiście budująca i motywująca jest idea ulepszeń, która wynika z autentycznej potrzeby lub dążenia, by ułatwić innym korzystanie z danego narzędzia, a nie czyjegoś „widzi mi się” – zmiana dla zmiany. W przyszłości napiszę o tym szerzej.
Co ciekawe obecnym, a zarazem pierwszym deweloperem-rezydentem Fundacji Pythona, który czuwa nad tymi sprawami jest Polak – Łukasz Langa. W zeszłym roku był gościem podcastu Python Talk To Me, gdzie udzielił interesującego wywiadu.
https://talkpython.fm/episodes/show/331/meet-the-python-developer-in-residence-lukasz-langa