从Python队列读取行
我正在埋头研究Python线程。我创建了一个供应商线程,它通过队列从*nix(serial)/dev返回字符/行数据 作为练习,我希望每次使用队列中的一行数据(使用“\n”作为行终止符) 我目前(过于简单)的解决方案是每次只将()放入队列中1个字符,因此消费者一次只能得到()一个字符。(这是一个安全的假设吗?)从Python队列读取行,python,Python,我正在埋头研究Python线程。我创建了一个供应商线程,它通过队列从*nix(serial)/dev返回字符/行数据 作为练习,我希望每次使用队列中的一行数据(使用“\n”作为行终止符) 我目前(过于简单)的解决方案是每次只将()放入队列中1个字符,因此消费者一次只能得到()一个字符。(这是一个安全的假设吗?) ... return_buffer = [] while True: rcv_data = queue.get(block=True) return_buffer.app
...
return_buffer = []
while True:
rcv_data = queue.get(block=True)
return_buffer.append(rcv_data)
if rcv_data == "\n":
return return_buffer
这似乎是可行的,但当我一次放入()2个字符时,我肯定会导致它失败
我希望使接收逻辑更通用,能够处理多字符put()s
我的下一个方法是使用rcv_data.partition(“\n”),将“余数”放在另一个缓冲区/列表中,但这需要在队列旁边处理临时缓冲区。
(我想另一种方法是一次只放一行,但这其中的乐趣何在?)
是否有一种更优雅的方式一次读取队列中的一行内容?如果您的特定用例生产者需要逐个字符地放入队列,那么我想我看不出在消费者中让它们进入循环有什么错。但是使用
StringIO
对象作为缓冲区可能会获得更好的性能
from cStringIO import StringIO
# python3: from io import StringIO
buf = StringIO()
如果文件喜欢对象,则可以写入它,查找它,并随时调用getvalue()
以获取缓冲区中的完整字符串值。这很可能会给您带来比不断增加列表、将其连接到字符串并清除列表更好的性能
return_buffer = StringIO()
while True:
rcv_data = queue.get(block=True)
return_buffer.write(rcv_data)
if rcv_data == "\n":
ret = return_buffer.getvalue()
return_buffer.seek(0)
# truncate, unless you are counting bytes and
# reading the data directly each time
return_buffer.truncate()
return ret
这可能是发电机的良好用途。它将精确地恢复到屈服后停止的位置,因此减少了您需要的存储和缓冲区交换量(我无法谈论它的性能)
编辑:
一旦J.F.塞巴斯蒂安指出行分隔符可以是多字符的,我也必须解决这个问题。我还使用了jdi答案中的StringIO。同样,我不能谈论效率,但我相信它在所有情况下都是正确的(至少在我能想到的情况下)。这是未经测试的,因此可能需要一些调整才能实际运行。感谢J.F.塞巴斯蒂安(J.F.Sebastian)和jdi(jdi)的回答,他们的回答最终导致了这一点
def getlines(chunks, splitOn="\n"):
r_buffer = StringIO()
for chunk in chunks
r_buffer.write(chunk)
pos = r_buffer.getvalue().find(splitOn) # can't use rfind see the next comment
while pos != -1: # A single chunk may have more than one separator
line = r_buffer.getvalue()[:pos + len(splitOn)]
yield line
rest = r_buffer.getvalue().split(splitOn, 1)[1]
r_buffer.seek(0)
r_buffer.truncate()
r_buffer.write(rest)
pos = rest.find(splitOn) # rest and r_buffer are equivalent at this point. Use rest to avoid an extra call to getvalue
line = r_buffer.getvalue();
r_buffer.close() # just for completeness
yield line # whatever is left over.
for line in getlines(iter(queue.get, None)): # break on queue.put(None)
process(line)
队列将返回您输入的内容。如果你放碎片,你会得到碎片。如果你放线,你就会得到线
如果允许输入中的部分行并且可以在以后完成,则要逐行使用,您需要显式或隐式缓冲区来存储部分行:
def getlines(fragments, linesep='\n'):
buff = []
for fragment in fragments:
pos = fragment.rfind(linesep)
if pos != -1: # linesep in fragment
lines = fragment[:pos].split(linesep)
if buff: # start of line from previous fragment
line[0] = ''.join(buff) + line[0] # prepend
del buff[:] # clear buffer
rest = fragment[pos+len(linesep):]
if rest:
buff.append(rest)
yield from lines
elif fragment: # linesep not in fragment, fragment is not empty
buff.append(fragment)
if buff:
yield ''.join(buff) # flush the rest
它允许任意长度的片段、行和行。linesep不应跨越多个片段
用法:
for line in getlines(iter(queue.get, None)): # break on queue.put(None)
process(line)
需要注意的是,队列中可能有多行。此函数将返回(并可选地打印)给定队列中的所有行:
def getQueueContents(queue, printContents=True):
contents = ''
# get the full queue contents, not just a single line
while not queue.empty():
line = queue.get_nowait()
contents += line
if printContents:
# remove the newline at the end
print line[:-1]
return contents
我之前回答的类似问题是否涵盖了这个问题?是的,正如我提到的,那会有用的。它还将把我与使用数据的方法“捆绑”在一起。这是终端数据,因此在“提示”字符处开始解析输出需要重新写入生产者。我希望保留一个泛型/dev读取器线程,并能够通过改变队列使用者方法来改变检索数据的方式。我知道如何用“艰难”的方式来实现这一点,但如果存在的话,我希望学习一种更优雅的方法。哦,我假设您希望队列有多个工作来处理它。这是否仅允许单个工作者使用字符?是的,只有一个使用者。对于多个消费者来说,我同意每条线都要加上标签。对于这个问题,这可能是一种过火的方法,但我使用这种方法来学习更多关于线程的知识,以便解决潜在的更大问题。rcv_数据可能包含多个字节。至少这是我对OP想要做什么的印象。当他得到字节时,输入字节,而不是一次输入一个字节。啊,是的,我忽略了这一部分。我只是把注意力集中在缓冲区上,我猜是+1,因为它教了我关于StringIO的知识,我以前没有遇到过它。@Grive:我认为StringIO,再加上你的答案就太好了。如果您愿意,可以随意将其添加到您的中。您可能希望在.truncate()而不是.reset()之前使用return\u buffer.seek(0)
。io.StringIO或StringIO.StringIO中没有.reset()。它只存在于cStringIOBrilliant中!就我而言,性能并不是一个真正的问题。好主意!生成器生成单个字符的列表。列表没有.strip()方法。您可以添加yield'。加入(返回缓冲区)
以修复it@J.F.Sebastian:我确实按照你上面的建议修改了“yield”。@J.F.Sebastian:如果splitOn超过一个字符,代码将无法正常工作,需要进行更多的更改,使其更加复杂。当我有更多的时间的时候,我可以看看这个。返回一个字符列表。我试图尽可能少地修改原始海报代码。:)@塞巴斯蒂安:刚才看到你在下面回答。这确实是我所想到的额外的复杂性好的,所以队列返回的数据正好有一个放入的数据。很高兴知道。谢谢@JS:我将其概括为:“你需要一个缓冲区”。如果len(linesep)==1,并且对于大片段来说性能并不重要,那么最好是这样。如果linesep被分割成两个片段呢?还有,我对函数名为lines有点困惑,显然还有一个作用域变量名为lines。@GREEF:捕捉得好。没有递归调用,因此意外使用行
可能有效。我已经编辑了答案。我已经添加了关于linesep和多个片段的注释。在queue.get上很好地使用了iter。我偷了它来编辑上面的内容。
def getQueueContents(queue, printContents=True):
contents = ''
# get the full queue contents, not just a single line
while not queue.empty():
line = queue.get_nowait()
contents += line
if printContents:
# remove the newline at the end
print line[:-1]
return contents