Wąż w stringach

Jednym z pierwszych typów danych o których uczymy się w ramach najróżniejszych kursów programowania jest typ łańcuchowy – często określany jako string. Nie bez powodu jest to jeden z najczęściej używanych w programowaniu typów. A dlaczego? Ponieważ jako ludzie głównie działamy na tekście: czytamy książki, piszemy listy, ostrzegamy o wysokim napięciu, opisujemy substancje chemiczne, gryzmolimy tagi na rozkładach jazdy (kto wracał komunikacją nocną i nie mógł odczytać rozkładu przez głupie tagi wie, że tego typu teksty są najmniej lubiane) – w dużym skrócie – tekstu używamy wszędzie.

Oczywiście ma to swoje konsekwencje -w programowaniu wykorzystujemy tekst aby wyświetlić cokolwiek na ekranie, do logowania aktualnego stanu aplikacji, gdy prosimy użytkownika o podanie informacji, przesyłamy informacje przez sieć.

Łańcuch znaków jest po prostu kolekcją… znaków. Mogą to być zarówno znaki drukowalne, jak np. litery alfabetu, cyfry czy znaki interpunkcyjne lub niedrukowalne – znaki o specjalnym znaczeniu – spacje, tabulacje, „dzwonek systemowy”.
Stringi mogą mieć długość od zera do… prawie rozmiaru pamięci zainstalowanej w komputerze. Oczywiście, mają tutaj jeszcze ogromne znaczenie inne czynniki – np. wersja Pythona czy architektura komputera, jednak dla codziennych zastosowań nie powinniśmy się tym aż tak bardzo przejmować. Gdybyśmy jednak chcieli zrobić kopię zapasową Internetu możemy w pewnym momencie zderzyć się z błędem MemoryError.

Długość (między innymi) łańcuchów określa się wbudowaną w Pythona funkcją len jednak należy mieć na uwadze, że ilość znaków, którą widzimy to nie zawsze rzeczywista długość łańcucha trzymana w pamięci. Posiłkując się StackOverflow:


>>> test = u'ë́aúlt' # znaki można policzyć na palcach jednej ręki (dla regionów oddalonych od Czarnobyla)
>>> len(test)
6
>>> # Hmm, chyba jednak jesteśmy blisko uszkodzonego reaktora…
>>> for c in test:
print("{} – {}".format(c, len(c)))
ë 1
1
a 1
ú 1
l 1
t 1
>>> # i wszystko "jasne"…

view raw

strlen_py3.py

hosted with ❤ by GitHub

Dla Pythona 2 wyniki są jeszcze bardziej zaskakujące (w odpowiedzi na StackOverflow został właśnie użyty Python 2).

Stringi w Pythonie możemy zapisywać w kodzie na kilka różnych sposobów:


>>> print("Witaj świecie")
Witaj świecie
>>> print("Witaj 'świecie'")
Witaj 'świecie'
>>> print("Witaj \"świecie\"")
Witaj "świecie"
>>> print('Witaj świecie')
Witaj świecie
>>> print('Witaj "świecie"')
Witaj "świecie"
>>> print("""Witaj 
świecie""")
Witaj
świecie
>>> print('''Witaj 
świecie''')
Witaj
świecie
>>> print("Witaj\t\t\t\n\t\t\t\tświecie")
Witaj
świecie
>>> print('Witaj\t\t\t\n\t\t\t\tświecie')
Witaj
świecie

view raw

pystrings.py

hosted with ❤ by GitHub

Jednak wyświetlanie na ekranie prostych łańcuchów znaków może w pewnym momencie stać się dość nudne – na szczęście wynaleziono interpolację.

Interpolacja to wg Słownika Języka Polskiego: „wstawienie wyrazów, zwrotów, zdań do pierwotnego tekstu; też: takie wstawione wyrazy, zwroty, zdania”.
W naszej sytuacji chodzi o podstawienie wartości ze zmiennych w konkretne miejsca w łańcuchu znaków.
Przykładowo jeśli posiadamy szablon z informacją o uczniu i jego ocenie: "Cześć {imię i nazwisko ucznia}. Uzyskałeś {ilość punktów} punktów z egzaminu." to każde nasze wystąpienie {coś tam} będziemy mogli podmienić konkretnymi wartościami.
Do uzyskania konkretnego łańcucha znaków – z już podmienionymi wartościami możemy podejść (ponownie) na kilka sposobów – lepszych lub gorszych.
I tak na przykład, jednym z nich – niezbyt fajnym ani wygodnym – będzie po prostu łączenie łańcuchów znaków:


uczen = "Alpha Bravo"
punkty = 80
print("Cześć " + uczen + ". Uzyskałeś " + str(punkty) + " punktów z egzaminu.")

Nie da się chyba tego lubić. Przy okazji warto zwrócić uwagę, że pamiętać musimy o rzutowaniu na odpowiedni typ funkcją str albo zderzymy się z błędem TypeError.

Inny ciekawy (dla masochistów) sposób, to zapisanie wszystkiego jako lista i jej połączenie pustym łańcuchem:


uczen = "Alpha Bravo"
punkty = 80
print("".join(["Cześć ", uczen, ". Uzyskałeś ", str(punkty), " punktów z egzaminu."]))

Kod tak wspaniały, że aż oczy bolą… No dobra, są sytuacje, gdzie podobne podejście będzie akceptowalne, całkiem zwięzłe i czytelne… ale to nie jest ten przypadek.

Zmierzamy powoli w kierunku lepszego jutra – rozwiązanie dość popularne jeszcze do niedawna:


uczen = "Alpha Bravo"
punkty = 80
print("Cześć %s. Uzyskałeś %d punktów z egzaminu." % (uczen, punkty))

Plusem tego sposobu interpolowania łańcucha jest to, że korzysta on z wielu dostępnych specyfikatorów formatowania – co pozwala nam z kolei przyoszczędzić na jawnym konwertowaniu wartości zmiennych na łańcuchowy typ danych – musimy jednak zadbać o odpowiedni specyfikator w stringu.

Pierwszy z zalecanych (moim zdaniem) sposobów interpolowania to wykorzystanie metody format obiektu string:


uczen = "Alpha Bravo"
punkty = 80
print("Cześć {}. Uzyskałeś {} punktów z egzaminu.".format(uczen, punkty))

Sposób ten jest tak fajny, że aż dorobił się własnej strony internetowej z najróżniejszymi przykładami.

Drugim zalecanym (znów, moim zdaniem) sposobem jest użycie tzw f-stringów – dostępnych niestety dopiero od Pythona 3.6:


uczen = "Alpha Bravo"
punkty = 80
print(f"Cześć {uczen}. Uzyskałeś {punkty} punktów z egzaminu.")
print(f"2 + 2 = { 2 + 2 }")

Jeszcze innym sposobem interpolowania o którym warto wspomnieć jest użycie klasy Template z modułu string:


from string import Template
uczen = 'Alpha Bravo'
punkty = 80
tpl = Template("Cześć $uczen. Uzyskałeś $punkty punktów z egzaminu.")
print(tpl.substitute(uczen=uczen, punkty=punkty))

jednak nie spotykałem się z nim zbyt często – warto jednak wiedzieć, że coś takiego jest.

Więcej o samej interpolacji w Pythonie poczytać możemy w oficjalnych dokumentach PEP-0215PEP-0498, PEP-0501 oraz PEP-0502.

Dodatkowo, sam łańcuch znaków jako obiekt ma całkiem sporo metod pozwalających operować na danym łańcuchu, przytaczając kilka takich metod:


# https://pl.wikipedia.org/wiki/Pangram
pangram = "pChNąĆ w Tę łÓdŹ jEżA lUb OśM sKrZyŃ fIg"
print(pangram.capitalize())
print(pangram.casefold())
print(pangram.title())
print(pangram.upper())

UWAGA: Wywołanie takiej metody na stringu nie powoduje jej zmiany w pamięci.

I tym oto sposobem przechodzimy do faktu, że łańcuchy znaków są niezmiennym typem danych (tzw. immutable). Cool. I co to znaczy?
W dużym skrócie chodzi o to, że jeśli zmienimy łańcuch znaków to zmieni się jego lokalizacja w pamięci – zostanie utworzony nowy, zmodyfikowany obiekt. Najlepiej zaobserwujemy to na przykładzie:


>>> my_str_i = "Alpha"
>>> my_str_ii = "Bravo"
>>> my_str_iii = my_str_i
>>> my_str_iv = "Alpha"
>>> hex(id(my_str_i))
'0x7f5fec555960'
>>> hex(id(my_str_ii)) # 2 różne wartości w różnych miejscach pamięci
'0x7f5fe67e71f0'
>>> hex(id(my_str_iii)) # po przypisaniu referencji do obiektu obie zmienne wskazują to samo miejsce w pamięci
'0x7f5fec555960'
>>> hex(id(my_str_iv)) # ta sama wartość może być trzymana w pamięci pod jednym adresem
'0x7f5fec555960'
>>> my_str_i += ' Delta' # zmieniamy referencję ponieważ tworzymy nowy obiekt i wrzucamy go w inne miejsce pamięci
>>> hex(id(my_str_i))
'0x7f5fe684e730'
>>> hex(id(my_str_iii)) # pozostałe referencje nie zostały zmienione
'0x7f5fec555960'
>>> hex(id(my_str_iv))
'0x7f5fec555960'

Dla wygody skorzystałem z wbudowanych funkcji hex i id.

Na koniec warto wspomnieć o dostarczanym ze standardową biblioteką Pythona modułem string, który głównie może okazać się przydatny jeśli będziemy potrzebowali kolekcji znaków: małych lub dużych liter, cyfr (także ósemkowych lub szesnastkowych) czy białych znaków, itp.

Dodatkowe źródła:
[1] A string of unexpected lengths
[2] Moduł string

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s

Ta witryna wykorzystuje usługę Akismet aby zredukować ilość spamu. Dowiedz się w jaki sposób dane w twoich komentarzach są przetwarzane.