💻
Разработка в IT
Опубликовано:
16.04.2026
Обновлено:
16.04.2026

Срезы строк в Python: как работает slicing с примерами

Алексей Иванов

Первый раз я столкнулся со срезами, когда пытался вытащить доменное имя из email-адреса в лог-файле. Нужно было взять подстроку между символом @ и пробелом. Два индекса, двоеточие, и Python вернул ровно то, что нужно. Срез (slice) это способ получить часть последовательности, указав начальную позицию, конечную позицию и шаг. Синтаксис выглядит так: sequence[start:stop:step].

Эта статья разбирает механику срезов строк от базового синтаксиса до отрицательных шагов, объясняет типичные ошибки и показывает, как срезы работают со списками и пользовательскими объектами.

Индексы строк: фундамент для срезов

Прежде чем разбирать срезы, нужно разобраться с индексами. Строка в Python это последовательность символов, каждый из которых имеет числовой индекс. Первый символ получает индекс 0, второй 1 и так далее.

fruit = "banana"
print(fruit[0])  # b
print(fruit[1])  # a
print(fruit[5])  # a

Длина строки "banana" равна 6 (символы с индексами от 0 до 5). Попытка обратиться к fruit[6] вызовет IndexError.

Отрицательные индексы

Python поддерживает отрицательную индексацию. Индекс -1 указывает на последний символ, -2 на предпоследний.

fruit = "banana"
print(fruit[-1])  # a (последний символ)
print(fruit[-2])  # n
print(fruit[-6])  # b (первый символ)

Отрицательные и положительные индексы ссылаются на те же символы. Для строки "banana" fruit[0] и fruit[-6] дают одинаковый результат "b".

b a n a n a
0 1 2 3 4 5 ← положительные
-6 -5 -4 -3 -2 -1 ← отрицательные

Базовый синтаксис среза: start и stop

Срез записывается в квадратных скобках через двоеточие: s[start:stop]. Python возвращает подстроку от индекса start (включительно) до индекса stop (не включая).

s = "Monty Python"
print(s[0:5])  # Monty
print(s[6:12])  # Python

Индекс stop не входит в результат. Это правило звучит непривычно, но даёт удобное свойство: длина среза равна stop - start.

s = "banana"
print(s[0:3])  # ban  (3 символа: 0, 1, 2)
print(s[3:6])  # ana  (3 символа: 3, 4, 5)

Пропуск start и stop

Если start не указан, Python начинает с начала строки. Если stop не указан, срез идёт до конца.

fruit = "banana"
print(fruit[:3])  # ban (от начала до индекса 3)
print(fruit[3:])  # ana (от индекса 3 до конца)
print(fruit[:])  # banana (копия всей строки)

Срез fruit[:] создаёт копию строки. Для неизменяемых строк это не имеет практического значения, но для списков list[:] часто используется для создания поверхностной копии.

Срез с одинаковыми start и stop

fruit = "banana"
print(fruit[3:3])  # пустая строка

Когда start равен stop, результат всегда пустая строка. Никакой ошибки Python не выбрасывает.

Третий параметр: шаг (step)

Полный синтаксис среза: s[start:stop:step]. Параметр step определяет, через сколько символов брать следующий.

Шаг больше единицы

s = "abcdefghij"
print(s[0:10:2])  # acegi   (каждый второй символ)
print(s[1:10:2])  # bdfhj   (каждый второй, начиная с индекса 1)
print(s[::3])  # adgj    (каждый третий символ)

При step=2 Python берёт символы с индексами 0, 2, 4, 6, 8. При step=3 с индексами 0, 3, 6, 9.

Отрицательный шаг

Отрицательный шаг переворачивает направление обхода. Python идёт от большего индекса к меньшему.

s = "abcdef"
print(s[::-1])  # fedcba  (реверс строки)
print(s[::-2])  # fdb     (каждый второй в обратном порядке)

Запись s[::-1] это каноничный способ развернуть строку в Python. Без start и stop Python берёт всю строку и обходит её справа налево.

Отрицательный шаг с границами

При отрицательном шаге start должен быть больше stop. Иначе срез вернёт пустую строку.

s = "abcdef"
print(s[4:1:-1])  # edc  (от индекса 4 до индекса 2, шаг -1)
print(s[1:4:-1])  # пустая строка (start < stop при шаге -1)

Границы при отрицательном шаге: start указывает, откуда начинаем (включительно), stop указывает, где останавливаемся (не включая). Направление обхода определяется знаком step.

s = "информатика"
print(s[-1::-1])  # акитамрофни (от последнего до первого)
print(s[-2:1:-1])  # китамрофн   (от предпоследнего до индекса 2)

Срезы не вызывают IndexError

Одно из удобных свойств срезов: выход за границы строки не вызывает ошибку. Python просто обрезает результат до доступных символов.

s = "hello"
print(s[0:100])  # hello  (stop выходит за границу, но ошибки нет)
print(s[-100:3])  # hel    (start выходит за границу)
print(s[10:20])  # пустая строка (оба за границей)

Это отличается от обычного индексирования: s[100] вызовет IndexError, а s[0:100] вернёт всю строку.

Как Python вычисляет срез внутри

Когда вы пишете s[start:stop:step], Python создаёт объект slice(start, stop, step) и передаёт его методу __getitem__. Метод slice.indices(length) вычисляет реальные значения start, stop, step с учётом длины последовательности.

s = slice(1, 10, 2)
print(s.indices(6))  # (1, 6, 2)

Функция indices() приводит значения к корректному диапазону. Здесь stop=10 было урезано до 6 (длина строки). Этот механизм объясняет, почему срезы не вызывают IndexError: границы нормализуются до реальных индексов.

Именованные срезы

Объект slice() можно сохранить в переменную и применять многократно.

YEAR = slice(0, 4)
MONTH = slice(5, 7)
DAY = slice(8, 10)

date_str = "2026-04-07"
print(date_str[YEAR])  # 2026
print(date_str[MONTH])  # 04
print(date_str[DAY])  # 07

Именованные срезы делают код читаемее, когда одна и та же структура разбирается в нескольких местах программы.

Практические примеры со строками

Извлечение домена из email

data = "From stephen.marquard@uct.ac.za Sat Jan  5 09:14:16 2008"
at_pos = data.find("@")
sp_pos = data.find(" ", at_pos)
host = data[at_pos + 1 : sp_pos]
print(host)  # uct.ac.za

Метод find() находит позицию символа, а срез извлекает подстроку между двумя позициями.

Проверка палиндрома

def is_palindrome(s):
    cleaned = s.lower().replace(" ", "")
    return cleaned == cleaned[::-1]


print(is_palindrome("А роза упала на лапу Азора"))  # True
print(is_palindrome("hello"))  # False

Срез [::-1] разворачивает строку. Если строка равна своему реверсу, она палиндром.

Маскирование номера карты

card = "4111111111111111"
masked = card[:4] + " **** **** " + card[-4:]
print(masked)  # 4111 **** **** 1111

Срез card[:4] берёт первые четыре цифры, card[-4:] последние четыре.

Удаление расширения файла

filename = "report_2026.pdf"
dot_pos = filename.rfind(".")
name = filename[:dot_pos]
print(name)  # report_2026

Метод rfind() ищет последнюю точку (на случай, если в имени файла несколько точек), а срез берёт всё до неё.

Извлечение каждого второго символа

cipher = "Phyetlhloonг"
print(cipher[::2])  # Python
print(cipher[1::2])  # hellо г

Срезы с шагом 2 позволяют разделить чётные и нечётные позиции строки.

Срезы списков

Срезы работают одинаково для любых последовательностей: строк, списков, кортежей. Но у списков есть дополнительная возможность: присваивание срезу.

Чтение среза списка

numbers = [10, 20, 30, 40, 50, 60]
print(numbers[1:4])  # [20, 30, 40]
print(numbers[::2])  # [10, 30, 50]
print(numbers[::-1])  # [60, 50, 40, 30, 20, 10]

Срез списка возвращает новый список. Оригинал не меняется.

Присваивание срезу

a = [1, 2, 3, 4, 5]
a[2:4] = [7, 8, 9]  # заменяем 2 элемента на 3
print(a)  # [1, 2, 7, 8, 9, 5]

b = [10, 20, 30, 40, 50]
b[1:4] = []  # удаляем элементы с 1 по 3
print(b)  # [10, 50]

c = [1, 2, 5]
c[2:2] = [3, 4]  # вставка без удаления
print(c)  # [1, 2, 3, 4, 5]

При присваивании непрерывному срезу (без шага) количество новых элементов может отличаться от количества заменяемых. Список автоматически вырастет или сократится.

Присваивание срезу с шагом

a = [1, 2, 3, 4, 5, 6, 7]
a[::2] = [10, 20, 30, 40]  # a[::2] содержит 4 элемента
print(a)  # [10, 2, 20, 4, 30, 6, 40]

При присваивании срезу с шагом количество новых элементов обязано совпадать с количеством позиций в срезе. Иначе Python выбросит ValueError.

Копирование списка через срез

original = [1, 2, 3]
copy = original[:]
copy.append(4)
print(original)  # [1, 2, 3]  (не изменился)
print(copy)  # [1, 2, 3, 4]

Срез [:] создаёт поверхностную копию (shallow copy). Для вложенных списков элементы копируются по ссылке, а не по значению.

Срезы и строковая неизменяемость

Строки в Python неизменяемы (immutable). Присвоить значение срезу строки нельзя.

greeting = "Hello, world!"
try:
    greeting[0:5] = "Jello"
except TypeError as e:
    print(e)
# 'str' object does not support item assignment

Для изменения части строки нужно создать новую:

greeting = "Hello, world!"
new_greeting = "Jello" + greeting[5:]
print(new_greeting)  # Jello, world!

Сравнение индексирования и среза

Операция Синтаксис Возвращает IndexError?
Один символ s[i] Строку длиной 1 Да, при выходе за границы
Срез s[i:j] Подстроку (может быть пустой) Нет, границы обрезаются
Срез с шагом s[i:j:k] Подстроку с выборкой Нет, границы обрезаются

Эта разница объясняет, почему s[100] падает с ошибкой, а s[0:100] спокойно возвращает строку.

Реализация срезов в своих классах

Чтобы поддержать срезы в пользовательском классе, нужно реализовать метод __getitem__. Когда к объекту применяют [] с двоеточием, Python передаёт в __getitem__ объект slice.

class MyList:
    def __init__(self, data):
        self._data = list(data)

    def __getitem__(self, key):
        if isinstance(key, slice):
            return MyList(self._data[key])
        return self._data[key]

    def __repr__(self):
        return f"MyList({self._data})"


m = MyList([10, 20, 30, 40, 50])
print(m[1:4])  # MyList([20, 30, 40])
print(m[::2])  # MyList([10, 30, 50])
print(m[3])  # 40

Неочевидные детали

Первый факт: шаг не может быть нулём. Запись s[::0] вызовет ValueError: slice step cannot be zero.

Второй факт: срез строки возвращает строку, срез списка возвращает список, срез кортежа возвращает кортеж. Тип результата всегда совпадает с типом исходного объекта.

Третий факт: s[:n] + s[n:] всегда равно s для любого n, даже если n выходит за границы. Это свойство полезно при разбиении строки на две части.

s = "Python"
for n in range(-10, 10):
    assert s[:n] + s[n:] == s  # никогда не упадёт

Четвёртый факт: в CPython если срез совпадает со всей строкой (s[:]), Python может вернуть ссылку на тот же объект, потому что строки неизменяемы.

Пятый факт: для байтовых строк (bytes) и bytearray срезы работают по тому же принципу. Срез bytes возвращает bytes, срез bytearray возвращает bytearray.

FAQ

Почему stop не включается в результат?

Это соглашение Python, заимствованное из C. Полуоткрытый интервал [start, stop) даёт два полезных свойства: len(s[a:b]) == b - a и s[:n] + s[n:] == s. Оба перестали бы работать, если бы stop включался.

Можно ли использовать переменные в качестве границ среза?

Да. Границы это обычные целые числа или None. Переменные, выражения, вызовы функций допустимы: s[start:end], s[:len(s)//2], s[a+1:b-1].

Как получить каждый n-й символ начиная с конца?

Используйте отрицательный шаг: s[::-n]. Например, s[::-3] вернёт каждый третий символ в обратном порядке.

Как развернуть слова в предложении, сохранив порядок букв?

Разделите строку, разверните список, соедините обратно:

sentence = "Python очень мощный"
reversed_words = " ".join(sentence.split()[::-1])
print(reversed_words)  # мощный очень Python

Здесь split() создаёт список слов, [::-1] разворачивает его, join() склеивает обратно.

Срезы работают с range?

Да, начиная с Python 3. range поддерживает и индексирование, и срезы, которые возвращают новый range-объект:

r = range(0, 20, 2)
print(r[2:5])  # range(4, 10, 2)
print(list(r[2:5]))  # [4, 6, 8]

Мой совет: запомните три правила и срезы перестанут путать. Первое: start включается, stop нет. Второе: пропущенные границы означают "от начала" и "до конца". Третье: отрицательный шаг переворачивает направление, и тогда start должен быть больше stop.

Читайте также