Kontynuujemy poznawanie Pythona. Tym razem weźmiemy się za napisy, różne rzeczy z nimi związane w tym i ich formatowanie.

Literał napisu oraz znaki specjalne

Literał napisu to cudzysłów (pojedynczy bądź podwójny), który rozpoczyna i kończy nasz napis.

print('Hello')
print("Hello")
# Hello
# Hello

Oto jak najbardziej poprawne napisy w Pythonie. Problem jedynie stanowi sytuacja, w której chcemy użyć apostrofa w pojedynczym cudzysłowie na przykład. Python musi bowiem wiedzieć, gdzie się napis zaczyna a gdzie kończy.

Zatem coś, co znajduje się wewnątrz napisu, musi być opatrzone znakiem ucieczki „\”. Lepiej to pokazać na przykładzie:

print('What\'s up?')
# What's up?
print("\"What's up?\" he said")
# "What's up?" he said

Python musi rozróżniać, które znaki ” lub ’ stanowią element napisu, a które oznaczają początek i koniec. Dlatego mamy znak „ucieczkowy” \.

Mamy też kilka znaków specjalnych. Warto znać \t (tab) oraz \n (enter, nowa linia)

print('\tWhat\'s up?')
# 	What's up?
print("a\nb\nc\n")
# a
# b
# c

Dziwnie wygląda, ale tak to działa. Komputer takie znaki jak tab czy enter musi przecież jakoś zapisać. Mógłby się nazywać tab i enter, ale jak odróżnisz czy to jest napis enter czy taki znak?

Zdecydowano się użyć sekwencji ucieczkowej \ oraz jednej literki. t od tab, n od newline czyli nowa linia.

Kolejny przykład, dla zrozumienia:

print("\ta", "b", "c", sep="\n")
# 	a
# b
# c

Tutaj wypisujemy znaki „a”, „b” i „c”. Pierwszy z tabem (znak „\t”) zaś jako separatora (oddzielenia) używamy znaku „\n”, znaku nowej linii, entera.

Niemutowalność napisu

Lista, dobrze nam znana, jest typem mutowalnym, czyli możemy modyfikować jej elementy. Oto przykład:

lst = ["abc", 123, "blabla"]
print(lst[0])
lst[0] = "def"
print(lst)
print(lst[0])
# abc
# ['def', 123, 'blabla']
# def

Element o indeksie 0 zamieniamy z „abc” na „def” bez żadnego problemu. Niemutowalnym typem jest natomiast tupla (krotka):

tup = ("abc", 123, "blabla")
print(tup)
print(tup[0])
# ('abc', 123, 'blabla')
# abc
tup[0] = "def"
#TypeError: 'tuple' object does not support item assignment

Jej elementów modyfikować w ten sposób nie możemy. Jest jeszcze typ liczbowy, który nawet nie podlega indeksowaniu:

string = "10"
print(string[0])
#1
num = 10
print(num[0])
# TypeError: 'int' object is not subscriptable

Tym samym mutowalny nie jest. A jak z napisami? Indeksowaniu podlegają. Ale czy mutowalne są? Sprawdźmy:

msg = "Hello!"
print(msg[-1])
#!
msg[-1] = "."
# TypeError: 'str' object does not support item assignment

Mutowalne nie są i choć staramy się tutaj wykrzyknik zamienić na kropkę, nie wychodzi nam to.

Oczywiście można zapytać: jak to? Nie możemy zmieniać tekstu? Wielokrotnie to robiliśmy.

Cóż, wielokrotnie braliśmy niemutowalny tekst i zwracaliśmy jego zmodyfikowaną kopię do jakiejś zmiennej. Oto przykład zamiany wykrzyknika na kropkę:

msg = "Hello!"
msg = msg[:-1] + "."
print(msg)
#Hello.

Tutaj do zmiennej msg wrzucamy kopię jej oryginalnej zawartości do ostatniego znaku (bez niego) plus kropkę. Pracujemy na kopii, którą do zmiennej msg wrzuciliśmy. Podobnie ten zapis:

num = 10
num += 1
print(num)
#11

Wydawać by się mogło, że dochodzi tu do zmiany wartości 10. Nie. Do zmiennej num wrzucamy wynik działania jej oryginalna zawartość + 1. Oto mniej skrócony zapis tego samego:

num = 10
num = num + 1
print(num)
#11

Do num wpada kopia jej oryginalnej zawartości + 1. Dla przykładu, zabawy z typem mutowalnym:

lst = [10, 1, "abc"]
lst[0] = 11
lst.pop()
lst.sort()
print(lst)
#[1, 11]

Tutaj zamieniamy sobie pierwszy element z 10 na 11, zrzucamy ostatni, sortujemy listę. Mutujemy ją, zmieniamy jej zawartość. Bez wrzucania do zmiennej jej kopii jakoś zmodyfikowanej.

Niby drobiazg albo wręcz coś dziwnego wnikanie w tę mutowalność. Cóż, zasada jest prosta: krotka, napis, liczba – typ niemutowalny. Lista – typ mutowalny.

Typy niemutowalne przez swoją niezmienność nadają się na klucze w słownikach. Typy mutowalne nie mogą być kluczem w słowniku:

cities = {
    (40.730610, -73.935242) : "New York"
}
print(cities[(40.730610, -73.935242)])
#New York

Typ niemutowalny, krotka zawierająca położenie geograficzne, jest w tym słowniku kluczem a wartością nazwa miasta.

Innym typem niemutowalnym są liczby. Zróbmy słownik z liczbami od 1 do 10 jako kluczami oraz kwadratami tych liczb jako wartościami:

numbers = {
    num: num ** 2 for num in range(1,11)
}
print(numbers)
print(numbers[5])
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81, 10: 100}
# 25

Napis, jako typ mutowalny, również nadaje się jako klucz do słownika:

person = {
    "name": "John",
    "languages": ["Python", "PHP", "JS"]
}
print(person)
# {'name': 'John', 'languages': ['Python', 'PHP', 'JS']}

Lista może być wartością w słowniku. Ale kluczem nie. Klucz musi być niezmienny a zatem niemutowalny.

Napis – funkcja .format()

Funkcja format to nieco starszy, ale nadal wart uwagi sposób formatowania tekstu, wplatania wartości zmiennych wewnątrz typu string. Oto jak ona działa:

name="John"
age = 30
print("My name is {} and I am {} years old".format(name, age))
# My name is John and I am 30 years old

Możemy też do tej funkcji wypakować listę/tuplę:

tup = ("John", 30)
print("My name is {} and I am {} years old".format(*tup))
#My name is John and I am 30 years old

Z jakiegoś powodu dostajemy tuplę i pierwszy jej element to imię, drugi to wiek. W ten sposób wypakowujemy to do funkcji format. Bardziej realny przykład:

for idxval in enumerate("hello"):
    print("Element with index {} has value {}".format(*idxval))
    
# Element with index 0 has value h
# Element with index 1 has value e
# Element with index 2 has value l
# Element with index 3 has value l
# Element with index 4 has value o

Enumerate zwraca nam tuple z indeksem i wartością, a my je wypakowujemy do format.

Format może też używać argumentów nazwanych:

print("My name is {name} and I am {age} years old".format(name="John", age=30))
#My name is John and I am 30 years old

W takim wypadku do format możemy sobie wypakować słownik:

person = {
    "name" : "John",
    "age" : 30
}
print("My name is {name} and I am {age} years old".format(**person))
#My name is John and I am 30 years old

Nie ma tu za bardzo się nad czym rozwodzić. Funkcja format jest dobra, ale została już zastąpiona czymś lepszym.

Formatowanie – f-string

F-string zaznaczamy literą f zaś zawartość zmiennych wrzucamy wewnątrz znaków {}.

name = "John"
age = 30
print(f"My name is {name} and Iam {age} years old")
# My name is John and Iam 30 years old

Możemy też robić coś z tymi zmiennymi wewnątrz nawiasów klamrowych

name = "John"
age = 30
print(f"My name is {name.upper()} and Iam {age*12} months old")
# My name is JOHN and Iam 360 months old

Możemy nawet, choć to dziwne, wrzucać tam same wywołania funkcji:

def get_name():
    return "John"

def get_age():
    return 30

print(f"My name is {get_name()} and Iam {get_age()*12} months old")
# My name is John and Iam 360 months old

Taki podchwytliwy przykład, który bardzo lubię:

print(f"Hi {input('Whats your name?')}")

Program pyta nas o imię i wyświetla „Hi {imię}” w jednej linijce. Oczywiście takich kodów trudnych do zrozumienia zazwyczaj się nie pisze, ale warto znać różnego rodzaju sztuczki w języku, którym się posługujemy.

Formatowanie f-stringów

Istnieją różne sztuczki, związane z formatowaniem wartości, które umieszczamy wewnątrz nawiasów klamrowych. Oto kilka z nich.

Przykład – chcemy wyświetlić zawartość zmiennej:

name = "John"
print(f"name = {name}")
# name = John

Można to zrobić prościej:

name = "John"
print(f"{name=}")
print(f"{name = }")
# name='John'
# name = 'John'

Wystarczy znak =. W dodatku warto zauważyć, że nasze spacje również są honorowane i program wypisuje nam to tak, jak tego chcemy.

To samo tyczy się działań matematycznych. Przykład:

print(f"2 + 2 = {2 + 2}")
print(f"{2 + 2 = }")
# 2 + 2 = 4
# 2 + 2 = 4

Dla liczb typu float (liczby z wartościami po przecinku) możemy określić ilość miejsc po przecinku, które chcemy wypisać:

num = 12.3456789
print(f"{num:.1f}")
print(f"{num:.2f}")
print(f"{num:.3f}")
# 12.3
# 12.35
# 12.346

Możemy też wyświetlać na przykład liczby w systemie szesnastkowym, dwójkowym i ósemkowym:

num = 255
print(f"{num} is {num:x} in hex")
print(f"{num} is {num:b} in binary")
print(f"{num} is {num:o} in octal")
# 255 is ff in hex
# 255 is 11111111 in binary
# 255 is 377 in octal

Istnieje jeszcze kilka sztuczek, które można sobie wyszukać, a które potrafią coś nam w jakiś sposób formatować.

Możemy mieć z tyłu głowy, że np. takie daty też posiadają swoje formaty i obiekt datetime możemy do woli formatować, znając te zapisy, wewnątrz f-stringów. Oto jeden przykład:

import datetime

today = datetime.datetime.today()
print(f"{today:%B %d, %Y}")
#March 21, 2024