Как стать автором
Обновить

Telegram Боты на Aiogram 3.x: Инлайн кнопки и CallBack Дата

Уровень сложностиСредний
Время на прочтение14 мин
Количество просмотров26K

Приветствую всех! В этой статье мы продолжим исследовать возможности библиотеки Aiogram 3 и рассмотрим тему инлайн кнопок и CallBack данных. На данный момент мы уже:

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

В данной статье мы рассмотрим:

  • Что такое CallBack хендлеры;

  • Разновидности CallBack хендлеров (ссылки, веб-приложения, обычные CallBack данные);

  • Научимся создавать более сложные конструкции через магические фильтры в контексте CallBack.

  • Поработаем с библиотекой Faker

  • Напишем функцию по генерации случайного пользователя и коснемся темы форматирования сообщений

  • Я покажу вам как работает имитация действий в боте (будем имитировать набор текста ботом)

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

Что такое CallBack в Aiogram 3

CallBack в Aiogram 3 — это способ обработки взаимодействий пользователей с ботом, когда они нажимают на инлайн кнопки. Когда пользователь нажимает на такую кнопку, бот получает специальное сообщение — CallBack, с информацией о том, какую кнопку нажали. Эта информация называется CallBack дата.

Проще говоря, CallBack позволяет боту реагировать на нажатия инлайн кнопок, выполняя определённые действия в ответ на это. Это очень удобно для создания интерактивных и динамических ботов, которые могут менять своё поведение в зависимости от выбора пользователя.

CallBack кнопки могут работать, как в формате текстовых кнопок. Это когда текст на кнопке равняется той информации (CallBack дате), которую пользователь передает боту. Бывает полезным, например, если в вашем боте нет текстовых клавиатур (в рамках общего стиля например), а есть только InlineKeyboard. В таком случае можно, к примеру, в инлайн кнопку Home передать CallData home, но обычно в практике обычно CallBack дата кардинально отличается от надписи на кнопке.

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

Например, у вас бот службы поддержки (порядка 5-6 проектов разной сложности делал в формате «support bot»). Пользователь отправляет некое сообщение в бота, бот перехватывает это сообщение, параллельно захватив телеграмм айди пользователя (в прошлой статье я показывал как достать телеграмм айди из объекта message).

Далее он отправляет это сообщение менеджерам службы поддержки с инлайн кнопкой «Ответить». Менеджер нажимает на эту кнопку, и бот ждёт ответного сообщения (подробнее рассмотрим в теме FSM). И тут основная фишка в том, что кликнув на кнопку «Ответить», менеджер не просто запускает сценарий ответа на сообщение, но и сразу указывает боту, что этот ответ должен полететь конкретному пользователю (хотите статью о том, как написать простую службу поддержки?). В данном случае бот достает телеграмм айди пользователя из CallData (сегодня мы сделаем нечто похожее).

Другой пример (тоже из службы поддержки):

В ботах периодически бывают так называемые FAQ (разделы с часто задаваемыми вопросами). Был опыт, когда я в админ панели прописывал функционал, позволяющий захватить вопрос и ответ на него (FSM), далее это всё записывалось в базу данных под определённым айдишником.

После, когда пользователь заходит в раздел FAQ, бот отправляет запрос в базу данных и при помощи генератора инлайн кнопок (InlineKeyboardBuilder по типу такого же как для текстовых кнопок) происходит генерация клавиатуры с вопросом и ответом.

Далее боту достаточно всего одного хендлера для того чтобы массово обрабатывать сразу все ответы на любые вопросы. Ниже пример реализации:

@qst_router.callback_query(F.data.startswith('qst_'))
async def cmd_start(call: CallbackQuery, state: FSMContext):
    await state.clear()
    await call.answer()
    qst_id = int(call.data.replace('qst_', ''))
    async with ChatActionSender(bot=bot, chat_id=call.from_user.id, action="typing"):
        info = await pg_db.select_data('questions', {'where_conditions': [{'id': qst_id}]})
        await call.message.answer(info.get('answer'), reply_markup=main_kb(call.from_user.id))

Сильно не вдавайтесь сейчас в подробности кода, в будущих статьях я вас научу делать каждую реализацию. Тут просто смысл в демонстрации мощи CallBackData — буквально пару строк кода способны закрыть огромный блок FAQ (масштабирование, по сути, неограниченное).

Ну ладно, я могу очень долго говорить про CallBack, так как технология, по моему мнению, шикарная. Если сейчас пока не понятно что к чему – не переживайте. Дочитав эту статью, вы точно разберётесь с темой CallBack.

Приступаем к коду.

Код я буду писать в том же проекте, что и писал в прошлых статьях (если хотите такой же шаблон как у меня – читайте первую статью по теме aiogram – там я подробно расписал свой каркас бота стартового).

Давайте в нашем пакете keyboards создадим новый файл под Inline клавиатуры и дадим ему название inline_kbs.py:

В него сразу импортируем следующие модули:

from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo
from aiogram.utils.keyboard import InlineKeyboardBuilder

Импортируемые модули в файле inline_kbs.py

  1. from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo

    • InlineKeyboardMarkup: Этот класс используется для создания разметки инлайн клавиатуры. Разметка определяет, как будут располагаться кнопки на клавиатуре и как они будут взаимодействовать с пользователем.

    • InlineKeyboardButton: Этот класс представляет собой отдельную кнопку на инлайн клавиатуре. С помощью него мы можем задавать текст кнопки и действие, которое произойдет при нажатии на неё, например, отправку CallBack данных.

    • WebAppInfo: Этот класс используется для создания кнопок, которые открывают веб-приложения внутри Telegram (тема заслуживает отдельного большого обсуждения, так что далее просто покажу, что оно существует). С его помощью можно определить URL веб-приложения, которое будет открыто при нажатии на кнопку. Это полезно для интеграции внешних веб-сервисов и приложений с ботом.

  2. from aiogram.utils.keyboard import InlineKeyboardBuilder

    • InlineKeyboardBuilder: Это удобный инструмент для построения инлайн клавиатур. С его помощью можно легко и быстро создавать клавиатуры, добавляя кнопки и определяя их расположение. Этот класс помогает упрощать процесс создания сложных разметок клавиатур, делая код более читаемым и удобным для поддержки. Работает похожим образом с ReplyKeyboardBuilder, но со своими особенностями, о которых мы сегодня поговорим.

Инлайн клавиатура со ссылками

def ease_link_kb():
    inline_kb_list = [
        [InlineKeyboardButton(text="Мой хабр", url='https://habr.com/ru/users/yakvenalex/')],
        [InlineKeyboardButton(text="Мой Telegram", url='tg://resolve?domain=yakvenalexx')],
        [InlineKeyboardButton(text="Веб приложение", web_app=WebAppInfo(url="https://tg-promo-bot.ru/questions"))]
    ]
    return InlineKeyboardMarkup(inline_keyboard=inline_kb_list)

Объяснение кода:

  1. Определение функции ease_link_kb():

    • Функция ease_link_kb предназначена для создания и возвращения инлайн клавиатуры с кнопками, которые ведут к различным ссылкам различных типов.

  2. Создание списка кнопок inline_kb_list:

    • Внутри функции создаётся список inline_kb_list, который содержит вложенные списки с объектами InlineKeyboardButton. Каждая вложенная структура представляет собой отдельную строку кнопок на инлайн клавиатуре.

  3. Кнопка с ссылкой на мой аккаунт в Хабре:

    • [InlineKeyboardButton(text="Мой хабр", url='https://habr.com/ru/users/yakvenalex/')]:

      • Создаётся кнопка с текстом "Мой хабр", которая при нажатии перенаправляет пользователя на страницу Хабра.

  4. Кнопка с ссылкой на мой Telegram аккаунт:

    • [InlineKeyboardButton(text="Мой Telegram", url='tg://resolve?domain=yakvenalexx')]:

      • Создаётся кнопка с текстом "Мой Telegram", которая при нажатии открывает Telegram и переходит к моему аккаунту.

  5. Кнопка для открытия веб-приложения:

    • [InlineKeyboardButton(text="Веб приложение", web_app=WebAppInfo(url="https://tg-promo-bot.ru/questions"))]:

      • Создаётся кнопка с текстом "Веб приложение", которая при нажатии открывает веб-приложение по указанному URL.

  6. Возвращение инлайн клавиатуры:

    • return InlineKeyboardMarkup(inline_keyboard=inline_kb_list):

      • Функция возвращает объект InlineKeyboardMarkup, который содержит разметку инлайн клавиатуры с указанными кнопками.

Этот пример демонстрирует, как создавать инлайн клавиатуру с различными типами ссылок, включая обычные URL, ссылки на аккаунты в Telegram и веб-приложения. Давайте тестировать.

Для тестов я предлагаю создать новый message handler, который будет вызываться текстом «Давай инлайн!». К нему прикрутим нашу инлайн клавиатуру и поклацаем ее.

@start_router.message(F.text == 'Давай инлайн!')
async def get_inline_btn_link(message: Message):
    await message.answer('Вот тебе инлайн клавиатура со ссылками!', reply_markup=ease_link_kb())

Думаю, что к настоящему моменту вы уже понимаете, что тут мы использовали магический фильтр F.text, который будет срабатывать на отправку текста 'Давай инлайн!', а чтоб было ещё интересней давайте мы создадим текстовую кнопку с текстом 'Давай инлайн!', а саму кнопку привяжем к главной клавиатуре (это вы уже умеете делать, если нет, то читайте прошлую статью).

Так теперь выглядит моя главная клавиатура
Так теперь выглядит моя главная клавиатура
Так выглядит инлайн клавиатура со ссылками
Так выглядит инлайн клавиатура со ссылками

Давайте теперь изучать каждую кнопку-ссылку:

После клика на обычную кнопку-ссылку появляется окно которое спрашивает хотим ли мы перейти по ссылке.

Хотите?
Хотите?

После клика на ссылку с моим профилем Telegram происходит переход без окна в мой профиль. До недавнего времени только ссылки формата «tg://resolve?domain=yakvenalexx» позволяли переходить в профиль без окна, но, при написании этого текста обнаружил что и при формате ссылки «https://t.me/yakvenalexx» окно не появлялось.

Теперь к интересному моменту ВЕБ-ПРИЛОЖЕНИЕ:

После клика по инлайн кнопке с «квадратиком» телеграмм отправит нам такое сообщение:

И это чистая правда. После эту информацию можно использовать
И это чистая правда. После эту информацию можно использовать
Пример простого приложения
Пример простого приложения

А после откроется то ВЕБ-ПРИЛОЖЕНИЕ, которое я создал. С ПК версии, возможно не так наглядно как с телефона. Сейчас продемонстрирую.

Анимированные кнопки
Анимированные кнопки

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

Как я говорил выше – все это тема отдельной статьи. Может как-то обсудим.

Итак, к промежуточным выводам. Мы разобрали все варианты инлайн-клавиатур ссылок, а это значит, что можем переходить к более интересной части – CallBack Data!

Начнем с простой клавиатуры. Пускай в ней будет 2 инлайн-кнопки. Одна кнопка должна переводить пользователя на стартовый экран, а вторая запускает некое действие, например, выводит информацию о случайном пользователе (первое, что пришло в голову).

Для этого нам нужно подготовиться.

Сначала напишем функцию, которая будет генерировать информацию о случайном пользователе. Для этого воспользуемся интересной библиотекой Faker (возьмите её на заметку, часто пригождается).

Устанавливаем библиотеку:

pip install faker

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

Пишем код:

from faker import Faker

def get_random_person():
    # Создаем объект Faker с русской локализацией
    fake = Faker('ru_RU')

    # Генерируем случайные данные пользователя
    user = {
        'name': fake.name(),
        'address': fake.address(),
        'email': fake.email(),
        'phone_number': fake.phone_number(),
        'birth_date': fake.date_of_birth(),
        'company': fake.company(),
        'job': fake.job()
    }
    return user

Тут все достаточно просто. Импортируем модуль. При инициализации объекта fake укажем, что нас интересуют русские данные. Далее создадим простой словарь и захватим в него следующие данные: имя, адрес, email, телефон, дата рождения, компания и работа.

Далее мы напишем специальный хендлер, который при получении CallData «get_person» будет возвращать хорошо оформленное сообщение с информацией о пользователе (как раз немного углубим свои знания в теме форматирования текста в aiogram 3).

Сначала импортируем функцию для генерации случайного пользователя из пакета utils и CallbackQuery для удобства аннотаций.

from utils.utils import get_random_person
from aiogram.types import CallbackQuery

Теперь напишем сам хендлер. Я его пропишу полностью, а дальше дам объяснения.

@start_router.callback_query(F.data == 'get_person')
async def send_random_person(call: CallbackQuery):
    # await call.answer('Генерирую случайного пользователя')
    user = get_random_person()
    formatted_message = (
        f"👤 <b>Имя:</b> {user['name']}\n"
        f"🏠 <b>Адрес:</b> {user['address']}\n"
        f"📧 <b>Email:</b> {user['email']}\n"
        f"📞 <b>Телефон:</b> {user['phone_number']}\n"
        f"🎂 <b>Дата рождения:</b> {user['birth_date']}\n"
        f"🏢 <b>Компания:</b> {user['company']}\n"
        f"💼 <b>Должность:</b> {user['job']}\n"
    )
    await call.message.answer(formatted_message)

Тут у нас изменился декоратор. Теперь это не @start_router.message, а @start_router.callback_query. Также изменился магический фильтр. Теперь мы обрабатываем не F.text, а F.data.

Также мы указываем, что работать будем с объектом CallbackQuery, что позволит нам получать подсказки от IDE, в котором мы ведем разработку бота (у меня это Pycharm).

Обратите внимание, что я закомментировал одну строку. Это сделано намеренно, и далее вы поймете зачем.

После мы сгенерировали нашего пользователя через функцию, которую писали ранее, а затем приступили к простому форматированию текста. Если вы пользуетесь моей структурой бота, то у вас тоже при инициализации указывалось:

bot = Bot(token=config('TOKEN'), default=DefaultBotProperties(parse_mode=ParseMode.HTML))

Тем самым мы научили бота воспринимать HTML теги в сообщениях.

Форматирование у нас достаточно простое. Из тегов мы использовали только <b></b>, который делает текст жирным. Далее мы достаем из объекта user данные и при помощи \n делаем перенос на новую строку.

С помощью await call.message.answer(formatted_message) мы отправляем сообщение пользователю (не путать с await call.answer!).

Теперь напишем нашу инлайн-клавиатуру с call_data и приступим к тестированию.

def get_inline_kb():
    inline_kb_list = [
        [InlineKeyboardButton(text="Генерировать пользователя", callback_data='get_person')],
        [InlineKeyboardButton(text="На главную", callback_data='back_home')]
    ]
    return InlineKeyboardMarkup(inline_keyboard=inline_kb_list)

Вы можете обратить внимание, что клавиатура не особо отличается от клавиатуры со ссылками. Единственное отличие в том, что вместо url мы передаем callback_data, на которые и будет реагировать наш бот (обработчик под callback_data='get_person' мы уже написали).

Давайте эту клавиатуру привяжем вместо клавиатуры со ссылками. Отлично. Запускаем бота и смотрим, что у нас получилось:

Немного изменил текст в хендлере, раньше бот говори про ссылки
Немного изменил текст в хендлере, раньше бот говори про ссылки

Мы видим, что информация о пользователе сгенерирована, но кнопка не перестает мигать (знаю, что на скрине это плохо видно и понятно, но, поверьте, она мигает, а на смартфоне будут часики возле кнопки). Дело в том, что инлайн-клавиатуры в Telegram устроены таким образом, что они всегда ждут ответа от хендлера, что тот выполнен. Ждут порядка 30 секунд, после чего успокаиваются.

Давайте мы дадим ответ серверам Telegram, что все ок и по плану:

await call.answer('Генерирую случайного пользователя', show_alert=False)

show_alert=False идет по умолчанию, но я оставлю это в коде, чтобы вас не путать. Перезапускаем бота и смотрим:

Обратите внимание и на формат сообщения что мы получили.
Обратите внимание и на формат сообщения что мы получили.

Мы видим, что появилась белая надпись на черной плашке (висит секунды 2-3) и, при этом, у нас кнопка не мигает больше. Отлично.

Если вам нечего сообщать пользователю, можете просто указывать await call.answer(), тогда кнопка будет мгновенно тухнуть. Давайте сменим теперь флаг show_alert на True и посмотрим, что у нас получится:

Теперь у нас появляется окно alert, и для продолжения нужно будет нажать на «Ок». В некоторых случаях это бывает удобно.

Теперь, для закрепления материала, давайте пропишем хендлер для обработки callback_data='back_home'. Выполните самостоятельно, уверен, что у вас все получится.

Генератор инлайн-клавиатур - InlineKeyboardBuilder

Сейчас мы не просто разберем генератор инлайн-клавиатур (это, на самом деле, не трудно), но и попробуем написать свой первый более-менее серьезный обработчик. Смысл всей затеи будет таким:

  1. Мы соберем некий массив данных (питоновский словарь наподобие JSON). Пусть это будут вопросы с ответами. Словарь будет иметь ключ в виде целого числа и принадлежащий ему массив данных в виде вопроса и ответа.

  2. Мы напишем функцию, которая будет генерировать инлайн-клавиатуры такого вида:

    • text = «Вопрос»

    • callback_data = f-строка, содержащая приставку qst_ и ID вопроса

  3. Универсальный хендлер, который будет давать ответ на каждый вопрос.

Я знаю, что уже давал выше пример такой функции из «боевого» бота. Тут же мы пропишем её упрощенную версию.

Начнем с массива вопросов и ответов. Напишем таких вопросов 10 штук. Вот пример:

questions = {
    1: {'qst': 'Столица Италии?', 'answer': 'Рим'},
    2: {'qst': 'Сколько континентов на Земле?', 'answer': 'Семь'},
    3: {'qst': 'Самая длинная река в мире?', 'answer': 'Нил'},
    4: {'qst': 'Какой элемент обозначается символом "O"?', 'answer': 'Кислород'},
    5: {'qst': 'Как зовут главного героя книги "Гарри Поттер"?', 'answer': 'Гарри Поттер'},
    6: {'qst': 'Сколько цветов в радуге?', 'answer': 'Семь'},
    7: {'qst': 'Какая планета третья от Солнца?', 'answer': 'Земля'},
    8: {'qst': 'Кто написал "Войну и мир"?', 'answer': 'Лев Толстой'},
    9: {'qst': 'Что такое H2O?', 'answer': 'Вода'},
    10: {'qst': 'Какой океан самый большой?', 'answer': 'Тихий океан'},
}

Этот массив я прописал в файле create_bot.py

Теперь напишем функцию, которая будет принимать словарь вопросов и возвращать инлайн-клавиатуру:

from aiogram.utils.keyboard import InlineKeyboardBuilder


def create_qst_inline_kb(questions: dict) -> InlineKeyboardMarkup:
    builder = InlineKeyboardBuilder()
    # Добавляем кнопки вопросов
    for question_id, question_data in questions.items():
        builder.row(
            InlineKeyboardButton(
                text=question_data.get('qst'),
                callback_data=f'qst_{question_id}'
            )
        )
    # Добавляем кнопку "На главную"
    builder.row(
        InlineKeyboardButton(
            text='На главную',
            callback_data='back_home'
        )
    )
    # Настраиваем размер клавиатуры
    builder.adjust(1)
    return builder.as_markup()

Как вы видите функция генератор инлайн клавиатуры не сильно отличается от своего аналога в генерации текстовых клавиатур. Она принимает питоновский словарь с вопросами, а после пробегается по каждому генерируя нужные нам кнопки.

Обратите внимание, что в конец этой клавиатуры я добавил callback_data='back_home' (надеюсь, что вы написали обработчик). Давайте теперь привяжем клавиатуру эту, например, к новому хендлеру, который будет реагировать на команду «/faq» (для закрепления данных из прошлых моих статей можете закрепить эту команду в командном меню).

async def set_commands():
    commands = [BotCommand(command='start', description='Старт'),
                BotCommand(command='start_2', description='Старт 2'),
                BotCommand(command='faq', description='Частые вопросы')]
    await bot.set_my_commands(commands, BotCommandScopeDefault())

У меня получился такой результат. Пишем обработчик.

 @start_router.message(Command('faq'))
async def cmd_start_2(message: Message):
    await message.answer('Сообщение с инлайн клавиатурой с вопросами', reply_markup=create_qst_inline_kb(questions))

Вот простой обработчик. Вопросы я импортировал из файла create_bot:

from create_bot import questions

Запускаем и смотрим что у нас получилось:

Видим, что вопросы благополучно подгрузились. Теперь нам остается написать универсальный обработчик, который будет отвечать на выбранный вопрос. Давайте к каждому ответу прикрутим инлайн клавиатуру с вопросами.

@start_router.callback_query(F.data.startswith('qst_'))
async def cmd_start(call: CallbackQuery):
    await call.answer()
    qst_id = int(call.data.replace('qst_', ''))
    qst_data = questions[qst_id]
    msg_text = f'Ответ на вопрос {qst_data.get("qst")}\n\n' \
               f'<b>{qst_data.get("answer")}</b>\n\n' \
               f'Выбери другой вопрос:'
    async with ChatActionSender(bot=bot, chat_id=call.from_user.id, action="typing"):
        await asyncio.sleep(2)
        await call.message.answer(msg_text, reply_markup=create_qst_inline_kb(questions))

Не волнуйтесь, сейчас со всем разберемся. Для начала выполним импорты:

import asyncio
from aiogram.utils.chat_action import ChatActionSender
from create_bot import questions, bot

asyncio нам тут нужен для одной цели – установим асинхронную паузу в 2 секунды. Сама пауза нам нужна для того чтоб мы могли имитировать набор ботом текста. Для этого мы использовали конструкцию async with ChatActionSender(bot=bot, chat_id=call.from_user.id, action="typing") . Сильно заострять тут внимание не буду, но общий смысл в том, что бот, в течении 2 секунд, имитирует набор текста.

Давайте к разбору кода.

F.data.startswith('qst_')) – нововведения начались с этой части. Подробно разберем все подобные конструкции в отдельной статье про фильтры, тут же отметим, что указанная конструкция выполнила проверку на то начинается ли CallBack data с «qst_» (магические фильтры в деле).

Далее, строкой await call.answer() мы дали понять серверу телеграмм что все у нас хорошо и все по плану (кнопка потухнет сразу).

А вот на этой строке qst_id = int(call.data.replace('qst_', '')) немного заострим внимание, так как на этом трюке можно выстраивать невероятно сложные сценарии интерактивного взаимодействия пользователя и телеграмм бота.

Данным трюком мы забираем значение call_data и трансформируем строку в айдишник вопроса, тем самым, давая боту понять ответ на какой вопрос мы хотим получить. Технически call.data – это самая обыкновенная строка, а значит с ней можно делать все что с обычными строками. Понимаете к чему я?

То есть в одной call_data вы можете передать много информации, например айди пользователя, сумма оплаты и идентификатор товара, который пользователь покупает в вашем боте (это реальный пример одного из моих ботов).

Выглядит так: «order_112344_232_1245»

Далее специальный хендлер срабатывает на F.data.startswith(order_')), а далее из строки забирает нужные ему данные. Представляете какие это возможности открывает?

Ну и пока вы под впечатлением (надеюсь) мы продолжим разбирать наш код.

Далее мы достаем нужный нам вопрос, через его айди, а после просто формируем сообщение, которое бот отправит пользователю через f-строку, забирая нужные нам данные.

Ну а дальше вы уже знаете. Сделали имитацию набора текста на 2 секунды и затем отправили сообщение с клавиатурой. Смотрим на примере:

Имитация набора текста 2 секунды
Имитация набора текста 2 секунды

Как вы видите бот начал имитировать набор текста. А вот и ответ:

Выводы

Друзья, сегодня мы разобрали очень важную тему, которая откроет вам двери к созданию телеграмм-ботов любой сложности. Если какие-то моменты остались неясными, не стесняйтесь задавать вопросы в комментариях. Честно говоря, когда я только начинал, тоже не сразу все понял, но со временем ощутил все преимущества работы с CallBack Data.

В остальном все как обычно. Если этот текст был полезен и, благодаря нему вы узнали что-то новое, не забудьте об это сообщить в комментариях, своим лайком или подпиской. Написание подобного рода туториолов требует много времени и усилий, а без вашей поддержки все это потеряет смысл.

До скорого!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Узнали новое
92.98% Да53
7.02% Нет4
Проголосовали 57 пользователей. Воздержались 2 пользователя.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 6: ↑4 и ↓2+4
Комментарии9

Публикации

Истории

Работа

Data Scientist
62 вакансии

Ближайшие события