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