asyncio create_connection 协议工厂

asyncio create_connection protocol factory

Python 3 的 asyncio 模块中的 create_connection 函数将协议工厂作为其第一个参数。该文档有以下注释:

Note protocol_factory can be any kind of callable, not necessarily a class. For example, if you want to use a pre-created protocol instance, you can pass lambda: my_protocol.

因此您可以使用 lambda 传递实例,如下所示:

create_connection(lambda: Protocol(a, b, c))

另一种方法是将 __call__ 定义为 return self,这样您就可以在不定义 lambda 的情况下传递实例。

protocol = Protocol(a, b, c)
create_connection(protocol)

是否有任何理由像文档建议的那样使用 lambda 在 class 上定义 __call__

简答:

应优先使用 lambda,因为它更具可读性 - 无需仔细检查协议 class 代码即可轻松理解。

解释:

BaseEventLoop.create_connection 从 BaseEventLoop._create_connection_transport ref 产生,它从协议 class 中实例化一个协议对象,如下所示:

    protocol = protocol_factory()

我们可以在没有事件循环代码的情况下以简化的方式呈现问题,以演示如何实例化协议:

class Prococol:
   pass

def create_connection(Protocol):
   protocol = Protocol()

create_connection(Protocol)

因此,"protocol = Protocol()" 需要使用参数。这可以通过使用 lambda 来实现:

class Protocol:
  def __init__(self, a):
     self.a = a

def create_connection(Protocol):
  protocol = Protocol()

create_connection(lambda: Protocol(1))

或者 OP 建议的替代建议是使对象可调用:

class Protocol:
  def __init__(self, a):
     self.a = a

  def __call__(self):
     return self

def create_connection(Protocol):
  protocol = Protocol()

create_connection(Protocol(1))

在功能上两者都可以,因此这是一个更好的做法的问题。我认为 lambda 方法更好,因为查看最后一行 create_connection(lambda: Protocol(1)) 清楚地表明我们正在传递给 create_connection 函数,调用时 returns 一个对象,而传递一个 a可调用对象使代码的可读性降低 - 因为需要仔细检查协议 class 以确定实例化对象也是可调用实体。

Udi 对这个问题的回答说,使用 def __call__(self): return self,将不适用于 create_server(顺便说一句,这不是问题所问的),因为它将重用实例化对象的一个​​实例.这个观察是正确的,但该答案中遗漏的是可以轻松调整可调用对象以与 create_server 一起使用。例如:

class Protocol:
  def __init__(self, a):
     self.a = a

  def __call__(self):
     return Protocol(self.a)

底线是使用 __call__ 应该像 lambda 方法一样工作。应优先使用 lambda 的原因是出于可读性原因。

注意这两行的区别:

loop.create_connection(MyProtocol, '127.0.0.1', 8888)    # Option #1
loop.create_connection(MyProtocol(), '127.0.0.1', 8888)  # Option #2

这是来自 asyncio 文档的 echo 客户端示例,已修改为与选项 #1 一起使用:

class MyEchoClientProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        message = "hello"
        transport.write(message.encode())
        print('Data sent: {!r}'.format(message))

    def data_received(self, data):
        print('Data received: {!r}'.format(data.decode()))

    def connection_lost(self, exc):
        print('The server closed the connection')
        print('Stop the event loop')
        loop.stop()


loop = asyncio.get_event_loop()
coro = loop.create_connection(MyEchoClientProtocol, '127.0.0.1', 8765)
loop.run_until_complete(coro)
loop.run_forever()
loop.close()

如果您选择使用选项 #2,则需要实施 MyProtocol.__call__(self),它适用于 MyProtocol 个实例

虽然这对于 create_connection 可能工作正常,因为您的 __call__ 将只被调用一次,这对于 create_serverprotocol_factory 参数不起作用:

...
# Each client connection will create a new protocol instance
coro = loop.create_server(EchoServerClientProtocol, '127.0.0.1', 8888)
...

此处 protocol_factory 被多次调用以创建新的 Protocol 实例。使用 EchoServerClientProtocol() 并定义 def __call__(self): return self 将仅重用 Protocol!

的一个实例