Рядки і текст
Рядки і текст
Текст для комп'ютера — це числа
Програми працюють із текстом постійно: читають файли, обробляють введення, формують повідомлення. Для людини текст — це символи, а для комп'ютера — числа. Рядок (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

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)) # 😀

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

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.

Практика
Після лекції переходьте до ноутбука з автоперевіркою. Завдання включають:
- Трасування доступу до символів через індекси й зрізи.
- Розбиття та склеювання рядків через
splitіjoin. - Обробку введення з
stripдля видалення зайвих пробілів. - Підрахунок слів і символів у тексті.
- Шифр Цезаря через
ord,chrі зсув кодових точок. - Перевірку паліндрома через
[::-1]. - Кодування й декодування рядків у різних кодуваннях.
Налагодження (debugging)
Типи помилок — синтаксичні, виконання та логічні; як читати traceback знизу вгору; print-налагодження; дебагер VS Code з breakpoint, step over і step into; вбудована функція breakpoint(); метод гумової качки.
Модулі, Пакети та Віртуальні Середовища
Глибоке дослідження системи модулів Python — від механізмів пошуку файлів через sys.path до кешування імпортів у sys.modules, архітектури пакетів з __init__.py та ізоляції залежностей через venv. Фундамент для будь-якого серйозного Python-проекту.