Python - wprowadzenie

03 - Łańcuchy znaków - cz. I

Czym jest ciąg znaków i jak go tworzymy?

Łańcuchy znaków (ang. string), jak wskazuje nazwa, to ciąg znaków. Tworzymy go, umieszczając w cudzysłowach. Dotychczas używaliśmy w tym celu podwójnych cudzysłowów:

print("To jest ciąg znaków, 123 #$%")
To jest ciąg znaków, 123 #$%

Można też użyć pojedynczych cudzysłowów:

print('To jest ciąg znaków, 123 #$%')
To jest ciąg znaków, 123 #$%

Trzeci sposób polega na użyciu trzech podwójnych cudzysłowów z każdej strony ciągu. Ten sposób pozwala także tworzyć bezpośrednio wielolinijkowe ciągi znaków a także zachowywać wcięcia w tekście:

print("""To jest 
      wielolinijkowy ciąg
         znaków""")
To jest 
      wielolinijkowy ciąg
         znaków

Można wewnątrz łańcucha znaków umieszczać bezpośrednio cudzysłowy, ale innego rodzaju, niż ten, który wyznacza początek i koniec ciągu:

print('Autorem książki "Trzej muszkieterowie" był Alexandre Dumas.')
print("D’Artagnan był muszkieterem.")
print("""D'Artagnan to bohater książki "Trzej muszkieterowie".""")
Autorem książki "Trzej muszkieterowie" był Alexandre Dumas.
D’Artagnan był muszkieterem.
D'Artagnan to bohater książki "Trzej muszkieterowie".

Można też umieścić w ciągu znaków ten sam rodzaj cudzysłowu, jak ten, który otacza łańcuch, należy je jednak w takim przypadku poprzedzić znakiem lewego ukośnika \:

print("\"Monty Python i Święty Graal\" to komedia filmowa z roku 1975.")
"Monty Python i Święty Graal" to komedia filmowa z roku 1975.

W przeciwnym razie otrzymamy błąd:

print(""Monty Python i Święty Graal" to komedia filmowa z roku 1975.")
  File "<ipython-input-36-a0632a6b059e>", line 1
    print(""Monty Python i Święty Graal" to komedia filmowa z roku 1975.")
                ^
SyntaxError: invalid syntax

Jak widać, sygnalizowany jest błąd składni (SyntaxError: invalid syntax).

W tym miejscu warto zwrócić uwagę na znak, który poprzednio użyliśmy: \. Jest to tzw. ,,znak ucieczki'', który w ciągach tekstowych zmienia znaczenie następnego znaku. Powyżej wskazywał on, że znak " nie powinien być traktowany jak koniec łańcucha znaków ale jak ,,zwykły'' znak. Stosując \ możemy wprowadzać do ciągów tekstowych znaki specjalne, takie jak tabulatory (\t) czy znaki nowej linii (\n). Jeśli chcielibyśmy wydrukować np. \t nie jako tabulator ale taki właśnie ciąg znaków, \ należy poprzedzić także znakiem \, wtedy jest on traktowany jako zwykły znak:

print("\tTo jest tekst z tabulatorem,\nto jest nowa linia,\na tak można \
\"uciec\" od znaku \"\\\": \\t")
    To jest tekst z tabulatorem,
to jest nowa linia,
a tak można "uciec" od znaku "\": \t

Na początku, może to się wydać skomplikowane, ale z czasem nabiera się wprawy.

Kolejnym sposobem tworzenia łańcucha znaków jest użycie funkcji str(), która może także służyć do konwersji z innych typów:

Przy okazji zauważ, że na końcu pierwszej linii znalazł się również znak \. Jeśli linia kodu jest zbyt długa można zakończyć ją tym znakiem i kontynuować wpisywanie kodu w kolejnej linii. To kolejne zastosowanie znaku \.

liczba = 7
print(type(liczba))
lancuch1 = str(liczba)
print(type(lancuch1))
lancuch2 = str(3.14)
print(type(lancuch2))
<class 'int'>
<class 'str'>
<class 'str'>

Więcej o funkcji print() i łączeniu sekwencji znaków.

Przyjrzyjmy się teraz bliżej funkcji print(), która z pewnością należy do najczęściej używanych, przynajmniej w programach działających w trybie tekstowym. Dotychczas przekazywaliśmy funkcji print() jeden ciąg znaków, lub zmienną. Jej możliwości są jednak dużo większe.

print("Zea mays", "posiada ", 10, "par chromosomów")
Zea mays posiada  10 par chromosomów

Jak widać, przekazaliśmy funkcji print() kilka argumentów, w tym cztery ciągi znaków i jedną liczbę. Sprawdźmy teraz jak ta funkcja poradzi sobie ze zmiennymi:

kukurydza = "Zea mays"
tekst_1 = "posiada"
n = 10
tekst_2 = "par chromosomów"
print(kukurydza, tekst_1, n, tekst_2)

Zea mays posiada 10 par chromosomów

Jak widać, również w tym przypadku funkcja print() poradziła sobie z zadaniem.

Zwróć uwagę, że pomiędzy poszczególnymi fragmentami drukowanego testu zostały wstawione spacje. Spacja jest domyślnym separatorem, ale można to zmienić:

print(kukurydza, tekst_1, n, tekst_2, sep="_")
print(kukurydza, tekst_1, n, tekst_2, sep="\n")
Zea mays_posiada_10_par chromosomów
Zea mays
posiada
10
par chromosomów

W pierwszym przypadku nowym separatorem został podkreślnik, w drugim znak nowej linii, dlatego każdy z argumentów został wypisany na ekranie w nowej linii.

Ciągi znaków można łączyć ze sobą:

rodzaj = "Zea"
gatunek = "mays"
kukurydza = rodzaj + gatunek
print(kukurydza)
Zeamays

Jak widać, w celu połączenia ciągów znaków użyliśmy operatora +, jednak oba łańcuchy zostały połączone bez żadnego separatora a zapewne chcielibyśmy oddzielić oba słowa za pomocą spacji. Trzeba ją dodać:

kukurydza = rodzaj + " " + gatunek
print(kukurydza)
Zea mays

Można oczywiście pominąć tworzenie zmiennej kukurydza:

print(rodzaj + " " + gatunek)
Zea mays

W tym przypadku, zmiana separatora nic nie da, ponieważ poszczególne łańcuchy są najpierw łączone, dopiero później w całości, jako jeden argument, przekazywane funkcji print():

print(rodzaj + " " + gatunek, sep="-")
Zea mays

Spróbujmy teraz dodać liczbę do ciągu znaków:

print(kukurydza + " posiada " + 10 + " par chromosomów.")
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-72-a7c1c190624c> in <module>
----> 1 print(kukurydza + " posiada " + 10 + " par chromosomów.")

TypeError: can only concatenate str (not "int") to str

Tym razem nie udało się. Komunikat TypeError: can only concatenate str (not "int") to str oznacza, że można łączyć tylko łańcuchy znaków (str) aby uzyskać łańcuch znaków a nie liczbę całkowitą (int). Musimy zatem przekształcić liczbę w ciąg tekstowy, służy do tego funkcja str().

print(kukurydza + " posiada " + str(10) + " par chromosomów.")
Zea mays posiada 10 par chromosomów.

Tym razem liczba 10 została przekształcona w łańcuch znaków a później sklejona z pozostałymi ciągami tekstowymi. Można też dokonań przy tej okazji obliczeń, np:

print(kukurydza + " posiada 2n=" + str(10*2) + " chromosomów.")
Zea mays posiada 2n=20 chromosomów.

W powyższym przykładzie najpierw wykonało się działanie, później jego wynik został przekształcony w łańcuch znaków.

F-strings

Powyższe sposoby łączenia łańcuchów znaków, liczb i zmiennych działają ale nie są zbyt czytelne. Python posiada szereg sposobów uproszczenia wyświetlania kombinacji ciągów znaków, liczb czy wyników zwracanych przez funkcje, ale skupimy się na najnowszych a co za tym idzie najprostszych i najbardziej efektywnych (przynajmniej moim zdaniem).

Wraz z wersją 3.6. języka Python wprowadzono tzw. f-strings pozwalające na polepszenie przejrzystości tego typu kodu a także oferujący dodatkowe możliwości. Spójrzmy na poniższy przykład:

m_roztworu = 50
m_substancji_rozpuszczonej = 5
print(f"Dla masy roztworu = {m_roztworu}g\n\
i masy substancji rozpuszczonej = {m_substancji_rozpuszczonej}g,\n\
stężenie = {100*m_substancji_rozpuszczonej/m_roztworu}%")
Dla masy roztworu = 50g 
i masy substancji rozpuszczonej = 5g, 
stężenie = 10.0%

Jak widać, przed pierwszym cudzysłowem umieszczona została litera f co wskazuje, ze mamy do czynienia z f-strings. Nazwy zmiennych, których wartości chcemy wydrukować otoczone są parą nawiasów klamrowych {}, można w nich także umieszczać np. wyrażenia matematyczne czy wywołania funkcji, które zwracają wartości, które chcemy umieścić w tekście.

Zmodyfikujmy nieco powyższy przykład:

m_roztworu = 13
m_substancji_rozpuszczonej = 7.3
print(f"Dla masy roztworu = {m_roztworu}g\n\
i masy substancji rozpuszczonej = {m_substancji_rozpuszczonej}g,\n\
stężenie = {100*m_substancji_rozpuszczonej/m_roztworu}%")
Dla masy roztworu = 13g
i masy substancji rozpuszczonej = 7.3g,
stężenie = 56.15384615384615%

Otrzymaliśmy dość dokładny wynik, ale zwykle nie potrzebujemy aż takiej precyzji a taka liczba cyfr po przecinku nie wygląda dobrze.

Przyjmijmy, że wystarczy nam dokładność do dwu miejsc po przecinku:

print(f"Dla masy roztworu = {m_roztworu}g\n\
i masy substancji rozpuszczonej = {m_substancji_rozpuszczonej}g,\n\
stężenie = {100*m_substancji_rozpuszczonej/m_roztworu:.2f}%")
Dla masy roztworu = 13g 
i masy substancji rozpuszczonej = 7.3g,
stężenie = 56.15%

Na końcu wyrażenia zwracającego liczbę zmiennoprzecinkową umieściliśmy :.2f, co oznacza, że powinny zostać wydrukowane dwa miejsca po przecinku (po zaokrągleniu).

Spójrzmy teraz na ten kod:

m_roztworu = 13
m_substancji_rozpuszczonej = 7.3
print(f"""
masa roztworu: {m_roztworu:.2f}g
masa substancji rozpuszczonej: {m_substancji_rozpuszczonej:.2f}g
stężenie: {100*m_substancji_rozpuszczonej/m_roztworu:.2f}%
""")
masa roztworu:			13.00g
masa substancji rozpuszczonej:	7.30g
stężenie:			56.15%

Użyliśmy potrójnego cudzysłowu aby łatwiej rozpisać wynik na trzy linijki (poprzednio użyliśmy w tym celu znaków \n) a także sformatowaliśmy wszystkie dane liczbowe tak, aby zawsze wyświetlały się dwa miejsca po przecinku. Pozwala to uestetycznić prezentowane wyniki. Skoro mowa o estetyce, można jeszcze wyrównać liczby w pionie używając odpowiedniej liczby spacji (można też użyć tabulatorów):

print(f"""
masa roztworu:                 {m_roztworu:.2f}g
masa substancji rozpuszczonej: {m_substancji_rozpuszczonej:.2f}g
stężenie:                      {100*m_substancji_rozpuszczonej/m_roztworu:.2f}%
""")
masa roztworu:                 13.00g
masa substancji rozpuszczonej: 7.30g
stężenie:                      56.15%

Jeśli chcemy wyrównać liczby względem kropki oddzielającej miejsca dziesiętne, dodajemy znak > oraz liczbę oznaczającą liczbę znaków w prezentowanych liczbach. Tu wpisaliśmy 5 (liczba pięcioznakowa), więc jeśli przypuszczasz, że któraś z podanych mas może mieć część całkowitą większą niż 2, należy ustawić większą długość:

print(f"""
masa roztworu:                 {m_roztworu:>5.2f}g
masa substancji rozpuszczonej: {m_substancji_rozpuszczonej:>5.2f}g
stężenie:                      {100*m_substancji_rozpuszczonej/m_roztworu:>5.2f}%
""")
masa roztworu:                 13.00g
masa substancji rozpuszczonej:  7.30g
stężenie:                      56.15%

Powyższy przykład można oczywiście napisać w inny sposób, np:

print(
    f"masa roztworu:                 {m_roztworu:>5.2f}g\n"
    f"masa substancji rozpuszczonej: {m_substancji_rozpuszczonej:>5.2f}g\n"
    f"stężenie:                      {100*m_substancji_rozpuszczonej/m_roztworu:>5.2f}%\n"
)
masa roztworu:                 13.00g
masa substancji rozpuszczonej:  7.30g
stężenie:                      56.15%

Zadanie: Co się stanie, jeśli usuniemy z powyższego przykładu znaki \n?

Stosowanie f-string-ów nie ogranicza się do bezpośredniego zastosowania w funkcji print(). Można ich użyć przy tworzeniu zmiennych czy bezpośredniego wypisywania ciągów znaków:

wynik = f"""
masa roztworu:                 {m_roztworu:>5.2f}g
masa substancji rozpuszczonej: {m_substancji_rozpuszczonej:>5.2f}g
stężenie:                      {100*m_substancji_rozpuszczonej/m_roztworu:>5.2f}%
"""
print(wynik)
masa roztworu:                 13.00g
masa substancji rozpuszczonej:  7.30g
stężenie:                      56.15%

Teraz sprawdź kod:

f"""
masa roztworu:                 {m_roztworu:>5.2f}g
masa substancji rozpuszczonej: {m_substancji_rozpuszczonej:>5.2f}g
stężenie:                      {100*m_substancji_rozpuszczonej/m_roztworu:>5.2f}%
"""
'\nmasa roztworu:                 13.00g\nmasa substancji rozpuszczonej:  7.30g\nstężenie:                      56.15%\n'

Jak widać, w tym drugim przypadku może czekać nas niespodzianka. Tym razem zamiast przejścia do nowej linii wyświetlany jest ciąg \n oznaczający nową linię, wyświetlane też są znaki ' ograniczające łańcuch.

Począwszy od wersji 3.8 języka Python wprowadzono ułatwienie, umożliwiające w prostszy sposób wydruk zarówno nazwy zmiennej jak i jej wartości (jeśli używasz wcześniejszej wersji kod nie zadziała):

sekwencja = 'AUGGCAGUU'
f'{sekwencja = }'
sekwencja = 'AUGGCAGUU'

Jeszcze o formatowaniu

Powyżej chcąc odpowiednio sformatować wynik, użyliśmy takiego kodu:

print(f'masa roztworu:                 {m_roztworu:>5.2f}g')
masa roztworu:                 13.00g

Przyjrzyjmy się dokładnie wyrażeniu: :>5.2f.

: jest początkowym znakiem wskazującym, że dalej zostanie określony sposób formatowania.

> oznacza, że będziemy stosować wyrównanie do prawej strony. Można także użyć znaków < - wyrównanie do lewej lub ^ - oznaczającego wyrównanie do środka.

Dalej następowały dwie cyfry, przedzielone kropką oznaczające liczbę znaków przeznaczoną na całą liczbę i część dziesiętną, oraz f wskazujący, że będzie to liczba zmiennoprzecinkowa (ang. float).

W przypadku liczb całkowitych zamiast f użyjemy d, dla łańcuchów znaków s, a podane wcześniej liczby, przedzielone . będą się odnosiły do minimalnej i maksymalnej liczby znaków przeznaczonych na wydrukowanie ciągu, przy czym obie są opcjonalne. Jeśli drukowana jest liczba, można po znaku określającym sposób wyrównania dodać " " (spację). W takim przypadku przed liczbami ujemnymi zostanie wydrukowany znak -, a przed dodatnimi, zostanie dodana spacja. Po znaku “:” można też dodać znak, który będzie służył do wypełnienia pustych miejsc, domyślnie jest to spacja.

Zobaczmy jak to działa na przykładach:

kodon = "AUG"
print(f'Metionina:{kodon:<}')
Metionina:AUG
print(f'Metionina:{kodon:<9s}')
Metionina:AUG      
print(f'Metionina:{kodon:>9s}')
Metionina:      AUG
print(f'Metionina:{kodon:*>9s}')
Metionina:******AUG
print(f'Metionina:{kodon:*^9s}')
Metionina:***AUG***
print(f'Metionina:{kodon:*^9.9s}')
Metionina:***AUG***
print(f'Metionina:{kodon:*^5.9s}')
Metionina:*AUG*
print(f'Metionina:{kodon:*^.9s}')
Metionina:AUG
liczba1 = 10
liczba2 = -20
print(f'Liczba 1:{liczba1:*^5f}')
print(f'Liczba 1:{liczba1:*^5d}')
print(f'Liczba 2:{liczba2:*^5d}')
Liczba 1:10.000000
Liczba 1:*10**
Liczba 2:*-20*
print(f'Liczba 1:{liczba1:*>5d}')
print(f'Liczba 2:{liczba2:*>5d}')
Liczba 1:***10
Liczba 2:**-20
print(f'Liczba 1:{liczba1:<5d}')
print(f'Liczba 2:{liczba2:<5d}')
Liczba 1:10   
Liczba 2:-20  
print(f'Liczba 1:{liczba1:< 5d}')
print(f'Liczba 2:{liczba2:< 5d}')
Liczba 1: 10  
Liczba 2:-20  
Last updated on 15 Oct 2020
Published on 15 Oct 2020