5

我正在尝试Telepot通过查看counter.py此处提供的示例来研究 python 库:https ://github.com/nickoala/telepot/blob/master/examples/chat/counter.py 。
我发现有点难以理解该DelegatorBot课程的实际运作方式。

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

1.

我看到最初正在定义这个类(派生自“ChatHandler”类):

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.

然后通过实例化类创建一个机器人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()调用的方法是否返回对另一个方法的引用?然后用参数调用这个返回的方法(per_chat_id(), create_open, MessageCounter, timeout=10)

4

1 回答 1

12

简短的回答

是的,pave_event_space()返回一个函数。让我们称之为fnfn然后用 调用fn(per_chat_id(), create_open, ...),它返回一个 2-tuple (seeder function, delegate-producing function)

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

更长的答案

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

DelegatorBot的构造函数在这里解释。简单地说,它接受一个 2-tuples 列表(seeder function, delegate-producing function)。为了减少冗长,我将调用第一个元素seeder和第二个元素delegate-producer

播种机具有此签名seeder(msg) -> number。对于收到的每条消息,seeder(msg)都会调用以生成一个number. 如果这number是新的,则将调用配套的委托生产者(与播种者共享相同元组的那个)来生成一个线程,该线程用于处理新消息。如果它number已被正在运行的线程占用,则什么也不做。本质上,播种器对消息进行“分类”。如果它看到一条消息属于一个新的“类别”,它就会产生一个新线程。

委托生产者具有此签名producer(cls, *args, **kwargs) -> Thread。它调用cls(*args, **kwargs)实​​例化处理程序对象(MessageCounter在您的情况下)并将其包装在线程中,因此处理程序的方法是独立执行的。

(注意:实际上,播种者不一定返回 a number,委托生产者不一定返回 a Thread。为清楚起见,我在上面进行了简化。请参阅参考资料以获取完整说明。)

在 Telepot 的早期,aDelegatorBot通常是通过透明地提供播种机和委托生产者来制作的:

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

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

播种者必须能够“分类”事件(除了外部消息)并返回number导致事件发射器的相同事件(因为我们不想为此事件产生线程;它应该由事件处理发射器本身)。委托生产者还必须将适当的事件空间传递给 Handler 类(因为每个 Handler 类都有一个唯一的事件空间,由外部生成)。

为了让一切正常工作,必须向播种机及其同伴委托生产者提供相同的事件空间。并且每一对(seeder, delegate-producer)都必须获得一个全球唯一的活动空间。pave_event_space()确保这两个条件,基本上将一些额外的操作和参数修补到per_chat_id()create_open()确保它们是一致的。

更深一点

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

首先,回想一下我们的最终目标是拥有一个 2-tuple (per_chat_id(), create_open(MessageCounter, ...))。对于“补丁”,它通常意味着(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

它接受 function pair,并返回一个类似pair的函数(签名与 相同pair),但带有更复杂的播种器和更多标记的参数。这就是我所说的“修补”。

pave_event_space是最常见的“修补程序”。其他修补程序包括include_callback_query_chat_idintercept_callback_query_origin. 它们都做基本相同的事情:接受一个类似pair函数,返回另一个pair类似函数,带有更复杂的播种器和更多标记的参数。因为输入和输出是相似的,所以它们可以链接起来应用多个补丁。如果您查看回调示例,您将看到如下内容:

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

它修补事件空间的东西,然后修补回调查询的东西,以使播种器 ( per_chat_id()) 和处理程序 ( Lover) 能够内聚地工作。

这就是我现在能说的。我希望这能对代码有所启发。祝你好运。

于 2017-07-30T07:20:14.030 回答