Работа со строками в Python: срезы, методы и f-строки

Работа со строками в Python: срезы, методы и f-строки Полезное

Строка в Python (тип str) — это неизменяемая последовательность символов Unicode. Работа со строками сводится к четырем действиям: достать часть строки (индексы и срезы), преобразовать ее методами (split, join, strip, replace), собрать новую строку из данных (f-строки) и перевести текст в байты и обратно (кодировки). Примеры проверены на Python 3.9; возможности более новых версий помечены номером версии.

Три понятия, которые часто смешивают:

  • символ в смысле Python — одна кодовая точка Unicode, именно их считает len();
  • байт — единица хранения; одна кодовая точка в UTF-8 занимает от 1 до 4 байт;
  • видимый символ (графема) — то, что человек видит как одну букву; он может состоять из нескольких кодовых точек.

Минимальный пример целиком

Сначала один рабочий фрагмент, где встречаются все основные операции. Скопируйте и запустите:

raw = "  Иван Петров;ivan@example.com;Python  "

line = raw.strip()                 # убираем пробелы по краям
name, email, course = line.split(";")
first = name.split()[0]            # "Иван"
domain = email[email.index("@") + 1:]

print(f"{first}, курс {course.upper()}, домен {domain}")
print(len(line), "символов в очищенной строке")
Иван, курс PYTHON, домен example.com
35 символов в очищенной строке

strip() вернул новую строку без пробелов по краям, split(";") разрезал ее на три части, срез взял все после @, f-строка собрала результат. Исходная raw не изменилась — ниже станет ясно почему.

Как создать строку

Строку записывают в одинарных, двойных или тройных кавычках — разницы для интерпретатора нет, важно лишь, чтобы открывающая и закрывающая кавычки совпадали. Тройные кавычки сохраняют переносы строк, поэтому их используют для многострочного текста и docstring.

a = 'Он сказал: "привет"'        # двойные кавычки внутри одинарных
b = "It's Python"                 # апостроф внутри двойных
c = 'It\'s Python'                # то же через экранирование
d = """Первая строка
Вторая строка"""
path = r"C:\new\table"            # raw-строка: \n и \t не превращаются в спецсимволы

print(b == c)
print(d.count("\n"), "перенос строки")
print(path)
True
1 перенос строки
C:\new\table

Без префикса r запись "C:\new\table" дала бы перенос строки и табуляцию вместо \n и \t. Ограничение raw-строк: такая строка не может заканчиваться одиночным обратным слешем.

Индексы и срезы

У каждого символа есть индекс, отсчет идет с нуля. Отрицательный индекс считает с конца: -1 — последний символ. Срез s[start:stop:step] берет символы с start включительно до stop не включительно.

s = "Python"
print(s[0], s[-1])      # первый и последний
print(s[1:4])           # индексы 1, 2, 3
print(s[:2], s[2:])     # начало и остаток
print(s[::2])           # каждый второй
print(s[::-1])          # строка задом наперед
print(s[10:])           # срез за границей - не ошибка
P n
yth
Py thon
Pto
nohtyP

Последний print вывел пустую строку: срез за пределами строки не ошибка. Обращение по одиночному индексу за границей ведет себя иначе — s[10] вызывает IndexError.

Неизменяемость строк

Строку нельзя изменить на месте — любой метод возвращает новый объект. Типичная ошибка новичка — попытка заменить символ по индексу:

s = "python"
try:
    s[0] = "P"
except TypeError as e:
    print("Ошибка:", e)

s = "P" + s[1:]              # исправление: собираем новую строку
print(s)
print(s.capitalize() is s)   # метод вернул другой объект
Ошибка: 'str' object does not support item assignment
Python
False

Вторая частая ошибка — вызвать метод и не сохранить результат: s.upper() ничего не меняет в s, нужно писать s = s.upper(). Иногда неизменяемость «доказывают» тем, что после s = "другое" у переменной меняется id(). Это неточно: так ведет себя и изменяемый список, потому что имя просто связывается с новым объектом. Доказательство неизменяемости — именно TypeError при присваивании по индексу.

Основные методы строк

Полный список выводит dir(str), но на практике хватает нескольких.

Метод Что делает Пример -> результат
split(sep) режет строку в список "a,b,c".split(",") -> ['a', 'b', 'c']
join(iter) склеивает список строк через разделитель "-".join(["a", "b"]) -> 'a-b'
strip() убирает пробельные символы по краям " hi \n".strip() -> 'hi'
replace(old, new) заменяет все вхождения подстроки "a-b-c".replace("-", "") -> 'abc'
find(sub) индекс вхождения или -1 "otus".find("t") -> 1
startswith / endswith проверка начала / конца "file.py".endswith(".py") -> True
upper / lower / title регистр "oTUs".title() -> 'Otus'

Пара нюансов split: без аргумента он делит по любым пробельным символам и выбрасывает пустые куски, а с явным разделителем — сохраняет их.

print("a  b\tc".split())      # по пробельным символам
print("a,,b".split(","))      # пустой элемент сохраняется
print(" | ".join(["id", "name", "email"]))
print("otus".find("x"), "otus".count("s"))
['a', 'b', 'c']
['a', '', 'b']
id | name | email
-1 1

Метод index отличается от find только поведением при промахе: вместо -1 он вызывает ValueError. join принимает только строки — список чисел сначала переводят: ",".join(map(str, [1, 2])).

Ловушка strip: он убирает набор символов, а не подстроку

Аргумент strip, lstrip и rstrip — это множество символов, которые срезаются с края, пока встречаются. Поэтому попытка отрезать расширение так дает неверный результат:

name = "report.txt"
print(name.rstrip(".txt"))        # неверно: срезаны все t, x и точки справа
print(name.removesuffix(".txt"))  # Python 3.9+
print("https://otus.ru".removeprefix("https://"))
repor
report
otus.ru

Методы removeprefix и removesuffix появились в Python 3.9. В более старых версиях то же делают проверкой if name.endswith(".txt"): name = name[:-4].

Сложение строк и f-строки

Оператор + склеивает строки, * повторяет строку заданное число раз. Сложить строку с числом нельзя — Python не преобразует типы неявно:

age = 30
try:
    msg = "Возраст: " + age
except TypeError as e:
    print("Ошибка:", e)

print("Возраст: " + str(age))   # явное преобразование
print("-" * 10)
Ошибка: can only concatenate str (not "int") to str
Возраст: 30
----------

Для сборки строки из данных удобнее f-строки (Python 3.6+): перед кавычкой ставят f, а выражения пишут в фигурных скобках. После двоеточия внутри скобок задается формат.

price = 1234.5
name = "Python Basic"
count = 3

print(f"{name}: {price:.2f} руб.")
print(f"{price:,.0f}".replace(",", " "))   # разделитель тысяч
print(f"|{name:<14}|{count:>3}|")          # выравнивание
print(f"{count * price = }")               # отладочный вид, Python 3.8+
Python Basic: 1234.50 руб.
1 234
|Python Basic  |  3|
count * price = 3703.5

Обратите внимание: 1234.5 с форматом ,.0f дало 1 234, а не 1 235. При ровной половине форматирование округляет к четному, а для чисел, которые неточно представимы в двоичном виде, результат зависит от их фактического значения. Для денег надежнее decimal.Decimal. До Python 3.12 внутри f-строки нельзя было повторно использовать ту же кавычку, что и снаружи (f"{d["key"]}" — синтаксическая ошибка). С Python 3.12 (PEP 701) это разрешено, но для совместимости с 3.11 и ниже лучше брать другой тип кавычек: f"{d['key']}".

Много кусков в цикле лучше копить в списке и склеить один раз через "".join(parts) — на больших объемах это обычно быстрее, чем +=.

Строки и кодировки кратко

Внутри программы строка хранит кодовые точки Unicode, а в файл или сеть уходят байты. Переход между ними — encode и decode с явной кодировкой. Цепочка на одном символе: буква «Я» -> кодовая точка U+042F -> байты D0 AF в UTF-8.

s = "Я"
print(ord(s), hex(ord(s)), chr(1071))
print(s.encode("utf-8"))
print(len("Привет"), len("Привет".encode("utf-8")))

data = "Привет".encode("utf-8")
try:
    data.decode("ascii")
except UnicodeDecodeError as e:
    print("Ошибка:", e.reason)
print(data.decode("utf-8"))
1071 0x42f Я
b'\xd0\xaf'
6 12
Ошибка: ordinal not in range(128)
Привет

len считает кодовые точки, поэтому у слова «Привет» длина 6, а в UTF-8 оно занимает 12 байт. Функция ord возвращает номер кодовой точки Unicode (для латиницы он совпадает с кодом ASCII: ord("a") равно 97), chr делает обратное. При чтении файлов кодировку лучше указывать явно: open(path, encoding="utf-8") — кодировка по умолчанию зависит от ОС и настроек.

Если не получилось

  • TypeError: can only concatenate str (not "int") to str — складываете строку с числом; оберните число в str() или используйте f-строку.
  • TypeError: 'str' object does not support item assignment — пытаетесь менять строку по индексу; соберите новую строку срезами или через replace.
  • Строка «не изменилась» после s.strip() или s.replace(...) — результат метода не сохранен в переменную.
  • UnicodeDecodeError при чтении файла — файл в другой кодировке; укажите верную encoding в open().

Выводы

  • Строка str в Python неизменяема: методы и операции всегда возвращают новую строку, ее нужно сохранить.
  • Срез s[start:stop:step] не включает stop и не падает за границами строки, в отличие от одиночного индекса.
  • Для повседневной обработки текста хватает split, join, strip, replace, find и f-строк; strip работает с набором символов, а не с подстрокой.
  • len() считает кодовые точки Unicode, а не байты и не видимые символы; байты появляются только после encode.

Где применяется / связь с практикой

Освойте тему на практике

Разбор логов и CSV, очистка пользовательского ввода, формирование отчетов и сообщений, работа с API — везде основная доля кода состоит из операций со строками. Строки, коллекции, функции и работу с файлами последовательно разбирают на курсе Python Developer. Basic. Посмотреть формат занятий можно на бесплатных открытых уроках.

FAQ

Чем одинарные кавычки отличаются от двойных?
Ничем: оба вида создают одинаковый str. Выбирают тот, что избавляет от экранирования кавычек внутри текста.

Как проверить, что подстрока есть в строке?
Оператором in: "py" in "python" вернет True. Если нужна позиция, используйте find.

Есть ли в Python отдельный тип для одного символа?
Нет. Один символ — это строка длиной 1, например "a"; s[0] тоже возвращает строку.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)