- 后端
- 即时通讯
- API设计
【免费下载链接】aiogram
aiogram is a modern and fully asynchronous framework for Telegram Bot API written in Python using asyncio
导读
在 aiogram 3.x 中,Telegram 键盘(内联键盘与回复键盘)不再需要手工拼装list[list[Button]]数据结构,而是可以通过aiogram.utils.keyboard提供的KeyboardBuilder系列工具动态生成、调整与合并键盘布局。本文以官方文档 docs/utils/keyboard.rst 为核心,结合 keyboard.py 源码 与 测试用例,完整讲解InlineKeyboardBuilder与ReplyKeyboardBuilder的用法、参数、底层实现与常见限制,读完即可在自己的机器人中按行调整按钮布局、拼接多组按钮并直接发送。
一、何时该用 Builder?静态 vs 动态
KeyboardBuilder的核心价值在于动态生成键盘 markup。官方文档特别提醒(docs/utils/keyboard.rst):
如果你的键盘是静态的、布局固定的,最好直接显式定义
InlineKeyboardMarkup,而不必使用 builder;但如果你需要根据运行时的数据动态决定按钮数量和布局,就可以放心使用 builder。
也就是说:
- 静态场景:直接构造
InlineKeyboardMarkup(inline_keyboard=[[...]])或ReplyKeyboardMarkup(keyboard=[[...]])更简洁、可读性更高; - 动态场景:例如根据数据库中的商品列表、用户选项数量不确定、需要按固定行宽自动换行时,builder 能大幅减少手写逻辑。
KeyboardBuilder是一个泛型抽象基类(源码见 keyboard.py),其下有两个具体实现:
| 类 | 对应的 Telegram 类型 | 单行最大按钮数(max_width) | 按钮总数上限(max_buttons) |
|---|---|---|---|
InlineKeyboardBuilder | InlineKeyboardMarkup | 8 | 100 |
ReplyKeyboardBuilder | ReplyKeyboardMarkup | 10 | 300 |
这两个上限定义在源码类属性中(keyboard.py 与 keyboard.py),与 Telegram Bot API 对键盘行数与按钮数的约束保持一致,超过会抛出ValueError。
二、快速上手:生成一个 10 按钮的内联键盘
官方文档给出的第一个示例是生成包含 10 个按钮的内联键盘:
from aiogram.utils.keyboard import InlineKeyboardBuilder builder = InlineKeyboardBuilder() for index in range(1, 11): builder.button(text=f"Set {index}", callback_data=f"set:{index}")要点:
builder.button(...)每次添加一个按钮,内部会构造InlineKeyboardButton并追加到 builder 中(源码见 keyboard.py);text是按钮显示的文本,callback_data是点击后回传给 Bot 的字符串(Telegram 限制为 1-64 字节);button()支持几乎所有InlineKeyboardButton字段,具体参数见下文第四节。
三、调整布局:adjust()按行宽重排
10 个按钮如果什么都不做,默认会被add()按 max_width=8 自动换行,得到 8 + 2 两行。若希望“第一行 3 个按钮,之后每行 2 个”,官方文档给出了adjust用法:
builder.adjust(3, 2)adjust(*sizes, repeat=False)的作用是把已添加的所有按钮按指定行宽重新排布(源码见 keyboard.py,这里修正为 keyboard.py):
- 依次使用传入的尺寸;当传入尺寸之和小于按钮总数时,最后一个尺寸会被复用来填充剩余按钮;
- 传
repeat=True时,所有尺寸会循环使用; - 不传任何尺寸时,等价于
adjust(max_width)(Inline 为 8,Reply 为 10); - 每个尺寸必须是
[min_width, max_width]范围内的整数,否则抛ValueError。
测试用例对此行为做了精确验证(tests/test_utils/test_keyboard.py):
| 按钮数 | sizes | repeat | 结果形状 |
|---|---|---|---|
| 10 | (3, 2, 1) | False | [3, 2, 1, 1, 1, 1, 1] |
| 12 | 空 | False | [10, 2](Reply,max_width=10) |
| 12 | (3, 2, 1) | True | [3, 2, 1, 3, 2, 1] |
源码中repeat_last生成器(keyboard.py)实现了“最后一个尺寸重复使用”的默认语义;repeat=True则借助itertools.cycle循环全部尺寸。
四、合并与拼接:attach()与from_markup()
动态构建时经常需要把“另一个 builder”或“已生成好的 markup”并入当前 builder。官方文档给出了两种方式:
4.1 拼接另一个 builder
another_builder = InlineKeyboardBuilder(...) # 另一个含按钮的 builder builder.attach(another_builder)attach()源码实现(keyboard.py)会校验:
- 参数必须是
KeyboardBuilder实例,否则抛ValueError; - 必须与当前 builder 的按钮类型一致——
InlineKeyboardBuilder只能 attachInlineKeyboardBuilder,ReplyKeyboardBuilder只能 attachReplyKeyboardBuilder。官方文档以 warning 明确提示“不能把回复键盘 builder 附加到内联键盘 builder,反之亦然”,源码中的_button_type类型检查(keyboard.py)保证了这一点,测试用例 test_attach_invalid_button_type 也覆盖了混用会抛ValueError的场景。
4.2 拼接已有 markup
markup = InlineKeyboardMarkup(inline_keyboard=[...]) # 一些已生成好的 markup builder.attach(InlineKeyboardBuilder.from_markup(markup))from_markup是一个类方法,接收InlineKeyboardMarkup(或ReplyKeyboardMarkup)并把它内部的行结构提取出来作为初始数据(源码见 keyboard.py 与 keyboard.py):
@classmethod def from_markup(cls, markup): return cls(markup=markup.inline_keyboard) # 或 markup.keyboard这非常实用:当你手上有旧代码生成的静态 markup,又想继续用 builder 追加按钮时,无需手工解析二维数组。
五、导出并发送:as_markup()
最终把 builder 转成可在消息中使用的 markup:
await message.answer("Some text here", reply_markup=builder.as_markup())as_markup()内部调用export()取出深拷贝的按钮二维数组,并构造对应的 markup 对象(keyboard.py):
InlineKeyboardBuilder.as_markup()返回InlineKeyboardMarkup;ReplyKeyboardBuilder.as_markup(resize_keyboard=True, ...)返回ReplyKeyboardMarkup,且可以透传is_persistent、resize_keyboard、one_time_keyboard、input_field_placeholder、selective、force_reply等参数(这些字段定义见 reply_keyboard_markup.py)。
需要注意:as_markup()不会清空 builder 内部状态,多次调用会得到内容相同的 markup;而export()每次返回的是深拷贝(deepcopy,见 keyboard.py),测试 test_export 验证了修改导出结果不会影响 builder 内部数据。
六、ReplyKeyboardBuilder:同样的接口
官方文档指出“回复键盘 builder 拥有相同的接口”。示例见仓库中的问答场景 examples/quiz_scene.py:
markup = ReplyKeyboardBuilder() markup.add(*[KeyboardButton(text=answer.text) for answer in quiz.answers]) if step > 0: markup.button(text="🔙 Back") markup.button(text="🚫 Exit") await message.answer( text=QUESTIONS[step].text, reply_markup=markup.adjust(2).as_markup(resize_keyboard=True), )这里混合使用了三种操作:
add(*buttons):一次性追加多个已构造好的KeyboardButton(add会尽量把按钮填入最后一行剩余空位,再按 max_width 换新行,见 keyboard.py);button(text=...):便捷地构造并添加单个按钮;adjust(2)+as_markup(resize_keyboard=True):两列排布并导出,同时让 Telegram 客户端按需缩放键盘高度。
ReplyKeyboardBuilder.button()的专属参数(源码 keyboard.py)对应 KeyboardButton 字段:
| 参数 | 类型 | 说明 |
|---|---|---|
text | str(必填) | 按钮文本;未使用其他特殊字段时,点击会直接把文本作为消息发送 |
icon_custom_emoji_id | str \| None | 按钮文本前显示的自定义 emoji ID |
style | str \| None | 按钮样式:danger(红)/success(绿)/primary(蓝) |
request_users | KeyboardButtonRequestUsers \| None | 点击后弹出用户选择器,仅私聊 |
request_chat | KeyboardButtonRequestChat \| None | 点击后弹出聊天选择器,仅私聊 |
request_contact | bool \| None | 请求用户电话号码,仅私聊 |
request_location | bool \| None | 请求用户当前位置,仅私聊 |
request_poll | KeyboardButtonPollType \| None | 请求用户创建并发送投票,仅私聊 |
web_app | WebAppInfo \| None | 点击后启动 Web App,仅私聊 |
七、InlineKeyboardBuilder.button() 参数全解
官方文档通过 autoclass 声明的button()方法签名(docs/utils/keyboard.rst)在源码中已扩展为更完整的参数列表(keyboard.py),对应 InlineKeyboardButton 的所有字段:
| 参数 | 类型 | 说明 |
|---|---|---|
text | str(必填) | 按钮标签文本 |
icon_custom_emoji_id | str \| None | 文本前的自定义 emoji |
style | str \| None | 按钮颜色样式(同上) |
url | str \| None | 打开的外部 HTTP(S) 或tg://链接 |
callback_data | str \| CallbackData \| None | 回传数据,1-64 字节;也接受CallbackData实例 |
web_app | WebAppInfo \| None | 启动 Web App,仅私聊 |
login_url | LoginUrl \| None | Telegram 登录小部件式的授权链接 |
switch_inline_query | str \| None | 切换到选中的聊天并插入内联查询 |
switch_inline_query_current_chat | str \| None | 在当前聊天输入框插入内联查询 |
switch_inline_query_chosen_chat | SwitchInlineQueryChosenChat \| None | 按类型选择聊天后插入内联查询 |
copy_text | CopyTextButton \| None | 点击复制指定文本到剪贴板 |
callback_game | CallbackGame \| None | 启动 HTML5 游戏 |
pay | bool \| None | 设为True显示支付按钮(⭐与XTR会被替换为 Star 图标) |
其中url、callback_data、web_app、login_url、switch_inline_query*、copy_text、callback_game、pay这些字段互斥——Telegram 要求一个按钮除text、icon_custom_emoji_id、style外只能指定一种行为类型(见 inline_keyboard_button.py 的类文档)。
7.1 与 CallbackData 工厂的集成
callback_data参数除了接收原始字符串,还可以直接传入CallbackData实例。底层_button()方法会检测到CallbackData类型并自动调用其pack()方法生成回调字符串(keyboard.py):
def _button(self, **kwargs): if isinstance(callback_data := kwargs.get("callback_data"), CallbackData): kwargs["callback_data"] = callback_data.pack() button = self._button_type(**kwargs) return self.add(button)CallbackData的工厂机制定义在 callback_data.py:子类需声明prefix,字段会按声明顺序以:分隔拼装,并强制校验最终长度不超过 64 字节。官方文档 callback_data.rst 给出了完整配合示例:
class AdminAction(CallbackData, prefix="adm"): action: Action # 字符串枚举 chat_id: int user_id: int # 在处理器内部 builder = InlineKeyboardBuilder() for action in Action: builder.button( text=action.value.title(), callback_data=AdminAction(action=action, chat_id=chat_id, user_id=user_id), ) await bot.send_message(admins_chat, f"...", reply_markup=builder.as_markup())之后用AdminAction.filter(F.action == Action.ban)过滤器即可精准命中对应回调(测试 test_button 验证了callback_data=MyCallback(value="test")会被打包为字符串"test:test"存入按钮)。
八、其他常用方法:buttons、copy、export、row
buttons属性
以生成器形式展平所有按钮(keyboard.py),常用于遍历或统计按钮总数:
for btn in builder.buttons: print(btn.text)copy()
返回一个与当前 builder 内容完全相同的新实例(深拷贝),用于在不污染原 builder 的前提下继续调整(源码 keyboard.py)。测试 test_copy 验证了两个副本相互独立:对副本执行add/row不会影响原 builder。
export()
返回深拷贝的list[list[Button]],可手工交给InlineKeyboardMarkup/ReplyKeyboardMarkup构造(keyboard.py):
markup = InlineKeyboardMarkup(inline_keyboard=builder.export())row(*buttons, width=None)
显式添加一整行按钮;行宽默认取max_width,也可用width参数指定。当传入按钮超过行宽时会自动拆成多行(keyboard.py)。与adjust的区别在于:row是“边构建边定行”,adjust是“先全部添加、最后统一重排”。
九、底层实现原理与数据校验
KeyboardBuilder在构造时即对 markup 结构做严格校验(keyboard.py):
button_type必须是InlineKeyboardButton或KeyboardButton的子类,否则抛ValueError;- 初始 markup 必须是
list[list[Button]],每行长度不得超过max_width,按钮总数不得超过max_buttons,否则抛ValueError(对应_validate_row/_validate_markup,见 keyboard.py); adjust/row的行宽必须落在[min_width, max_width]区间内且为整数(_validate_size,见 keyboard.py)。
这些校验在 tests/test_utils/test_keyboard.py 中都有对应用例:非法的按钮类型、超长的行、超量的按钮总数、非整数行宽等都会抛出ValueError,保证最终生成的 markup 一定符合 Telegram API 的约束。
十、注意事项小结
- 静态优先:布局固定的键盘直接写
InlineKeyboardMarkup/ReplyKeyboardMarkup,动态内容再使用 builder; - 类型不可混搭:
attach()只允许同类型 builder 互相拼接,内联与回复键盘互不兼容(keyboard.py); - 行宽与总数上限:Inline 每行最多 8 个、总数最多 100;Reply 每行最多 10 个、总数最多 300,违反会抛
ValueError; adjust的尾部复用规则:默认最后一个尺寸重复使用,repeat=True才循环全部尺寸;export()返回深拷贝:可以放心修改导出结果而不影响 builder 内部状态。
结语
InlineKeyboardBuilder与ReplyKeyboardBuilder是 aiogram 3.x 处理动态键盘的标准工具:button()负责便捷地造按钮,add/row/adjust负责布局,attach/from_markup负责合并,as_markup()负责交付给消息发送接口。配合CallbackData工厂还可以获得类型安全的回调数据链路。更多 API 细节可查阅 keyboard.py 源码、官方文档 docs/utils/keyboard.rst 以及完整的 test_keyboard.py 测试套件。
- 后端
- 即时通讯
- API设计
【免费下载链接】aiogram
aiogram is a modern and fully asynchronous framework for Telegram Bot API written in Python using asyncio
相关推荐
探秘Apache MINA:构建高效可扩展网络应用的利器
探秘Apache MINA:构建高效可扩展网络应用的利器 在当今瞬息万变的互联网世界中,寻找一款既能保证高性能又能支持高可扩展性的网络应用框架成为开发者们的一大
后端即时通讯API设计aiogram 内联键盘 InlineKeyboardMarkup 完全指南:数据结构、按钮类型与动态构建器实战
aiogram 内联键盘 InlineKeyboardMarkup 完全指南:数据结构、按钮类型与动态构建器实战 本文以 aiogram 3.x 仓库中 Inl
后端即时通讯API设计Flowbite KBD(Keyboard)键盘按键组件实战指南:用 Tailwind CSS 快速构建键位提示 UI
Flowbite KBD(Keyboard)键盘按键组件实战指南:用 Tailwind CSS 快速构建键位提示 UI KBD(Keyboard)是 Flowb
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考