TeAnli Blog

Back

发现#

我再给学校ACM实验室新生群写一个机器人, 几乎是从零开始写. 在这期间我加了这个机器人框架的群聊, 这个框架刚诞生没多久, 但是适配了 napcat 这个客户端, 启动非常便捷, 不用自己配置客户端, 所以我就开始了自己的开发之旅

我设置了两个命令, 添加订阅比赛, 移除订阅比赛, 这两个命令我都想只让群聊管理员使用, 但是经过在群里的询问, 我发现框架没有实现这个过滤器, 只实现了少部分的过滤器, 尽管我可以直接在命令处理的函数中判断消息来源是不是管理员, 但是这样还是不太方便, 每次都重复判断, 感觉代码很shit, 所以我就萌生了自己写这个过滤器的想法.

我刚学python就开始做机器人练手, 所以部分语法不是很清晰, 当时群里有个大佬给我发了他自己实现的代码, 用于检测bot是不是管理员的过滤器,我当时看到之后十分感谢这位大佬, 于是我拿去自己测试了一下

问题出现#

[17:40:48.978] ERROR 执行函数random_god_image时发生错误: Plugin.random_god_image() missing required positional argument: 'event'
bash

我人直接啥了, 这是啥玩意, 意思大概是没找到event这个参数 但是我明明写了啊!

import random

@require_sender_admin
@command_registry.command("/随机图片"description='')
async def random_image(self, event: BaseMessageEvent):
  random_id = random.randint(1, 5)
  await self.api.send_group_image(event.group_id,f'plugins/scpc/assets/image{random_id}.png')
python
def require_sender_admin():
    """
    用于群聊命令的权限过滤装饰器:仅允许群管理员/群主使用被装饰的命令。
    """
    def decorator(func: Callable):
        @wraps(func)
        async def wrapper(self: NcatBotPlugin, event: BaseMessageEvent, *args, **kwargs):
            group_id = getattr(event, "group_id", None)
            user_id = getattr(event, "user_id", None)
            if group_id is None or user_id is None:
                return await func(self, event, *args, **kwargs)
            try:
                member_info = await self.api.get_group_member_info(
                    group_id=group_id,
                    user_id=user_id,
                )
                if member_info.role == "owner" or member_info.role == "admin":
                    return await func(self, event, *args, **kwargs)
                return await event.reply("您不是群管理员或群主,无法执行此命令。")
            except Exception as e:
                _logger.warning(f"Failed to get sender's group role: {e}")
                await event.reply("无法获取您的群成员信息,暂时无法执行该命令。")
                return
        return wrapper
    return decorator
python

为什么会这样??

后来我了解到, 原来是装饰器优先级的问题 装饰器本质上就是在运行函数前后先运行个别的函数

所以说我的代码中 command_registry.command 命令依赖注入 BaseMessageEvent 后异步函数才有的 event 参数 而先用require_sender_admin装饰在函数上则会报错, 因为没检测到 依赖注入的 event 参数

原来如此, 那就调整一下顺序吧

import random
@command_registry.command("/随机图片"description='')
@require_sender_admin
async def random_image(self, event: BaseMessageEvent):
  random_id = random.randint(1, 5)
  await self.api.send_group_image(event.group_id,f'plugins/scpc/assets/image{random_id}.png')
python

写几个简单的装饰器

日志装饰器

from functools import wraps
from typing import Callable

def logit(func: Callable):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logit
def addition_func(x):
    return x + x

print(addition_func(4))
python

在调用函数 addition_func(4) 之前就会输出 func.__name__ + " was called"

带参数的装饰器

from functools import wraps
# 默认参数为 out.log 这个函数可以传递参数, 作为装饰器的名称标注
def logit(file='out.log'):
    # 装饰器, 在logit函数中仅充当装饰器对象作用, 可以理解为装饰器实际的执行函数
    def decorator(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            log_string = func.__name__ + " was called"
            with open(file, 'a') as opened_file:
                opened_file.write(log_string + '\n')
            return func(*args, **kwargs)
        return wrapper
    return decorator
 
@logit(file='log.log')
def func():
    pass
    
func()
python

总结#

总之, 这次开发经历十分有趣, 问了很多大佬, 学了很多知识, 感谢善良的人们

python装饰器学习
https://astro-pure.js.org/blog/python-decorator
Author TeAnli
Published at November 11, 2025