【问题标题】:Python - how can I use generators more succinctly?Python - 如何更简洁地使用生成器?
【发布时间】:2014-02-19 07:18:43
【问题描述】:

(Python 3)

我正在使用 Python 生成器从队列中读取消息。

消费者读取队列消息后,如果队列消息处理成功,需要能够告诉生成器删除队列消息。

为了将 .send() 发送到 Python 生成器,看来我必须首先将 .send(None) 发送到生成器。这让我的代码比我想象的要胖。

谁能建议一种方法让 qconsumer.py 用更少的代码行来驱动生成器?我已经确定了我希望消除以下哪些行。

简而言之,我怎样才能使下面的代码更紧凑,关于如何删除行有什么建议吗?

下面的代码是qconsumer.py:

from qserver import Qserver

myqserver = Qserver()

myproducer = myqserver.producer() # trying to eliminate this line
# first send to a generator must be None
myproducer.send(None) # trying to eliminate this line
for msg in myproducer:
    # do something with message
    print(msg)
    if messageprocessok:
        myproducer.send('delete')

下面的代码是qserver.py:

# -*- coding: utf-8 -*-
import boto
from boto.sqs.connection import SQSConnection
from boto.sqs.message import Message

QNAME = 'qinbound'
SQSREGION = 'us-west-1'

class Qserver():
    """A simple Q server."""

    def __init__(self, qname=None, sqsregion=None):
        self.qname = qname or QNAME
        self.sqsregion = sqsregion or SQSREGION
        self.sqsconn = boto.sqs.connect_to_region(self.sqsregion)
        self.q_in = self.sqsconn.get_queue(self.qname)

    def producer(self):
        while True:
            qmessage = self.q_in.read(wait_time_seconds=20)
            if qmessage is None:
                continue
            action = (yield qmessage.get_body())
            if action == 'delete':
                # if processing completed ok, clear message from this queue
                self.q_in.delete_message(qmessage)

【问题讨论】:

  • 您确定需要它吗? None 是您唯一可以发送到未启动的生成器的东西,但您当然不需要这样做。对qconsumer.py 代码的简单测试(使用更简单的虚拟生成器)无需调用myproducer.send(None) 即可正常工作。但是,generator.send()返回生成器的下一个值;您在 for 循环中混合迭代生成器并使用 myproducer.send 显式提取下一个值 - 您应该只使用一个或另一个。
  • @lanzz hmmm.... 听起来我的方法是错误的。

标签: python python-3.x generator


【解决方案1】:

您当前的消费者正在丢弃消息,因为每个send 调用都会返回一个。你应该这样做:

myqserver = Qserver()
myproducer = myqserver.producer() 
messageprocessok = False
while True:
    msg = myproducer.send('delete' if messageprocessok else None)
    # do something with message
    print(msg)

或者:

myqserver = Qserver()
myproducer = myqserver.producer() 
msg = next(myproducer)
while True:
    # do something with message
    print(msg)
    msg = myproducer.send('delete' if messageprocessok else None)

您需要分别调用Qserver()myqserver.producer() 的事实仅仅是因为您将prouducer 设为类的方法。或者,您可以使用独立函数,或创建一个简单返回 Qserver().producer() 的包装函数。这是单机版:

def producer(qname=None, sqsregion=None):
    qname = qname or QNAME
    sqsregion = sqsregion or SQSREGION
    sqsconn = boto.sqs.connect_to_region(sqsregion)
    q_in = sqsconn.get_queue(qname)
    while True:
        qmessage = q_in.read(wait_time_seconds=20)
        if qmessage is None:
            continue
        action = (yield qmessage.get_body())
        if action == 'delete':
            # if processing completed ok, clear message from this queue
            q_in.delete_message(qmessage)

【讨论】:

  • 嗨@Janne。在消息被处理之前,我们不会知道 messageprocessok。这符合您的解决方案吗?
  • @DukeDougal 第一个电话需要是myproducer.send(None)next(myproducer)。我正在初始化messageprocessok = False 来完成它。在第一次调用时,生成器从头开始运行,直到它产生第一条消息。
  • 有价值的信息。我将另一个标记为解决方案,因为这就是我要使用的,但你的方法似乎也不错。 StackOverflow 说只能有一个解决方案......
【解决方案2】:

在了解了您要做什么之后,我想我会避免将 send 与迭代混为一谈。让myqserver 类成为迭代器本身对我来说似乎更有意义:

# -*- coding: utf-8 -*-
import boto
from boto.sqs.connection import SQSConnection
from boto.sqs.message import Message

QNAME = 'qinbound'
SQSREGION = 'us-west-1'

class Qserver():
    """A simple Q server."""
    _current_message = None

    def __init__(self, qname=None, sqsregion=None):
        self.qname = qname or QNAME
        self.sqsregion = sqsregion or SQSREGION
        self.sqsconn = boto.sqs.connect_to_region(self.sqsregion)
        self.q_in = self.sqsconn.get_queue(self.qname)

    def __iter__(self):
        return self

    def __next__(self):
        while True:
            qmessage = self.q_in.read(wait_time_seconds=20)
            if qmessage is not None:
                self._current_message = qmessage
                return qmessage

    next = __next__

    def delete_current(self):
        if self._current_message is not None:
            self.q_in.delete_message(self._current_message)

而用法会是这样的:

from qserver import Qserver

myqserver = Qserver()
for msg in myqserver:
    # do something with message
    print(msg)
    if messageprocessok:
        myqserver.delete_current()

【讨论】:

  • 看起来很有趣。您能解释一下您建议避免将发送与迭代混合的想法吗?这样做有什么问题?
  • 主要是因为它让事情变得更简单。仅使用数据(通过send)或仅生成数据(通过屈服)的生成器更容易理解和调试。如果您同时使用两者,则需要小心,因为正如上面提到的,send 会导致生成器恢复执行,直到遇到下一个 yield。如果下一个yield 是语句而不是表达式,则结果将通过对send 的调用返回。如果你不小心,你会丢失返回的值。
  • 如果我还希望能够将消息写入队列,是否只需在您建议的 Qserver 实现中添加“writemessage”方法是否有意义?
  • 抱歉,我想我在之前(现已删除)的评论中误解了您的问题。是的,像write_messageadd_message 这样将消息添加到队列的方法将是解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-16
相关资源
最近更新 更多