Текст — самые частые данные в программе: разобрать, проверить, привести к порядку
Строка — последовательность символов. У каждого символа есть номер — индекс. Отсчёт с нуля: у первого символа индекс 0, у последнего — len(s) - 1. Отрицательные индексы считают с конца: -1 — последний символ.
| Символ | P | y | t | h | o | n |
|---|---|---|---|---|---|---|
| Индекс | 0 | 1 | 2 | 3 | 4 | 5 |
| С конца | -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.
Срез s[начало:конец:шаг] возвращает часть строки. Правила те же, что у range: конец не включается; пропущенное начало — с начала строки, пропущенный конец — до конца.
>>> s = "Санкт-Петербург"
>>> s[0:5]
'Санкт'
>>> s[:5]
'Санкт'
>>> s[6:]
'Петербург'
>>> s[-4:]
'бург'
>>> s[::2]
'СнтПтруг'
>>> s[::-1]
'грубретеП-ткнаС'
>>> s[100:]
''
s[:5] и s[5:] вместе всегда дают всю строку — удобно делить строку по позиции. s[::-1] — строка задом наперёд. В отличие от индекса, срез за пределами строки ошибку не вызывает, а возвращает пустую строку.
Изменить символ внутри существующей строки нельзя. Любая «правка» создаёт новую строку, и её присваивают той же или другой переменной:
>>> 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() отдельной командой ничего не делает.
Метод — функция, которая принадлежит значению и вызывается через точку: строка.метод(аргументы). У строк их несколько десятков; эти нужны постоянно:
| Метод | Что возвращает |
|---|---|
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 не изменилась.
>>> 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("ё", "е").
Обратная косая черта \ внутри строки начинает экранированную последовательность: \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.
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 букв)
Палиндром — фраза, которая читается одинаково в обе стороны, если не обращать внимания на пробелы, регистр и знаки препинания. Оставляем буквы в нижнем регистре и сравниваем со срезом [::-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)\n, \t, \\ — специальные символы; r"…" отключает их