DelegatorBot 在 TelePot 中是如何工作的?

How does the DelegatorBot work exactly in TelePot?

我正在尝试通过查看此处提供的 counter.py 示例来研究 python 库 Telepothttps://github.com/nickoala/telepot/blob/master/examples/chat/counter.py.
我发现有点难以理解 DelegatorBot class 的实际工作原理。

这是我到目前为止所理解的:

1.

我看到最初这个 class(派生自 "ChatHandler" class)正在被定义:

class MessageCounter(telepot.helper.ChatHandler):

    def __init__(self, *args, **kwargs):
        super(MessageCounter, self).__init__(*args, **kwargs)
        self._count = 0

    def on_chat_message(self, msg):
        self._count += 1
        self.sender.sendMessage(self._count)

2.

然后通过实例化 class DelegatorBot:

创建一个机器人
bot = telepot.DelegatorBot(TOKEN, [
    pave_event_space()(
        per_chat_id(), create_open, MessageCounter, timeout=10
    ),
])

3.

我了解到 DelegatorBot 的新实例已创建并放入变量 bot 中。第一个参数是电报验证此机器人所需的令牌,第二个参数是一个包含我不理解的内容的列表。

我是说这部分:

pave_event_space()(
    per_chat_id(), create_open, MessageCounter, timeout=10
)

然后我的问题是..

pave_event_space() 调用的方法是 returns 对另一个方法的引用吗?然后使用参数 (per_chat_id(), create_open, MessageCounter, timeout=10) ?

调用这个返回的方法

简答

是的,pave_event_space()returns一个函数。我们称之为 fnfn 然后用 fn(per_chat_id(), create_open, ...) 调用,其中 returns 一个二元组 (seeder function, delegate-producing function).

如果您想进一步研究代码,这个简短的回答可能不是很有帮助...

更长的答案

要了解 pave_event_space() 的作用以及这一系列参数的含义,我们必须回到基础并了解 DelegatorBot 接受的参数。

DelegatorBot的构造函数是explained here。简单地说,它接受一个 2 元组 (seeder function, delegate-producing function) 的列表。为了减少冗长,我将调用第一个元素 seeder 和第二个元素 delegate-producer.

一个播种者有这个签名seeder(msg) -> number。对于收到的每条消息,都会调用 seeder(msg) 以生成 number。如果 number 是新的,则将调用伴随的委托生产者(与播种者共享同一个元组的那个)来产生一个线程,该线程用于处理新消息。如果 number 已被 运行 线程占用,则什么都不做。实质上,播种者 "categorizes" 消息。如果它看到属于新 "category".

的消息,它会生成一个新线程

委托制作人具有此签名 producer(cls, *args, **kwargs) -> Thread。它调用 cls(*args, **kwargs) 来实例化一个处理程序对象(在你的例子中是 MessageCounter)并将其包装在一个线程中,因此处理程序的方法是独立执行的。

(注意:实际上,播种者不一定 returns 和 number 委托制作人不一定 returns 和 Thread。我简化了上面是为了清楚。See the reference 以获得完整的解释。)

在 telepot 的早期,DelegatorBot 通常是通过透明地提供播种机和委托制作人来实现的:

bot = DelegatorBot(TOKEN, [
        (per_chat_id(), create_open(MessageCounter, ...))])

后来,我向处理程序(例如 ChatHandler)添加了生成自己的事件(例如,超时事件)的功能。每个 class 的处理程序都有自己的 事件 space,所以不同的 classes 事件不会混合。在每个事件 space 中,事件对象本身也有一个 source id 来标识哪个处理程序发出了它。这种架构对播种者和委托生产者提出了一些额外的要求。

Seeders 必须能够 "categorize" 事件(除了外部消息)和 returns 相同的 number 导致事件发射器(因为我们不希望为此事件生成一个线程;它应该由事件发射器本身处理)。委托生产者还必须将适当的事件 space 传递给处理程序 class(因为每个处理程序 class 都会获得一个在外部生成的唯一事件 space)。

为了使一切正常工作,必须向播种机及其伙伴委托制作人提供相同的事件 space。并且每一对 (seeder, delegate-producer) 都必须获得一个全局唯一事件 space。 pave_event_space() 保证了这两个条件,基本上是在 per_chat_id()create_open() 上修补了一些额外的操作和参数,并确保它们是一致的。

更深

"patching"究竟是如何完成的?为什么我让你做 pave_event_space()(...) 而不是更直接的 pave_event_space(...)

首先,回想一下我们的最终目标是拥有一个 2 元组 (per_chat_id(), create_open(MessageCounter, ...))。对于 "patch",它通常意味着 (1) 向 per_chat_id() 附加一些额外的操作,以及 (2) 向调用 create_open(... more arguments here ...) 插入一些额外的参数。这意味着我不能让用户直接调用 create_open(...) 因为一旦调用,我就不能插入额外的参数。我需要一个更抽象的构造,其中用户指定 create_open 但调用 create_open(...) 实际上是由我进行的。

想象一个名为 pair 的函数,其签名为 pair(per_chat_id(), create_open, ...) -> (per_chat_id(), create_open(...))。换句话说,它将第一个参数作为第一个元组元素传递,并通过使用剩余参数实际调用 create_open(...) 来创建第二个元组元素。

现在,已经到了无法用语言解释源代码的地步(我已经思考了30分钟)。 pave_event_space 的伪代码如下所示:

def pave_event_space(fn=pair):
    def p(s, d, *args, **kwargs):
        return fn(append_event_space_seeder(s), 
                  d, *args, event_space=event_space, **kwargs)
    return p

它采用函数 pair,并且 returns 是一个类似于 pair 的函数(签名与 pair 相同),但具有更复杂的播种器和更多参数标记上。这就是我所说的 "patching".

pave_event_space 是最常见的 "patcher"。其他修补程序包括 include_callback_query_chat_idintercept_callback_query_origin。它们基本上都做同样的事情:接受一个类似 pair 的函数,returns 另一个类似 pair 的函数,带有更复杂的播种器和更多标记的参数。因为输入和输出相似,所以可以将它们链接起来应用多个补丁。如果你查看 callback examples,你会看到类似这样的内容:

bot = DelegatorBot(TOKEN, [
    include_callback_query_chat_id(
        pave_event_space())(
            per_chat_id(), create_open, Lover, timeout=10),
])

它修补事件 space 的东西,然后修补回调查询的东西,使播种器 (per_chat_id()) 和处理程序 (Lover) 能够协同工作。

我现在只能说这些了。我希望这对代码有所启发。祝你好运。