我试图通过查看此处提供的Telepot
示例https://github.com/nickoala/telepot/blob/master/examples/chat/counter.py来尝试研究python库counter.py
。
我发现有点难以理解DelegatorBot
类实际上是如何工作的。
这是我认为到目前为止我所理解的:
我看到最初这个类(派生自" 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)
然后通过实例化课程DelegatorBot
来创建机器人:
bot = telepot.DelegatorBot(TOKEN, [
pave_event_space()(
per_chat_id(), create_open, MessageCounter, timeout=10
),
])
我了解创建DelegatorBot
的新实例并将其放入变量bot
中。第一个参数是电报验证此机器人所需的令牌,第二个参数是包含我不理解的内容的列表。
我的意思是这一部分:
pave_event_space()(
per_chat_id(), create_open, MessageCounter, timeout=10
)
pave_event_space()
调用的方法是否返回对另一个方法的引用?然后使用参数(per_chat_id(), create_open, MessageCounter, timeout=10)
?
答案 0 :(得分:12)
是的,pave_event_space()
返回一个函数。我们称之为fn
。然后使用fn
调用fn(per_chat_id(), create_open, ...)
,返回2元组(seeder function, delegate-producing function)
。
如果你想进一步研究这些代码,这个简短的答案可能不是很有用......
要了解pave_event_space()
的作用以及该系列参数的含义,我们必须回到基础并理解DelegatorBot
接受的参数。
DelegatorBot
的构造函数是explained here。简而言之,它接受2元组(seeder function, delegate-producing function)
的列表。为了减少冗长,我打算将第一个元素称为播种机,将第二个元素称为委托生产者。
播种机有此签名seeder(msg) -> number
。对于收到的每条消息,都会调用seeder(msg)
来生成number
。如果number
是新的,则会调用伴随委托生成器(与播种器共享相同元组的那个)生成一个线程,该线程用于处理新消息。如果number
已被正在运行的线程占用,则不执行任何操作。从本质上讲,播种机"分类"消息。如果它看到一条消息属于一个新的"类别"它会产生一个新线程。
委托制作人拥有此签名producer(cls, *args, **kwargs) -> Thread
。它调用cls(*args, **kwargs)
来实例化一个处理程序对象(在你的情况下为MessageCounter
)并将其包装在一个线程中,因此处理程序的方法是独立执行的。
(注意:实际上,播种者不一定会返回number
,而委托制作者也不一定会返回Thread
。为了清楚起见,我在上面进行了简化。See the reference完整的解释。)
在电话的早期阶段,DelegatorBot
通常是通过透明地提供播种机和代表制作人来制作的:
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元组(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
它接受函数pair
,并返回一个类似pair
的函数(与pair
相同的签名),但标注更复杂的播种器和更多参数。这就是我的意思"修补"。
pave_event_space
是最常见的"修补程序"。其他修补程序包括include_callback_query_chat_id
和intercept_callback_query_origin
。他们都做了基本相同的事情:采用类似pair
的函数,返回另一个pair
- 类函数,使用更复杂的播种器和更多参数标记。由于输入和输出相似,因此可以链接它们以应用多个补丁。如果您查看callback examples,您会看到如下内容:
bot = DelegatorBot(TOKEN, [
include_callback_query_chat_id(
pave_event_space())(
per_chat_id(), create_open, Lover, timeout=10),
])
它修补事件空间的内容,然后修补回调查询内容,使播种器(per_chat_id()
)和处理程序(Lover
)能够协同工作。
我现在可以说的全部。我希望这会对代码有所启发。祝你好运。