Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 16 из 20 Начальный 15 мин 100 XP

Циклы и строки в Python: разбор текста на слова

Разбор текста начинается со split: строка превращается в список слов, цикл считает, ищет и собирает отчёт.

Редакция Питоники

Текст — самая ходовая работа для программы: логи, сообщения, имена файлов, ввод пользователя. Цикл по строке — это цикл по её символам, а разбор текста начинается со split: строка превращается в список слов. На этом уроке соединяем циклы и строки в рабочие связки: считаем слова и гласные, ищем подстроки в журнале, нумеруем строки и собираем маленький отчёт, который отвечает на вопрос о данных.

Слова строки: split

Строка сама по себе — одно целое. Метод split режет её по пробелам и отдаёт список слов, с которым цикл уже умеет работать:

строка превращается в список слов
line = 'Мама мыла раму утром'

words = line.split()
print(len(words))

for w in words:
    print(w, '-', len(w), 'букв')
Вывод
4
Мама - 4 букв
мыла - 4 букв
раму - 4 букв
утром - 5 букв

split() без аргумента режет строку по пробелам, порядок слов сохраняется. Дальше это обычный список: len(words) считает слова, а цикл for w in words ходит по каждому, и len(w) меряет длину слова. Связка «split — потом for» — самый частый вход в обработку текста: после неё доступны все приёмы из уроков про списки.

Подсчёт: гласные и не только

Цикл по строке идёт по символам: переменная ch по очереди принимает каждый символ строки. Проверка принадлежности ch in 'аеёиоуыэюя' отвечает на вопрос, входит ли символ в набор гласных:

сколько гласных в строке
text = 'Мама мыла раму'

vowels = 0
for ch in text.lower():
    if ch in 'аеёиоуыэюя':
        vowels += 1
print(vowels)
Вывод
6

Аккумулятор vowels стартует с нуля и растёт на единицу за каждую гласную — тот же счётчик из урока про аккумуляторы, только шаг цикла здесь не элемент списка, а символ строки. lower() приводит текст к нижнему регистру перед сравнением: без него заглавная М из начала предложения ускользнула бы от проверки, потому что символ М и символ м — разные символы.

Список строк: поиск подстроки

Список строк — почти файл: каждая строка — запись журнала. Оператор in работает в двух ролях: в for line in log он организует перебор, а внутри if проверяет, входит ли подстрока в строку:

сколько ошибок в журнале
log = [
    'сервер запущен',
    'ошибка соединения',
    'запрос обработан',
    'ошибка соединения',
]

errors = 0
for line in log:
    if 'ошибка' in line:
        errors += 1
print(errors)
Вывод
2

Проверка 'ошибка' in line отвечает True, если подстрока входит в строку, где бы она ни стояла: в начале, в конце, в середине. Несовпавшие строки просто не трогают счётчик — это та же фильтрация, что в уроке про continue, только без явного пропуска итерации.

Счётчик сказал, сколько ошибок, но не сказал, где они. Номер строки даёт enumerate: он выдаёт пары «номер — элемент».

где именно ошибки
log = [
    'сервер запущен',
    'ошибка соединения',
    'запрос обработан',
    'ошибка соединения',
]

for i, line in enumerate(log, start=1):
    if 'ошибка' in line:
        print('строка', i, ':', line)
Вывод
строка 2 : ошибка соединения
строка 4 : ошибка соединения

enumerate(log, start=1) нумерует итерации с единицы, поэтому в отчёте человеческие номера строк — вторая и четвёртая. Распаковка i, line разбирает каждую пару на две переменные: внутри цикла доступны и адрес строки, и её содержимое. Подробный разбор enumerate был отдельным уроком про индекс и значение.

Самое длинное слово

Классическая задача на аккумулирование: пройтись по словам и удержать самое длинное из виденных.

поиск самого длинного слова
text = 'мы программируем на питоне и это удобно'

longest = ''
for w in text.split():
    if len(w) > len(longest):
        longest = w
print(longest)
Вывод
программируем

Переменная longest стартует пустой строкой: у неё нулевая длина, поэтому первое же слово её перезапишет. Условие строгое, знак больше: при равных длинах побеждает первое слово, второе равное по длине результат не тронет. Этот же каркас решает обратные задачи — самое короткое слово, самая ранняя дата, первое слово на заданную букву: меняется только сравнение.

Многострочный текст

Настоящие тексты многострочные: файлы, сообщения, отчёты. Их режут на строки методом splitlines, а каждую строку — на слова уже знакомым split:

двухступенчатый разбор
text = 'первая строка\nвторая строка длиннее\nтретья'

total = 0
for line in text.splitlines():
    total += len(line.split())
print(total)
Вывод
6

Двойной разбор — строка за строкой, слово за словом — это каркас любого текстового отчёта: csv-файл, лог сервера и глава книги разбираются одинаково. Внешний цикл ходит по строкам, внутренний вызов split разрезает каждую на слова, аккумулятор складывает всё в один итог.

Частотный словарь: цикл, который считает повторы

От «сколько слов» до «какие слова повторяются» — один шаг, и делается он всё тем же циклом со счётчиком. Разница в том, что счётчик здесь не один, а по одному на каждое слово, и хранить их удобно в словаре.

считаем, сколько раз встретилось каждое слово
text = 'кот и кот и программист'
words = text.split()

counts = {}
for w in words:
    counts[w] = counts.get(w, 0) + 1

print(counts)
Вывод
{'кот': 2, 'и': 2, 'программист': 1}

Словарь хранит пары «слово — сколько раз встретилось». Ключи стоят в порядке первого появления: сначала кот, потом и, потом программист. Конструкция counts.get(w, 0) + 1 — это тот же аккумулятор, только счётчик выбирается по ключу: для нового слова get вернёт ноль, для знакомого — прежнее значение. Отсюда же растёт понимание, зачем нужен параметр по умолчанию.

выбираем самое частое слово
text = 'кот и кот и программист'
words = text.split()

counts = {}
for w in words:
    counts[w] = counts.get(w, 0) + 1

top = ''
top_count = 0
for w, c in counts.items():
    if c > top_count:
        top_count = c
        top = w
print('чаще всего:', top, '-', top_count)
Вывод
чаще всего: кот - 2

Каркас максимума поверх счётчика: перебираем пары «слово — количество» и держим лучшее. Кот и и набрали по два, программист — один, и при равных счётах побеждает первый встреченный: сравнение строгое, знак больше. Всё это — два цикла и один словарь, ничего нового.

Буквы против символов

Считать символы строки просто, а вот отличить буквы от пробелов и знаков — уже интереснее. Метод isalpha() отвечает на вопрос «это буква?» для одного символа, и цикл по строке превращает это в подсчёт.

буквы и все символы
s = 'Привет, мир! 123'

letters = 0
for ch in s:
    if ch.isalpha():
        letters += 1

print('символов всего:', len(s))
print('букв:', letters)
Вывод
символов всего: 16
букв: 9

Девять букв против шестнадцати символов: семь позиций заняли пробелы, запятая, восклицательный знак и три цифры. Метод isalpha() работает и с кириллицей, и с латиницей — ему всё равно, какой алфавит. Родные проверки работают и для цифр (isdigit()), и для знаков препинания (ispunctuation()), и для пробелов (isspace()).

считаем знаки препинания
text = 'Привет, мир! Как дела?'

marks = 0
for ch in text:
    if ch in ',.!?':
        marks += 1
print('знаков препинания:', marks)
Вывод
знаков препинания: 3

Три запятые-знака: запятая, восклицательный и вопросительный. Вариант со списком символов в строке работает и для конкретного набора знаков, а ispunctuation() посчитал бы их все — вместе с кавычками и тире, которых в этом тексте нет, но которые в живой речи встречаются постоянно.

Разбор по разделителю

split() без аргумента режет по пробелам — это удобно для текста. Но данные часто приходят в других форматах: csv-строка, разделённая запятыми, путь к файлу с обратными слэшами, теги через запятую. Для них у split есть второй аргумент — разделитель.

split по запятой
csv = 'a,b,c'

print(csv.split(','))
print(csv.split(',', 1))
Вывод
['a', 'b', 'c']
['a', 'b,c']

Второй вызов отличается только числом разбиений: maxsplit=1 режет строку один раз и оставляет хвост целиком. Это ровно то, что нужно, когда вы знаете, что в хвосте могут быть запятые: разделитель один, а остальное — данные. Число в скобках можно опустить, по умолчанию строка режется до конца.

Регистр: почему lower решает половину задач

Проверка подстроки в тексте выглядит простой, но спотыкается о регистр: 'ошибка' не найдётся в строке 'Ошибка'. Приводить текст к нижнему регистру перед сравнением — стандартный приём, и он же объясняет, почему в реальных логах ошибок находят больше, чем кажется.

поиск с учётом регистра
line = 'Ошибка: не найден файл'

print('ошибка' in line)
print('ошибка' in line.lower())
Вывод
False
True

Первая проверка не нашла ничего — заглавная О не совпала со строчной о. Вторая нашла, потому что lower() привёл обе стороны к одному регистру. Тот же приём работает для подсчёта: сравнивать с приведённой строкой или приводить в цикле — выбор только в том, где стоит вызов.

Чистка перед разбором

Реальные строки приходят с мусором: лишние пробелы по краям, пустые строки между абзацами, табы вместо пробелов. Разбор до чистки даёт мусорные слова и лишние элементы в списке, и все последующие счётчики считают не то.

strip убирает края, split режет
raw = '   привет   '

print(repr(raw.strip()))
print(raw.strip().split())
print(repr('a,,b'.split(',')))
Вывод
'привет'
['привет']
['a', '', 'b']

Метод strip срезает пробелы с обоих концов строки — именно края, внутренние пробелы он не трогает. Функция repr показывает результат в кавычках, и по ним видно, что пробелов больше нет: без repr пустая строка выглядела бы так же, как строка с одним пробелом. А вот split(',') оставил пустой кусок — и его придётся отфильтровать отдельно.

фильтр пустых кусков
parts = 'a,,b'.split(',')

clean = []
for p in parts:
    if p:
        clean.append(p)
print(clean)
print([p for p in parts if p])
Вывод
['a', 'b']
['a', 'b']

Две записи дают один результат: цикл с continue и генератор списка с условием. Вторая короче, первая понятнее новичку. На практике я бы оставил цикл в том месте, где разбор идёт впервые, а в рабочем коде перешёл бы на генератор: чистка — это операция над коллекцией, а не действие.

Что дальше: текст становится данными

Разобранный текст — это уже не текст, а структура: список слов, словарь частот, набор чисел. Дальше с такой структурой работают обычные инструменты: сортировка, срезы, генераторы списков. Весь разбор — это мост между строками и коллекциями, а мост этот строится циклом.

ЗадачаИнструментГде встречается
слова строкиsplit()первый шаг почти любого разбора
символы строкиfor ch in textпосимвольные проверки
буквы без знаковch.isalpha()подсчёт букв и слов
поиск подстроки'слово' in lineлоги, анкеты, отчёты
частотыcounts.get(w, 0) + 1анализ текста
разбор по разделителюsplit(',')csv, теги, пути
нормализацияlower(), strip()регистр и лишние пробелы
ИнструментЧто делает
split()Режет строку по пробелам в список слов
splitlines()Режет многострочный текст в список строк
ch in 'аеёиоуыэюя'Проверяет, входит ли символ в набор
'ошибка' in lineПроверяет, входит ли подстрока в строку
enumerate(lines, start=1)Выдаёт пары «номер строки — строка»

Мини-отчёт из цикла

Соберём всё вместе: журнал заказов, из которого цикл извлекает ответ на деловой вопрос.

отчёт по заказам
report = [
    'заказ 1: доставлен',
    'заказ 2: отменён',
    'заказ 3: доставлен',
    'заказ 4: доставлен',
]

delivered = 0
for line in report:
    if 'доставлен' in line:
        delivered += 1
print('Доставлено:', delivered)
print('Всего заказов:', len(report))
Вывод
Доставлено: 3
Всего заказов: 4

Три инструмента — перебор, in и аккумулятор — сложились в программу, которая читает данные и отвечает на вопрос о них. Дальше по курсу такие отчёты станут привычными: финальный проект раздела соберёт из тех же деталей полный разбор оценок класса.

фрагмент: обратная сборка
words = ['мама', 'мыла', 'раму']
line = ' '.join(words)   # 'мама мыла раму'
Фрагмент: join склеивает список слов обратно в строку через разделитель — сборка и разбор текста ходят парами, подробно об этом в курсе строк.
Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

text = 'раз два три'

print(len(text.split()))
count = 0
for ch in 'аоа':
    if ch == 'а':
        count += 1
print(count)
total = ''
for w in ['а', 'б', 'в']:
    total += w
print(total)
Проверь себя
0 / 8

1. Что вернёт 'Мама'.split()?

2. Сколько итераций сделает цикл for ch in 'кот'?

3. Как найти строки журнала, в которых есть слово «ошибка»?

4. Что даёт enumerate(log, start=1) при переборе списка строк?

5. Зачем перед подсчётом гласных текст прогоняют через lower()?

6. Как посчитать, сколько раз каждое слово встретилось в тексте?

7. Что вернёт 'a,,b'.split(',')?

8. Зачем нужны isalpha(), isdigit() и подобные методы?

Карточки терминов
Запомнено: 0 / 9
Практика

Для строки 'Изучаю циклы и массивы' выведи, сколько в ней слов, а затем посчитай и выведи, сколько в ней букв а (в любом регистре).

practice.py
Вопросы и ответы по уроку

Как перебрать слова в строке в Python?

Разбить строку на слова через split() и пройти циклом: for w in text.split(). Каждое w — отдельное слово, len(w) даст его длину, а аккумулятор посчитает любые подходящие слова.

Урок 9 курса строк: split

Как посчитать, сколько раз символ встречается в строке?

Циклом по строке с аккумулятором: счётчик стартует с нуля и растёт на каждый совпавший символ. Для готовой строки есть и метод count: text.count('а') сделает то же самое одним вызовом.

Чем split отличается от splitlines?

split() режет по пробелам на слова, splitlines() — по переводам строки на строки текста. Многострочный разбор обычно двухступенчатый: сначала splitlines, потом split для каждой строки.

Как в цикле получить и номер строки, и её содержимое?

enumerate со start=1: for i, line in enumerate(lines, start=1). Распаковка разбирает пару на две переменные, а start=1 делает номера человеческими — первая строка получит номер 1.

Урок 4: enumerate

Как посчитать, сколько раз каждое слово встретилось в тексте?

Циклом по словам и счётчиком в словаре: counts[w] = counts.get(w, 0) + 1. Ключи в словаре стоят в порядке первого появления. В рабочем коде то же делает Counter из курса словарей, но цикл стоит написать руками хотя бы раз.

Курс словарей

Как разделить строку по запятой и не получить лишних пустых элементов?

split(',') режет по запятым, и между соседними запятыми остаётся пустой кусок. Чистят его фильтром: [p for p in parts if p]. Заодно помогает strip() — он срезает пробелы с краёв, чтобы в словарь не попадали ключи вроде ' a'.

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

Циклом по символам с проверкой isalpha(): счётчик растёт на каждый символ, для которого ch.isalpha() истинно. Для цифр есть isdigit(), для знаков препинания — ispunctuation().

Понравился урок? Сошлитесь на него

«Цикл по строке — это цикл по её символам, а разбор текста начинается со split: строка превращается в список слов.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.