Разберемся с начала, что это за статья зачем она и для кого. Пришлось мне в рамках хакатона "Поколение ИТ" писать бота для телеги.  Но готового решения для пагинации, которое бы нам подходило мы не нашли. Поэтому было принято решение изобретать велосипед. Решение моих товарищей было максимально странным, брать количество записей и перебирать их в цикле от 1 до N (конца, записей), но данная идея сразу была отброшена. Поэтому предоставляю вашему вниманию наше творчество, которое мы изобрели. Постараюсь максимально просто и в полной мере описать, как мы собирали эту балалайку. Извинюсь сразу, если статья слишком нудная, это моя первая статья. Покажу для начала, что мы вообще сделали и о чем пойдет речь. Времени было мало, и нужен был алгоритм, который позволит быстро и просто реализовать пагинацию под разные таблицы. Для простоты описания вся суть алгоритма будет описана на шаблоном боте, который будет отвечать на любое сообщение. Заглушкой ответа на данном этапе будет сообщение "Привет" Проверили, все работает. А значит идем дальше. Первым делом в начале кода пропишем: Теперь добавим inline кнопку скрыть, для удаления сообщения от бота. Перед тем, как отправить сообщение пользователю создадим markup  с inline кнопкой "скрыть" после чего отправим пользователю сообщение с данной кнопкой. Чтобы кнопка работала, нам для нее нужен отдельный обработчик он выглядит так:    Необходимо учитывать, что здесь может возникнуть эксепшн, т.к у телеграмма есть ограничения на удаление. Например старые сообщения бот не сможет удалить. Поэтому нужно писать отдельное исключение. Но цель моей статьи заключается в том, что бы показать мой подход к демонстрации алгоритма пагинации. Снова проверяем: При нажатии на кнопку скрыть сообщение удаляется, а значит не будем зацикливаться больше на этом. Теперь перейдем непосредственно к кнопкам "Вперд" и "Назад" для перехода по страницам? которые будут располагаться под кнопкой "скрыть". К уже существующему markup добавим еще две новые кнопки. Т.к при первом сообщение от бота приходит первая страница, добавим пока что только кнопку "Вперед".  Кнопка вперед будет в callback_data отправлять строку 'next-page', а в обработчике мы будем прибавлять к page 1. После чего пересоздадим markap уже с кнопкой назад и новым сообщением. Аналогичным образом для кнопки назад в callback_data будем отправлять строку 'back-page' Теперь наш код выглядит вот так:    На данном этапе для простоты демонстрации я делаю count и page глобальными переменными, позже я заменю их.  Теперь мы можем перемещаться по страницам: Да, но перед нами встает две проблемы: Нужно писать отдельные исключения, чтобы при возврате кнопкой назад на первую страницу - кнопка назад больше не отображалась. А при переходе на последнюю не было кнопки вперед. Проблема передачи локальной переменной (page и count) от главной функции в обработчик нажатия кнопки. Telebot в отличие от например aiogramm не может передать другие параметры вместе с callback_data. А callback_data – это строка. Решение этой проблемы, я увидел в передаче в callback_data склеенного через разделитель json в который и запишу count и page. Мой товарищ вышел из данной ситуации более странным на мой взгляд решением – он записывал во временную таблицу бд id узера, id сообщение и страницу, которую он смотрит и потом удалял их. Достаточно радикальный способ по ряду многих причин (что если не удалиться запись из БД; БД вообще не для этого сделана; нам нужно будет столько таблиц, во скольких местах будет пагинация), но переубедить я его не смог ).Как вариант еще можно создать публичный словарь, но ради двух переменных это странно + способ с передачей json с двумя параметрами, как строку в callback_data на мой взгляд кажется самым универсальным и адекватным решением при данной проблеме. Для начала решим 2 проблему Удалим глобальные переменный page и count и создадим их внутри функции start В callback будем отправлять такую строку: Для кнопки вперед, это выглядит вот так:  Для кнопки назад, это выглядит, так:    На данном этапе видно, что я начал отдавать номер строки, на которую перехожу. Если нажму "Назад" со второй строки в обработчик уйдет 1. Это избавляет меня от необходимости использовать теперь разные обработчик для кнопки назад (back-page) и вперед (next-page) (их можно просто удалить) Теперь за пагинацию будет отвечать новый обработчик, который увидит в полученной строке вхождение 'pagination': После чего строка полученная в callback_data будет распаршена в json. И уже из JSON мы получим необходимые нам Count и Page. Пока пишу новый обработчик, сразу решу проблему 1. Сделаю три условия вывода кнопок. Вывод кнопки вперед, для первой страницы    Вывод "Вперед" и "Назад" для всех страниц между первой и последней Вывод кнопки "Назад" для последний страницы Вот, что получилось: А вот и  полученный код: Желательно в обработчик добавить исключение, что page > 0 и page <=count, а так же строку c bot.edit_message_text занести в блок try. На случай, если телеграмм зависнет и пользователь, сможет сделать двойной клик по одной кнопке. Но статья направлена на описания подхода реализации. Поэтому на этом я не буду останавливаться. Вернемся к "главной" функции start, которая принимает сообщения от пользователя. Я оставил count = 10 и page = 1; Пора это исправить! count - необходимо будет получать из БД, а в сообщение пользователю отдавать необходимые строки. Для этого создадим новый класс database, который будет отвечать за подключение к БД и вывод нужных строк из БД. Что бы выводить построчно записи из БД я буду использовать конструкцию SQL: OFFSET-FETCH.Она предназначена, как раз для разбиения результирующего набора на части (страницы) У меня завалялась таблица учебных организаций Москвы. На ней и покажу, как это выглядит: Делаем сортировку по id и отображаем 15 записей от 0 (т.е самой первой). Это будет наша первая страница, что бы показать 2 страницу нужно будет пропустить столько записей, сколько было на первой странице. Таким образом число после NEXT – является всегда статичным это будет переменная SkipSize. А число после OFFSET - то после которой надо забрать следующие (SkipSize) строк. Записать это можно, как (Номер страницы – 1)*SkipSize На псевдо-коде это выглядит вот так: Теперь перейдем к написанию самого класса. Он вышел вот таким: Теперь в main нам надо объявить наш новый класс: А вот так к нему можно обратиться:    Или вот так: Вот такой вывод получаем по итогу: И тут я понял, что общие количество записей так и не получил (тот самый count). В return к функции (listColledjeForPage) вывода записей добавлю еще вывод общего количества записей в запросе. Я еще раз убедился, что все работает и пошел дописывать main Сделаем вывод уже полученной строки из нашего нового класса. В "главной функции" переменную page оставляем равной 1, а count получаем из функции класса database, которая выполняет нужный нам запрос. Вот, что получили: Супер, мне нравится осталось только вывести этот текст в отформатированном виде и сделать такой же вывод в обработчики, который отвечает за перелистывание страниц вперед, назад. Осталось причесать наш вывод: Теперь такое же форматирование вставляем в обработчик при редактировании сообщения после нажатия кнопок "Впред"/"Назад". Вот что получилось по итогу:    А вот итоговый код: main.py database.py  Программист