Модуль 04 — Строки

Текст — самые частые данные в программе: разобрать, проверить, привести к порядку

Прогресс курса Модуль 4 из 9

Что вы освоите в этом модуле

01

Индексы

Строка — последовательность символов. У каждого символа есть номер — индекс. Отсчёт с нуля: у первого символа индекс 0, у последнего — len(s) - 1. Отрицательные индексы считают с конца: -1 — последний символ.

СимволPython
Индекс012345
С конца-6-5-4-3-2-1
>>> s = "Python"
>>> len(s)
6
>>> s[0]
'P'
>>> s[5]
'n'
>>> s[-1]
'n'
>>> s[-2]
'o'
>>> s[6]
Traceback (most recent call last):
  File "<python-input-6>", line 1, in <module>
    s[6]
    ~^^^
IndexError: string index out of range

Индекса 6 в строке из 6 символов нет — IndexError. Отдельного типа «символ» в Python нет: s[0] — тоже строка, длиной 1.

02

Срезы

Срез s[начало:конец:шаг] возвращает часть строки. Правила те же, что у range: конец не включается; пропущенное начало — с начала строки, пропущенный конец — до конца.

>>> s = "Санкт-Петербург"
>>> s[0:5]
'Санкт'
>>> s[:5]
'Санкт'
>>> s[6:]
'Петербург'
>>> s[-4:]
'бург'
>>> s[::2]
'СнтПтруг'
>>> s[::-1]
'грубретеП-ткнаС'
>>> s[100:]
''

s[:5] и s[5:] вместе всегда дают всю строку — удобно делить строку по позиции. s[::-1] — строка задом наперёд. В отличие от индекса, срез за пределами строки ошибку не вызывает, а возвращает пустую строку.

03

Строки неизменяемы

Изменить символ внутри существующей строки нельзя. Любая «правка» создаёт новую строку, и её присваивают той же или другой переменной:

>>> word = "кот"
>>> word[0] = "К"
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    word[0] = "К"
    ~~~~^^^
TypeError: 'str' object does not support item assignment
>>> word = "К" + word[1:]
>>> word
'Кот'

То же касается методов: text.upper() не меняет text, а возвращает новую строку. Частая ошибка — вызвать метод и не сохранить результат: строка name.strip() отдельной командой ничего не делает.

04

Методы: очистка, разбиение, сборка

Метод — функция, которая принадлежит значению и вызывается через точку: строка.метод(аргументы). У строк их несколько десятков; эти нужны постоянно:

МетодЧто возвращает
s.strip(), lstrip(), rstrip()Без пробелов и переводов строки по краям (с обоих, слева, справа). С аргументом — без указанных символов: s.strip(".,!")
s.lower(), s.upper()Все буквы строчные / прописные
s.title(), s.capitalize()Каждое слово / первое слово с прописной
s.replace(старое, новое)Все вхождения заменены
s.split(разделитель)Список частей; без аргумента — по любым пробельным символам
разделитель.join(список)Одна строка из списка строк
>>> text = "  Привет, Мир!  "
>>> text.strip()
'Привет, Мир!'
>>> text.lower()
'  привет, мир!  '
>>> text.upper()
'  ПРИВЕТ, МИР!  '
>>> text.strip().replace("Мир", "Python")
'Привет, Python!'
>>> text
'  Привет, Мир!  '
>>> "a,b,,c".split(",")
['a', 'b', '', 'c']
>>> "раз  два   три".split()
['раз', 'два', 'три']
>>> " | ".join(["Иванов", "Пётр", "21В"])
'Иванов | Пётр | 21В'

Сравните два вызова split: с явным разделителем "," пустая часть между двумя запятыми сохраняется; без аргумента лишние пробелы схлопываются. join вызывается у разделителя, а не у списка — это нужно просто запомнить. Список (в квадратных скобках) — тема модуля 05; здесь достаточно знать, что split его возвращает, а for умеет его перебирать.

Методы можно сцеплять: text.strip().replace(...) — сначала очистка, потом замена над её результатом. Исходная text не изменилась.

05

Поиск и проверки

>>> email = "student@nevabit.ru"
>>> "@" in email
True
>>> email.find("@")
7
>>> email.find("#")
-1
>>> email.index("#")
Traceback (most recent call last):
  File "<python-input-4>", line 1, in <module>
    email.index("#")
    ~~~~~~~~~~~^^^^^
ValueError: substring not found
>>> email.count(".")
1
>>> email.startswith("student")
True
>>> email.endswith((".ru", ".рф"))
True
>>> email.split("@")
['student', 'nevabit.ru']

in отвечает на вопрос «есть ли» — это самый читаемый вариант. find возвращает индекс первого вхождения или -1, если не нашёл; index делает то же, но при отсутствии выбрасывает ошибку. startswith и endswith принимают и несколько вариантов в круглых скобках.

Методы is… проверяют, из каких символов состоит строка, — пригодятся до преобразования ввода в число:

>>> "2026".isdigit()
True
>>> "20.5".isdigit()
False
>>> "Анна".isalpha()
True
>>> "abc123".isalnum()
True
>>> "   ".isspace()
True
>>> "иванов иван".title()
'Иванов Иван'
>>> "Ёлка" < "Яблоко"
True
>>> "ёлка" < "яблоко"
False
>>> "Б" < "а"
True
>>> ord("A"), ord("а"), ord("ё")
(65, 1072, 1105)
>>> chr(1071)
'Я'

isdigit отвечает «все символы — цифры», поэтому "20.5" не проходит: точка — не цифра.

Последние строки сеанса показывают, как сравниваются строки: посимвольно, по номерам символов в Unicode. ord даёт номер символа, chr — символ по номеру. Все прописные русские буквы идут раньше строчных, поэтому "Б" < "а". А «ё» стоит отдельно, после «я» (1105 против 1103), и "ёлка" < "яблоко" ложно. Для сортировки русских слов «как в словаре» есть модуль locale; простой приём — сравнивать s.lower().replace("ё", "е").

06

Специальные символы

Обратная косая черта \ внутри строки начинает экранированную последовательность: \n — перевод строки, \t — табуляция, \\ — сама косая черта, \" — кавычка.

>>> print("Колонка1\tКолонка2")
Колонка1	Колонка2
>>> print('Он сказал: "Готово"')
Он сказал: "Готово"
>>> print("Путь: C:\\Users\\student")
Путь: C:\Users\student
>>> print(r"Путь: C:\Users\student")
Путь: C:\Users\student
>>> s = """Первая строка
... вторая строка"""
>>> print(s)
Первая строка
вторая строка
>>> len("Ёж\n")
3

Кавычки другого вида внутри строки экранировать не нужно: строка в одинарных кавычках спокойно содержит двойные. Префикс r («raw», сырая строка) отключает экранирование — так удобно записывать пути Windows и регулярные выражения. Строка в тройных кавычках может занимать несколько строк кода. \n — один символ, поэтому длина "Ёж\n" — 3.

07

Перебор символов

for по строке выдаёт символы по одному. Так считают, фильтруют и собирают новую строку:

>>> for ch in "кот":
...     print(ch, ord(ch))
... 
к 1082
о 1086
т 1090
>>> vowels = 0
>>> for ch in "Программирование":
...     if ch.lower() in "аеёиоуыэюя":
...         vowels += 1
... 
>>> vowels
7

Строку-результат собирают через +=. Пример — номер телефона в едином виде, как бы его ни ввели:

# Приводит номер телефона к виду +7 (XXX) XXX-XX-XX
raw = input("Телефон: ")
digits = ""
for ch in raw:
    if ch.isdigit():
        digits += ch

if len(digits) == 11 and digits[0] in "78":
    digits = digits[1:]
if len(digits) != 10:
    print("Ошибка: в номере должно быть 10 цифр после кода страны")
else:
    print(f"+7 ({digits[:3]}) {digits[3:6]}-{digits[6:8]}-{digits[8:]}")
$ python3 phone.py
Телефон: 8 (921) 555-12-34
+7 (921) 555-12-34
$ python3 phone.py
Телефон: +7-921-555-1234
+7 (921) 555-12-34
$ python3 phone.py
Телефон: 9215551234
+7 (921) 555-12-34
$ python3 phone.py
Телефон: 921-55-12
Ошибка: в номере должно быть 10 цифр после кода страны

Программа сначала оставляет только цифры, затем отрезает код страны (7 или 8) и только после этого проверяет длину. Срезы раскладывают 10 цифр на группы.

Слова текста: split делит по пробелам, strip обрезает знаки препинания, «слово» из одного тире пропускается:

text = input("Текст: ")
count = 0
longest = ""
for w in text.lower().split():
    w = w.strip(".,!?;:«»\"()—-")
    if w == "":          # тире или знак препинания отдельно — не слово
        continue
    count += 1
    if len(w) > len(longest):
        longest = w
print("Слов:", count)
print("Самое длинное:", longest, f"({len(longest)} букв)")
$ python3 words.py
Текст: Мороз и солнце; день чудесный! Ещё ты дремлешь, друг прелестный — пора, красавица, проснись.
Слов: 13
Самое длинное: прелестный (10 букв)
08

Две задачи целиком

Палиндром — фраза, которая читается одинаково в обе стороны, если не обращать внимания на пробелы, регистр и знаки препинания. Оставляем буквы в нижнем регистре и сравниваем со срезом [::-1]:

phrase = input("Фраза: ")
letters = ""
for ch in phrase.lower():
    if ch.isalpha():
        letters += ch
if letters == letters[::-1]:
    print("Палиндром")
else:
    print("Не палиндром")
$ python3 palindrome.py
Фраза: А роза упала на лапу Азора
Палиндром
$ python3 palindrome.py
Фраза: Привет, мир
Не палиндром

Шифр Цезаря — каждая буква заменяется на букву, стоящую дальше по алфавиту на заданный сдвиг; после «я» счёт идёт снова с «а». Позицию буквы даёт index, «по кругу» — остаток от деления на длину алфавита:

# Шифр Цезаря для русского алфавита
ALPHABET = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя"
shift = int(input("Сдвиг: "))
text = input("Текст: ")
result = ""
for ch in text:
    low = ch.lower()
    if low in ALPHABET:
        i = (ALPHABET.index(low) + shift) % len(ALPHABET)
        new = ALPHABET[i]
        result += new.upper() if ch.isupper() else new
    else:
        result += ch
print(result)
$ python3 caesar.py
Сдвиг: 3
Текст: Привет, мир!
Тулезх, плу!
$ python3 caesar.py
Сдвиг: -3
Текст: Тулезх, плу!
Привет, мир!

Строка new.upper() if ch.isupper() else new — условное выражение: короткая запись if/else, которая возвращает значение. Здесь оно сохраняет регистр буквы. Имя ALPHABET прописными по соглашению означает константу — значение, которое программа не меняет. Расшифровка — тот же алгоритм с обратным сдвигом; остаток от деления в Python для отрицательных чисел тоже неотрицательный, поэтому сдвиг влево работает без отдельной ветки.

Ключевые выводы модуля

Запомните главное

  • Индексы с нуля, -1 — последний символ; срез s[a:b] не включает b и не выходит за границы с ошибкой
  • Строки неизменяемы: методы возвращают новую строку, её нужно сохранить
  • strip — очистить края, split — разбить, "…".join — склеить, replace — заменить
  • in — проверить вхождение, find — найти позицию (или -1)
  • Строки сравниваются по номерам Unicode: прописные раньше строчных, «ё» после «я»
  • \n, \t, \\ — специальные символы; r"…" отключает их
Практические задания — на учебном портале. Задания, критерии оценивания, сдача работ и оценки преподавателя — в курсе на portal.nevabit.ru. Учётную запись выдаёт преподаватель.
Модуль 03: Условия и циклы Все модули Модуль 05: Коллекции