☰
NoneBot2 插件编写与加载全指南:插件结构、nb-cli 创建与七种加载方式详解
2026/9/27 10:12:28 网站建设 项目流程
  • 后端
  • 即时通讯

【免费下载链接】nonebot2

跨平台 Python 异步聊天机器人框架 / Asynchronous multi-platform chatbot framework written in Python

项目地址:https://gitcode.com/gh_mirrors/no/nonebot2
点击查看免费下载

本篇教程聚焦 NoneBot2 插件开发的起点——插件是什么、如何创建、如何加载。你将学会区分单文件插件与包插件、通过nb plugin create或手动方式创建插件,并掌握load_plugin、load_plugins、load_all_plugins、load_from_json、load_from_toml以及内置插件加载等全部加载接口的适用场景与底层实现。读完即可在真实项目中独立组织插件目录并正确接入入口文件bot.py。

插件结构:插件本质上是一个特殊的 Python 模块

在 NoneBot 中,插件即是 Python 的一个模块(module)。NoneBot 会在导入时对这些模块做一些特殊处理,使其成为一个真正的插件。插件之间应尽量减少耦合,可以进行有限制的相互调用,NoneBot 能够正确解析插件间的依赖关系。

从源码实现来看,这一"特殊处理"发生在 nonebot/plugin/manager.py 的PluginLoader.exec_module中:模块执行前,框架会先通过_new_plugin创建Plugin对象并挂载为模块的__plugin__属性,随后进入插件上下文(_current_plugin)再真正执行模块代码;若执行抛出异常,则调用_revert_plugin回滚注册,避免留下脏数据。因此,一个普通.py文件在导入后,只要其__plugin__属性存在且为Plugin实例,就会被识别为插件——这就是"插件即模块"的底层依据。

单文件插件

一个普通的.py文件即可以作为一个插件。例如创建一个foo.py文件:

📂 plugins └── 📜 foo.py

这个时候模块foo已经可以被称为一个插件了,尽管它还什么都没做。

包插件

一个包含__init__.py的文件夹即是一个常规 Python 包(package)。例如创建一个foo文件夹:

📂 plugins └── 📂 foo └── 📜 __init__.py

这个时候包foo同样是一个合法的插件,插件内容可以在__init__.py文件中编写。

仓库佐证:pkgutil.iter_modules扫描插件目录时,单文件插件与包含__init__.py的包插件都会被纳入搜索;在 nonebot/utils.py 的path_to_module_name中,若路径文件名是__init__,则取父目录作为模块名,这正是包插件能被正确转换为模块名的原因。

创建插件:nb-cli 交互式创建与手动创建

创建插件可以通过nb-cli命令从完整模板创建,也可以手动新建空白文件。通过以下命令创建一个名为weather的插件:

$ nb plugin create [?] 插件名称: weather [?] 使用嵌套插件? (y/N) N [?] 请输入插件存储位置: awesome_bot/plugins

nb-cli会在awesome_bot/plugins目录下创建一个名为weather的文件夹,其中包含的文件将在后续章节中用到:

📦 awesome-bot ├── 📂 .venv ├── 📂 awesome_bot │ └── 📂 plugins │ └── 📂 weather │ ├── 📜 __init__.py │ └── 📜 config.py ├── 📜 .env.prod ├── 📜 pyproject.toml └── 📜 README.md

生成出的config.py是插件专属配置的声明文件,用于配合get_plugin_config从全局配置中提取插件所需配置项(见 nonebot/plugin/init.py),后续编写插件业务逻辑时可以在__init__.py中导入使用。

基于 bootstrap 模板项目的修改

如果在之前的快速上手章节中已经使用bootstrap模板创建了项目,那么需要做出如下修改:

  1. 在项目目录中创建一个两层文件夹awesome_bot/plugins

    📦 awesome-bot ├── 📂 .venv ├── 📂 awesome_bot │ └── 📂 plugins ├── 📜 .env.prod ├── 📜 pyproject.toml └── 📜 README.md
  2. 修改pyproject.toml文件中的nonebot配置项,在plugin_dirs中添加awesome_bot/plugins

    [tool.nonebot] plugin_dirs = ["awesome_bot/plugins"]

    plugin_dirs数组正是后续load_from_toml读取[tool.nonebot]Table 时所依赖的字段,它声明了机器人要扫描的本地插件目录。

基于手动创建项目的修改

如果在之前的创建项目章节中手动创建了相关文件,那么需要做出如下修改:

  1. 在项目目录中创建一个两层文件夹awesome_bot/plugins

    📦 awesome-bot ├── 📂 awesome_bot │ └── 📂 plugins └── 📜 bot.py
  2. 修改bot.py文件中的加载插件部分,取消注释或者添加如下代码

    # 在这里加载插件 nonebot.load_builtin_plugins("echo") # 内置插件 nonebot.load_plugins("awesome_bot/plugins") # 本地插件

加载插件:时机、约束与七种加载接口

加载时机与必须遵守的约束

加载插件是在机器人入口文件中完成的,需要在框架初始化之后、运行之前进行,即位于nonebot.init()与nonebot.run()之间:

import nonebot nonebot.init() # 加载插件 nonebot.run()

:::danger[警告] 请勿在插件被加载前import插件模块,这会导致 NoneBot 无法将其转换为插件而出现意料之外的情况。 :::

这条警告的根源在PluginLoader.create_module与PluginLoader.exec_module的实现逻辑(nonebot/plugin/manager.py):如果模块早已被普通import放入sys.modules,则加载器会直接复用已存在的模块而跳过"创建插件对象"的步骤,导致该模块没有__plugin__属性,最终在load_plugin中抛出Module ... is not loaded as a plugin!错误。

此外还需注意:加载的插件模块名称(插件文件名或文件夹名)不能相同,且每一个插件只能被加载一次,重复加载将会导致异常。这对应 nonebot/plugin/manager.py 中_prepare_plugins的去重校验——无论是独立插件名还是目录扫描出的插件,只要插件标识符已存在,就会抛出Plugin already exists: xxx! Check your plugin name。

如果你使用nb-cli管理插件,那么可以跳过本节,nb-cli会自动处理加载;如果使用自定义的入口文件bot.py,则需要手动加载。

加载插件的方式有多种,但底层的加载逻辑是一致的(所有接口最终都汇聚到PluginManager与importlib),以下是为加载插件提供的几种方式。

load_plugin:加载单个插件

通过点分割模块名称或使用pathlib的Path对象来加载插件,通常用于加载第三方插件或者项目插件。例如:

from pathlib import Path nonebot.load_plugin("path.to.your.plugin") # 加载第三方插件 nonebot.load_plugin(Path("./path/to/your/plugin.py")) # 加载项目插件

:::warning[注意] 本地插件的路径应该为相对机器人**入口文件(通常为 bot.py)**可导入的,例如在项目plugins目录下。 :::

源码实现上(nonebot/plugin/load.py),传入Path时先经path_to_module_name转换为点分模块名,再交由PluginManager加载。仓库测试 tests/test_plugin/test_load.py 同时验证了"模块名加载"与"路径加载"两条路径,并确认加载不存在的插件会返回None。

load_plugins:加载目录下所有插件

加载传入插件目录中的所有插件,通常用于加载一系列本地编写的项目插件。例如:

nonebot.load_plugins("src/plugins", "path/to/your/plugins")

:::warning[注意] 插件目录应该为相对机器人**入口文件(通常为 bot.py)**可导入的,例如在项目plugins目录下。 :::

底层通过PluginManager(search_path=plugin_dir)扫描目录,注意实现细节(nonebot/plugin/manager.py):以_开头的文件或文件夹不会被导入。这一点在测试中得到印证:assert "plugin._hidden" not in sys.modules(见 tests/test_plugin/test_load.py)——仓库的tests/plugins/_hidden.py正是用来验证下划线前缀插件会被忽略。

load_all_plugins:混合加载

这种加载方式是以上两种方式的混合,加载所有传入的插件模块名称,以及所有给定目录下的插件。例如:

nonebot.load_all_plugins(["path.to.your.plugin"], ["path/to/your/plugins"])

签名对应源码load_all_plugins(module_path: Iterable[str], plugin_dir: Iterable[str])(nonebot/plugin/load.py),它把独立插件与目录插件统一交给一个PluginManager处理。

load_from_json:从 JSON 文件加载

通过 JSON 文件加载插件,是load_all_plugins的 JSON 变种,通过读取 JSON 文件中的plugins字段和plugin_dirs字段进行加载。例如:

{ "plugins": ["path.to.your.plugin"], "plugin_dirs": ["path/to/your/plugins"] }
nonebot.load_from_json("plugin_config.json", encoding="utf-8")

源码会校验 JSON 顶层必须是 dict,且plugins、plugin_dirs均为列表(nonebot/plugin/load.py),否则抛出TypeError或AssertionError。仓库中的测试样例 tests/plugins.json 与校验测试见 tests/test_plugin/test_load.py(非法 JSON 会触发TypeError)。

:::tip[提示] 如果 JSON 配置文件中的字段无法满足你的需求,可以使用load_all_plugins方法自行读取配置来加载插件。 :::

load_from_toml:从 TOML 文件加载

通过 TOML 文件加载插件,是load_all_plugins的 TOML 变种,通过读取 TOML 文件中的[tool.nonebot]Table 中的plugin_dirsArray 与[tool.nonebot.plugins]Table 中的多个 Array 进行加载。例如:

[tool.nonebot] plugin_dirs = ["path/to/your/plugins"] [tool.nonebot.plugins] "@local" = ["path.to.your.plugin"] # 本地插件等非插件商店来源的插件 "nonebot-plugin-someplugin" = ["nonebot_plugin_someplugin"] # 插件商店来源的插件
nonebot.load_from_toml("plugin_config.toml", encoding="utf-8")

源码解析逻辑(nonebot/plugin/load.py)值得留意:

  • 若 TOML 中没有[tool.nonebot]Table,直接抛出ValueError: Cannot find '[tool.nonebot]' in given toml file!(测试见 tests/test_plugin/test_load.py);
  • [tool.nonebot]下既支持新版plugins作为 Table([tool.nonebot.plugins]下的多个 Array,按来源分组),也兼容旧版plugins作为 Array 的格式——旧格式会输出警告Legacy project format found! Upgrade withnb upgrade-format.;
  • 仓库测试样例 tests/plugins.toml(新版分组格式)与 tests/plugins.legacy.toml(旧版数组格式)对两者均有覆盖。

:::tip[提示] 如果 TOML 配置文件中的字段无法满足你的需求,可以使用load_all_plugins方法自行读取配置来加载插件。 :::

load_builtin_plugin:加载单个内置插件

加载一个内置插件,传入的插件名必须为 NoneBot 内置插件。该方法是load_plugin的封装。例如:

nonebot.load_builtin_plugin("echo")

源码实现(nonebot/plugin/load.py)等价于load_plugin(f"nonebot.plugins.{name}"),即把内置插件当作nonebot.plugins包下的模块加载。仓库内置插件目录 nonebot/plugins/echo.py 定义了/echo命令——它通过on_command("echo", to_me())注册响应器并回复消息内容。

load_builtin_plugins:加载多个内置插件

加载传入插件列表中的所有内置插件。例如:

nonebot.load_builtin_plugins("echo", "single_session")

源码实现(nonebot/plugin/load.py)等价于load_all_plugins([f"nonebot.plugins.{p}" for p in plugins], [])。第二个内置插件 nonebot/plugins/single_session.py 是"唯一会话"插件——加载后自动生效,通过@event_preprocessor限制同一会话内同时只能运行一个响应器。

其他加载方式

以上是面向入口文件的全部加载接口。除此之外,插件加载机制还覆盖两个进阶场景,可参考官方文档深入了解:

  • 跨插件访问:通过require(name)声明依赖并获取其他插件模块(实现见 nonebot/plugin/load.py),详见跨插件访问;
  • 嵌套插件:子插件以父插件标识符:子插件名的形式注册(测试nested:nested_subplugin见 tests/test_plugin/test_load.py),详见嵌套插件。

加载流程小结与自检清单

所有加载接口最终都经由PluginManager(nonebot/plugin/manager.py)完成:先_prepare_plugins搜索并缓存可用插件(含重名校验),再由load_plugin触发importlib导入,导入过程被注册在sys.meta_path首位的PluginFinder拦截,改用PluginLoader执行模块并注入__plugin__属性,最终插件进入全局注册表_plugins(nonebot/plugin/init.py),可通过get_loaded_plugins()/get_plugin()查询。

完成本教程后,建议按以下清单自查:

  • 插件目录(如awesome_bot/plugins)已创建,且相对入口文件可导入;
  • 插件是普通.py文件或含__init__.py的包,且插件名不与已加载插件重名;
  • 入口文件中nonebot.init()之后、nonebot.run()之前调用加载接口;
  • 未在任何插件加载前手动import插件模块;
  • 目录内以_开头的文件/文件夹不会被当作插件加载(这是特性,不是 bug)。
  • 后端
  • 即时通讯

【免费下载链接】nonebot2

跨平台 Python 异步聊天机器人框架 / Asynchronous multi-platform chatbot framework written in Python

项目地址:https://gitcode.com/gh_mirrors/no/nonebot2
点击查看免费下载

相关推荐

上一篇:ng-zorro-antd List 组件完全指南:从基础列表到栅格、加载更多与虚拟滚动
下一篇:3步搞定抖音无水印视频下载:完整指南让你永久保存高清原创内容

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询