Python Core

Рядки і текст

Рядок як незмінна послідовність кодових точок Unicode, індекси й зрізи, функція len, методи split, join, strip, replace, find та інші, оператор in, f-рядки поглиблено, різниця між str і bytes, кодування та декодування, UTF-8.

Рядки і текст

Текст для комп'ютера — це числа

Програми працюють із текстом постійно: читають файли, обробляють введення, формують повідомлення. Для людини текст — це символи, а для комп'ютера — числа. Рядок (string) у Python — це незмінна послідовність кодових точок Unicode, де кожен символ має номер. Урок показує, як працювати з рядками через індекси, зрізи й методи, і чому символ не те саме, що байт.

🎯 Мета лекції

  • Отримувати символи рядка через індекси, зокрема від'ємні.
  • Будувати підрядки через зрізи з синтаксисом [start:stop:step].
  • Застосовувати методи: split, join, strip, replace, find, count.
  • Пояснювати, чому методи рядків повертають новий об'єкт, а не змінюють старий.
  • Розрізняти символ і байт: що таке Unicode та UTF-8.
  • Кодувати рядок у байти й декодувати назад через encode і decode.

🔑 Ключові терміни

  • Рядок (string): незмінна послідовність символів (кодових точок Unicode).
  • Індекс: позиція символу в рядку (з 0).
  • Зріз (slice): підрядок від індексу start до stop із кроком step.
  • Кодова точка (code point): номер символу в Unicode (U+0041 — це A).
  • UTF-8: кодування, яке перетворює кодові точки на байти (1–4 байти на символ).
  • Байт (byte): число від 0 до 255; тип bytes зберігає послідовність байтів.

Пригадаємо попередній урок

Індекси: додатні та від'ємні

Рядок — це послідовність символів, кожен з яких має індекс — позицію, що починається з 0:

text = "Python"
# індекси:  0   1   2   3   4   5
#          P   y   t   h   o   n

Доступ до символу:

print(text[0])   # P
print(text[1])   # y
print(text[5])   # n

Від'ємні індекси відраховують з кінця: -1 — останній символ, -2 — передостанній:

print(text[-1])  # n
print(text[-2])  # o
print(text[-6])  # P

Додатні індекси зручні, коли працюєте з початком, від'ємні — коли з кінцем. Вихід за межі викликає IndexError:

print(text[10])  # IndexError: string index out of range

Індекси відповідають положенню між символами, тому останній символ рядка з 6 символів має індекс 5, а не 6. Це спрощує обчислення довжини: len(text) дорівнює індексу після останнього символу.

Зрізи: межі між символами

Зріз (slice) витягує підрядок від індексу start (включно) до stop (не включно) з кроком step:

text = "Python"
print(text[0:3])   # Pyt (індекси 0, 1, 2)
print(text[2:5])   # tho (індекси 2, 3, 4)

Пропущений start означає «від початку», stop — «до кінця»:

print(text[:3])    # Pyt (те саме, що [0:3])
print(text[3:])    # hon (від 3 до кінця)
print(text[:])     # Python (весь рядок)

Крок задає інтервал: [::2] бере кожен другий символ, [::-1] розвертає рядок:

print(text[::2])   # Pto (індекси 0, 2, 4)
print(text[::-1])  # nohtyP (від останнього до першого)

Зріз не викликає помилку при виході за межі: Python просто обрізає до наявної довжини:

print(text[2:100])  # thon (те саме, що [2:])
print(text[10:20])  # "" (порожній рядок, бо початок поза межами)

Межа stop не включається в зріз, тому довжина результату дорівнює stop - start. Це дозволяє легко поєднувати частини: text[:n] + text[n:] завжди дає весь рядок.

Незмінність: методи повертають новий рядок

Рядки в Python незмінні (immutable): не можна змінити символ на місці. Спроба присвоїти значення індексу викликає TypeError:

text = "Python"
text[0] = "J"  # TypeError: 'str' object does not support item assignment

Щоб «змінити» рядок, створіть новий із потрібними частинами:

text = "Python"
new_text = "J" + text[1:]
print(new_text)  # Jython

Усі методи рядків повертають новий рядок, а не змінюють оригінал:

text = "python"
upper_text = text.upper()
print(text)        # python (не змінився)
print(upper_text)  # PYTHON

Це означає, що виклик методу без збереження результату нічого не змінить:

text = "python"
text.upper()       # створив новий рядок, але не зберіг
print(text)        # python (як і було)

Незмінність дозволяє Python кешувати однакові рядки й використовувати їх як ключі словників, але вимагає пам'яті для кожної зміни. Тому конкатенація в циклі неефективна — про це далі.

Методи split, join, strip, replace

Python надає десятки методів для роботи з рядками. Найчастіше використовують ті, що розбивають, склеюють, обрізають і замінюють частини.

split: розбиття на частини

split(sep) розбиває рядок на список частин за роздільником sep. Без аргументу розбиває за будь-яким пробільним символом (пробіл, табуляція, перенос рядка):

text = "apple banana cherry"
words = text.split()
print(words)  # ['apple', 'banana', 'cherry']

З явним роздільником:

date = "2026-11-01"
parts = date.split("-")
print(parts)  # ['2026', '11', '01']

Якщо роздільник не знайдено, split повертає список з одним елементом — оригінальним рядком:

text = "hello"
print(text.split(","))  # ['hello']

join: склеювання частин

sep.join(iterable) склеює елементи послідовності через роздільник sep. Це протилежність split:

words = ['apple', 'banana', 'cherry']
text = " ".join(words)
print(text)  # apple banana cherry

Роздільник може бути будь-яким рядком:

parts = ['2026', '11', '01']
date = "-".join(parts)
print(date)  # 2026-11-01

Для склеювання без роздільника використовують порожній рядок:

chars = ['P', 'y', 't', 'h', 'o', 'n']
word = "".join(chars)
print(word)  # Python
Пастка += у циклі. Кожна конкатенація result += word створює новий рядок і копіює всі символи, тому для N слів це O(N²) операцій. Натомість "".join(words) копіює кожен символ один раз — O(N).

strip, lstrip, rstrip: обрізання пробілів

strip() видаляє пробільні символи (пробіл, табуляція, \n) з початку та кінця:

text = "  hello  "
print(text.strip())   # "hello"
print(text.lstrip())  # "hello  " (ліва сторона)
print(text.rstrip())  # "  hello" (права сторона)

Можна вказати набір символів для видалення:

url = "https://example.com/"
clean = url.strip("https:/")
print(clean)  # example.com

Це корисно для очищення введення користувача:

name = input("Ім'я: ").strip()

replace: заміна підрядка

replace(old, new) замінює всі входження old на new:

text = "Python is awesome"
new_text = text.replace("awesome", "great")
print(new_text)  # Python is great

Можна задати максимальну кількість замін:

text = "apple apple apple"
new_text = text.replace("apple", "orange", 2)
print(new_text)  # orange orange apple

Якщо old не знайдено, replace повертає оригінальний рядок без змін.

Пошук: find, count, in

Методи find і count шукають підрядок без використання регулярних виразів.

find: перше входження

find(sub) повертає індекс першого входження підрядка sub, або -1, якщо не знайдено:

text = "Python is awesome"
print(text.find("is"))      # 7
print(text.find("Java"))    # -1

Можна вказати діапазон пошуку через start і end:

print(text.find("o", 10))   # 15 (шукає з індексу 10)

Для перевірки наявності краще використовувати in:

if "Python" in text:
    print("Знайдено")

count: кількість входжень

count(sub) рахує, скільки разів sub зустрічається в рядку:

text = "banana"
print(text.count("a"))     # 3
print(text.count("na"))    # 2

Перекриття не враховуються: count("ana") для "banana" дасть 1, а не 2.

Оператор in

sub in text перевіряє наявність підрядка:

text = "Python is awesome"
print("Python" in text)    # True
print("Java" in text)      # False

Це набагато читабельніше за text.find("Python") != -1.

Unicode: символ — це кодова точка

Комп'ютер зберігає текст як послідовність чисел. Unicode — це стандарт, який присвоїв кожному символу кодову точку — унікальний номер від U+0000 до U+10FFFF. Наприклад:

  • A → U+0041 (65 у десятковій)
  • а (кирилиця) → U+0430 (1072)
  • ї → U+0457 (1111)
  • 😀 (емодзі) → U+1F600 (128512)

Рядок Python — це послідовність кодових точок, а не байтів. Функція len рахує кількість символів:

text = "Привіт"
print(len(text))       # 6 (шість символів)

emoji = "😀🎉"
print(len(emoji))      # 2 (два символи)

Щоб побачити кодову точку, використовують ord:

print(ord("A"))        # 65
print(ord("а"))        # 1072
print(ord("ї"))        # 1111
print(hex(ord("😀")))  # 0x1f600

Зворотна функція — chr:

print(chr(65))         # A
print(chr(1072))       # а
print(chr(0x1f600))    # 😀

Хибне уявлення: «символ = байт». Це правда лише для ASCII (перші 128 символів Unicode). Кирилиця, емодзі та більшість мов світу потребують більше одного байту в UTF-8.

UTF-8: кодова точка → байти

Рядок Python зберігає символи як кодові точки, але файли й мережа працюють із байтами. UTF-8 — це кодування, яке перетворює кодову точку на послідовність байтів (від 1 до 4):

  • U+0000..U+007F (ASCII) → 1 байт
  • U+0080..U+07FF (кирилиця, грецька) → 2 байти
  • U+0800..U+FFFF (більшість мов) → 3 байти
  • U+10000..U+10FFFF (емодзі, рідкісні символи) → 4 байти

Метод encode перетворює рядок на об'єкт bytes:

text = "Python"
b = text.encode("utf-8")
print(b)           # b'Python'
print(type(b))     # <class 'bytes'>
print(len(b))      # 6 (шість байтів)

Для ASCII результат виглядає так само, але для кирилиці — ні:

text = "Привіт"
b = text.encode("utf-8")
print(b)           # b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd1\x96\xd1\x82'
print(len(text))   # 6 (шість символів)
print(len(b))      # 12 (дванадцять байтів, по 2 на символ)

Символ ї (U+0457) кодується як два байти \xd1\x97:

char = "ї"
b = char.encode("utf-8")
print(b)           # b'\xd1\x97'
print(len(char))   # 1 (один символ)
print(len(b))      # 2 (два байти)

Емодзі займають чотири байти:

emoji = "😀"
b = emoji.encode("utf-8")
print(b)           # b'\xf0\x9f\x98\x80'
print(len(emoji))  # 1 (один символ)
print(len(b))      # 4 (чотири байти)

Зворотна операція — decode:

b = b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd1\x96\xd1\x82'
text = b.decode("utf-8")
print(text)        # Привіт

Якщо байти не є правильним UTF-8, decode викликає UnicodeDecodeError:

b = b'\xff\xfe'
text = b.decode("utf-8")  # UnicodeDecodeError: invalid start byte

Python 3.15 використовує UTF-8 за замовчуванням (PEP 686) для open() і інших функцій роботи з файлами, навіть на Windows. У старіших версіях Windows міг використовувати cp1251 або інше кодування, що призводило до кракозябр.

str vs bytes

str зберігає текст (послідовність кодових точок), bytes — сирі дані (послідовність чисел 0–255). Літерал байтів починається з префікса b:

text = "hello"       # str
data = b"hello"      # bytes

print(type(text))    # <class 'str'>
print(type(data))    # <class 'bytes'>

Байти можна створити зі списку чисел:

data = bytes([72, 101, 108, 108, 111])
print(data)          # b'Hello'

Операції зі str і bytes схожі, але байти незмінні й не підтримують методів, що залежать від кодування (наприклад, upper працює, але тільки для ASCII):

data = b"hello"
print(data.upper())  # b'HELLO'
print(data[0])       # 104 (число, а не символ)

Не можна змішувати str і bytes без явного перетворення:

text = "hello"
data = b" world"
result = text + data  # TypeError: can only concatenate str (not "bytes") to str

Правильно:

text = "hello"
data = b" world"
result = text + data.decode("utf-8")
print(result)        # hello world

Або навпаки:

result = text.encode("utf-8") + data
print(result)        # b'hello world'
Типова помилка: відкрити бінарний файл у текстовому режимі або навпаки. Файл .txt відкривають у текстовому режимі ("r"), а .jpg чи .bin — у бінарному ("rb"). У бінарному режимі read() повертає bytes, у текстовому — str.

+= у циклі vs join

Конкатенація через += у циклі здається природною, але створює новий рядок на кожній ітерації:

result = ""
for word in ["apple", "banana", "cherry"]:
    result += word + " "
print(result)  # apple banana cherry

Це працює, але для N слів Python копіює O(N²) символів, бо кожен += створює новий рядок. Для тисячі слів різниця помітна. Правильно:

words = ["apple", "banana", "cherry"]
result = " ".join(words)
print(result)  # apple banana cherry

join будує список частин і копіює кожен символ один раз — O(N).

Підсумок

Рядок — це незмінна послідовність символів (кодових точок Unicode). Індекси починаються з 0, від'ємні відраховують з кінця. Зрізи [start:stop:step] витягують підрядок; межа stop не включається. Методи split, join, strip, replace, find, count повертають новий рядок, не змінюють оригінал. Символ — це кодова точка, а не байт: len("ї") дорівнює 1, але в UTF-8 це 2 байти. encode перетворює str на bytes, decode — навпаки. Конкатенація через += у циклі неефективна — краще join.

Практика

Після лекції переходьте до ноутбука з автоперевіркою. Завдання включають:

  1. Трасування доступу до символів через індекси й зрізи.
  2. Розбиття та склеювання рядків через split і join.
  3. Обробку введення з strip для видалення зайвих пробілів.
  4. Підрахунок слів і символів у тексті.
  5. Шифр Цезаря через ord, chr і зсув кодових точок.
  6. Перевірку паліндрома через [::-1].
  7. Кодування й декодування рядків у різних кодуваннях.
Спіральне повторення 1 після уроку 07. Міні-проєкт «текстова гра / квіз у терміналі» перенесено після уроку 07 за планом. Урок 08 — це початок блоку про колекції (рядки, списки, кортежі, словники).
Copyright © 2026