Команды с аргументами
Сегодня Цыплёнок-помощник научится не просто реагировать на команду, а слушать, что ты к ней дописал — например, добавлять дело в список прямо командой /add погулять с собакой.
Аргументы команды — это всё, что пользователь написал после самой команды и пробела. Команда/add молоко— это командаaddс аргументоммолоко, и aiogram умеет отдать тебе этот «хвост» отдельно.
Зачем команде аргументы
Представь свой список дел на сегодня. Было бы здорово, если бы можно было просто написать боту /add сделать домашку по физике — и дело само добавилось в список. А /del 2 удалило бы вторую строчку. Не нужно никаких меню, никаких диалогов — одна команда, и сразу результат. Так работают многие реальные боты: /weather Москва, /find мемы про котов, /kick @игрок в боте игрового клана.
Секрет в том, что команда — это не только слово после слэша. Всё, что ты допишешь дальше, бот тоже получает. В прошлом уроке про хэндлеры сообщений Цыплёнок реагировал на текст целиком. Сейчас мы научимся аккуратно отделять саму команду от её «хвоста» — и этот хвост называется аргументами команды.
Вот к чему мы придём за урок — бот, который принимает дело прямо в команде:
from aiogram.filters import Command
from aiogram.types import Message
from aiogram.filters import CommandObject
@dp.message(Command("add"))
async def add_handler(message: Message, command: CommandObject):
if command.args is None:
await message.answer("Напиши, что добавить: например /add купить хлеб")
return
delo = command.args
await message.answer(f"Добавил в список: {delo} \ud83d\udc24")Результат: на сообщение /add купить хлеб бот ответит «Добавил в список: купить хлеб 🐤». А если написать просто /add без текста — вежливо подскажет: «Напиши, что добавить: например /add купить хлеб». Дальше разберём по косточкам, откуда бот берёт этот «хвост».
Как Telegram передаёт аргументы
Тут поможет аналогия с запиской официанту. Когда ты пишешь /add купить хлеб, Telegram не присылает боту что-то волшебное — он присылает обычную строку текста "/add купить хлеб". Всю строку целиком. А дальше aiogram, как внимательный официант, читает записку и делит её на две части:
- команда — первое слово после слэша, то есть
add; - аргументы — всё, что идёт после команды и первого пробела, то есть
купить хлеб.
Граница между ними — самый первый пробел после команды. Всё, что слева, — это команда. Всё, что справа, — один длинный аргумент-строка. Обрати внимание: aiogram не режет аргументы на отдельные слова сам. Он отдаёт тебе весь хвост одной строкой "купить хлеб", а уже ты решаешь, что с ним делать: оставить как есть или разбить на части.
Почему так, а не сразу списком слов? Потому что библиотека не может угадать, чего именно ты хочешь. Для команды /say привет всем тебе нужен весь хвост целиком — иначе фраза развалится. А для /calc 12 + 8 тебе, наоборот, нужны три отдельных куска. Одна и та же команда у разных ботов разбирается по-разному, поэтому aiogram честно отдаёт сырой текст и не лезет в твою логику. Это удобно: ты сам выбираешь, по чему резать — по пробелам, по запятым или вообще не резать.
Ещё одна деталь, которая часто удивляет новичков: лишние пробелы. Если пользователь напишет /add купить хлеб с тремя пробелами после команды, aiogram отрежет только одну границу и отдаст тебе аргументы уже без ведущих пробелов — то есть "купить хлеб". А вот пробелы внутри хвоста он трогать не станет: это твой текст, и решать, что с ним делать, тоже тебе. Запомни этот принцип — он избавит от половины вопросов «почему у меня в аргументах какой-то мусор».
Чтобы было совсем наглядно, посмотрим на чистом Python, как примерно происходит это деление. Возьмём метод строки split с ограничением на одно разбиение:
def razobrat(text):
parts = text.split(maxsplit=1)
command = parts[0]
args = parts[1] if len(parts) > 1 else None
return command, args
for msg in ["/add купить хлеб", "/add", "/weather Москва"]:
print(repr(msg), "->", razobrat(msg))Вывод:
'/add купить хлеб' -> ('/add', 'купить хлеб')
'/add' -> ('/add', None)
'/weather Москва' -> ('/weather', 'Москва')Видишь? split(maxsplit=1) делит строку максимум один раз — поэтому команда отрывается от хвоста, а сам хвост остаётся целым, даже если в нём несколько слов. И заметь: когда после команды ничего нет, второго куска просто не существует, и мы получаем None. Это важный случай, к нему мы ещё вернёмся. Aiogram внутри делает почти то же самое, только аккуратнее — а тебе отдаёт готовый результат.
Получаем аргументы через CommandObject
Теперь главное. Чтобы aiogram отдал тебе разобранную команду, нужно попросить у него специальный объект — CommandObject. Делается это волшебно просто: ты дописываешь его в параметры хэндлера, и aiogram сам его подставит.
from aiogram.filters import Command, CommandObject
from aiogram.types import Message
@dp.message(Command("add"))
async def add_handler(message: Message, command: CommandObject):
await message.answer(f"Аргументы команды: {command.args}")Результат: на /add купить хлеб бот ответит «Аргументы команды: купить хлеб». На просто /add ответит «Аргументы команды: None». Разберём, что здесь происходит.
Откуда берётся command
Смотри на параметры функции: async def add_handler(message: Message, command: CommandObject). Раньше у нас был только message. Теперь мы добавили второй параметр command с типом CommandObject. Aiogram видит этот тип в подсказке и сам создаёт объект и передаёт его — тебе не надо ничего вызывать руками. Это как сказать официанту: «и принеси мне ещё разобранную записку» — а он уже знает, что это значит.
Что внутри CommandObject
У объекта command есть несколько полезных полей. Главное для нас — command.args: это строка со всеми аргументами (тот самый «хвост») или None, если аргументов не было. Есть и другие поля, они тоже пригодятся:
| Поле | Что хранит | Пример для /add купить хлеб |
command.command | имя команды без слэша | "add" |
command.args | строка аргументов или None | "купить хлеб" |
command.prefix | префикс команды | "/" |
В 95% случаев тебе нужен только command.args. Запомни его — это твой главный инструмент в этом уроке.
Разбираем строку аргументов на части
Часто одного куска текста мало. Например, для команды /add 3 купить молоко ты, может, захочешь отдельно число 3 (приоритет дела) и отдельно текст купить молоко. Аргументы приходят одной строкой — значит, разбить их на части придётся тебе. И здесь снова работает обычный Python, который ты уже знаешь: метод строки split.
Пример 1. Один аргумент — берём как есть
Самый простой случай: команда /say, которая повторяет за тобой любой текст. Тут резать ничего не надо — весь хвост и есть то, что нужно повторить.
@dp.message(Command("say"))
async def say_handler(message: Message, command: CommandObject):
if command.args is None:
await message.answer("Что повторить? Напиши: /say привет всем")
return
await message.answer(command.args)Результат: на /say привет, клан! бот ответит «привет, клан!». Никакого разбора — берём command.args целиком.
Пример 2. Два аргумента — отделяем первое слово
Теперь команда /remind 18 сделать уроки: первое слово — во сколько напомнить, остальное — текст напоминания. Используем split(maxsplit=1), чтобы оторвать только первый кусок, а хвост оставить целым:
@dp.message(Command("remind"))
async def remind_handler(message: Message, command: CommandObject):
if command.args is None:
await message.answer("Формат: /remind 18 текст напоминания")
return
parts = command.args.split(maxsplit=1)
if len(parts) < 2:
await message.answer("Нужно и время, и текст: /remind 18 сделать уроки")
return
chas, tekst = parts[0], parts[1]
await message.answer(f"В {chas}:00 напомню: {tekst} \ud83d\udc24")Результат: на /remind 18 сделать уроки бот ответит «В 18:00 напомню: сделать уроки 🐤». Почему именно maxsplit=1? Потому что без него split разрезал бы строку по каждому пробелу, и «сделать уроки» превратилось бы в два отдельных слова. А нам нужно отделить только время, а текст сохранить как есть — даже если в нём пять слов.
Пример 3. Список значений — режем по запятой
А вот команда для опроса друзей: /poll пицца, суши, бургеры. Здесь логично разделять варианты по запятой, а не по пробелу — ведь вариант может состоять из нескольких слов. Берём split(",") и подчищаем пробелы у каждого куска:
@dp.message(Command("poll"))
async def poll_handler(message: Message, command: CommandObject):
if command.args is None:
await message.answer("Дай варианты через запятую: /poll пицца, суши, бургеры")
return
varianty = [v.strip() for v in command.args.split(",")]
spisok = "\n".join(f"{i + 1}. {v}" for i, v in enumerate(varianty))
await message.answer(f"Голосуем за:\n{spisok}")Результат: на /poll пицца, суши, бургеры бот ответит списком «1. пицца / 2. суши / 3. бургеры» (каждый пункт с новой строки). Метод .strip() у каждого куска убирает лишние пробелы — ведь после запятой пользователь почти всегда ставит пробел, и без strip вышло бы « суши» с пробелом в начале.
Маленький разбор приёмов с разбором строки — на чистом Python, чтобы закрепить:
args = "пицца, суши, бургеры"
varianty = [v.strip() for v in args.split(",")]
print(varianty)
print("Количество вариантов:", len(varianty))Вывод:
['пицца', 'суши', 'бургеры'] Количество вариантов: 3
Обрабатываем пустой ввод
А теперь — самое важное правило этого урока. Пользователь почти наверняка хоть раз напишет команду без аргументов: просто /add и ничего больше. Может, забыл дописать, может, не знает формата, может, нажал случайно. И если ты к этому не готов — бот сломается.
Помнишь, в начале мы видели, что при отсутствии аргументов command.args равно None? Вот тут это и стреляет. Если ты сразу попробуешь сделать command.args.split() на None, Python выдаст ошибку AttributeError: 'NoneType' object has no attribute 'split' — потому что у «ничего» нет метода split. Поэтому первым делом в каждом хэндлере с аргументами проверяй: а пришли ли они вообще?
@dp.message(Command("add"))
async def add_handler(message: Message, command: CommandObject):
if command.args is None:
await message.answer(
"После /add напиши, что добавить.\n"
"Например: /add купить корм коту"
)
return
await message.answer(f"Добавил: {command.args} \ud83d\udc24")Результат: на просто /add бот вежливо объяснит формат и покажет пример, а на /add купить корм коту — добавит дело. Обрати внимание на return после ответа об ошибке: он нужен, чтобы функция тут же завершилась и не пошла дальше выполнять основной код с пустыми аргументами. Без return бот сначала пожалуется, а потом всё равно попробует обработать None — и упадёт.
Заметь ещё одну приятную мелочь: текст подсказки мы собрали из двух строк, склеенных через \n — это перенос строки. В Telegram сообщение придёт в две строчки: сначала «После /add напиши, что добавить.», а под ней — пример. Такие маленькие подсказки прямо в ответе бота — признак заботы о пользователе: человек не гадает, что он сделал не так, а сразу видит правильный формат и пример. Хороший бот не ругается «ошибка», а мягко направляет — и Цыплёнок именно такой.
Хорошее правило для всех команд с аргументами выглядит так:
- сначала проверь
command.args is None— если пусто, подскажи формат и сделайreturn; - при необходимости разбей строку через
splitи проверь, что кусков хватает; - только потом выполняй основную работу команды.
Частые ошибки и подводные камни
На командах с аргументами новички спотыкаются предсказуемо. Покажу грабли заранее.
1. Забыл проверить на None
Самая частая беда. Пользователь шлёт /add без текста, ты сразу делаешь command.args.split() — и ловишь AttributeError, бот в этот момент «глохнет». Лечится одной строчкой проверки if command.args is None в самом начале.
2. Перепутал command.args и message.text
Можно соблазниться взять весь текст сообщения через message.text и резать его самому. Но тогда в аргументы попадёт и сама команда: message.text для /add хлеб — это "/add хлеб" целиком, со слэшем и словом add. А command.args — уже чистый "хлеб" без команды. Бери command.args, не изобретай велосипед.
3. Забыл добавить CommandObject в параметры
Если объявить хэндлер как async def add_handler(message: Message) без второго параметра, то command внутри взять будет неоткуда. Чтобы aiogram передал объект, его нужно дописать в параметры именно с типом CommandObject — по этому типу библиотека и понимает, что подставить.
4. Используешь split() без maxsplit, когда хвост — это фраза
Если для /remind 18 сделать уроки написать просто command.args.split(), ты получишь список ['18', 'сделать', 'уроки'] — три куска вместо двух. Текст напоминания развалится. Когда первое слово — параметр, а остальное — цельная фраза, бери split(maxsplit=1).
5. Не подчистил пробелы после разбора
После split(",") по строке "пицца, суши" ты получишь ['пицца', ' суши'] — у второго варианта в начале прилип пробел. В сравнениях и выводе это аукнется. Привычка вызывать .strip() у каждого куска экономит часы отладки.
Мини-практика: команда /calc своими руками
Теперь твоя очередь. Задание: научи Цыплёнка считать простые выражения вида /calc 12 + 8. Бот должен брать два числа и знак между ними, посчитать и ответить результатом.
Подсказки, чтобы не застрять:
- Зарегистрируй хэндлер на
Command("calc")и добавь параметрcommand: CommandObject. - Первым делом проверь
command.args is None— если пусто, подскажи формат/calc 12 + 8и сделайreturn. - Разбей строку:
parts = command.args.split(). Здесь обычныйsplitподойдёт — нам нужны ровно три куска: число, знак, число. - Проверь, что кусков именно три (
len(parts) == 3), иначе снова подскажи формат. - Преврати первый и третий кусок в числа через
int(...), посмотри на знакparts[1]и посчитай: для"+"сложи, для"-"вычти. - Ответь результатом через
await message.answer(...).
Вот примерный каркас (сначала попробуй сам, потом сверься):
@dp.message(Command("calc"))
async def calc_handler(message: Message, command: CommandObject):
if command.args is None:
await message.answer("Формат: /calc 12 + 8")
return
parts = command.args.split()
if len(parts) != 3:
await message.answer("Нужно: число знак число, например /calc 12 + 8")
return
a, znak, b = int(parts[0]), parts[1], int(parts[2])
if znak == "+":
await message.answer(f"{a} + {b} = {a + b}")
elif znak == "-":
await message.answer(f"{a} - {b} = {a - b}")
else:
await message.answer("Знаю только + и -")Результат: на /calc 12 + 8 бот ответит «12 + 8 = 20», а на /calc 30 - 7 — «30 - 7 = 23». Если справился — ты уже умеешь принимать структурированные данные прямо из команды. Подумай на будущее: что если пользователь напишет /calc два плюс три? int("два") вызовет ошибку. Защититься от такого мы научимся в уроках про обработку ошибок.
Итоги
Сегодня Цыплёнок-помощник стал заметно умнее: теперь он слушает не только саму команду, но и то, что ты к ней дописал. Закрепим главное:
- Аргументы команды — это весь текст после команды и первого пробела; Telegram присылает команду целой строкой, а aiogram отделяет хвост за тебя.
- Чтобы получить аргументы, добавь в хэндлер параметр
command: CommandObject— aiogram подставит объект сам. Нужное поле —command.args. - Разбивать строку на части — твоя задача: обычный
split()для слов,split(maxsplit=1)для «первое слово + фраза»,split(",")для списка через запятую (не забудь.strip()). - Всегда первым делом проверяй
command.args is Noneи делайreturn— иначе на пустой команде бот упадёт сAttributeError.
В следующем уроке мы перестанем заставлять пользователя помнить форматы команд и дадим ему кнопки — те самые удобные клавиатуры под полем ввода. Цыплёнок начнёт предлагать варианты сам, а пользоваться им станет приятно даже тому, кто впервые открыл бота. Не забудь сохранить свой bot.py — до встречи!