我的程序("TRAC Processor")使用逐个字符的输入。我正在为字符串实现类似readline的输入功能,这些字符串以enter
以外的字符(通常为'
)终止,并且可能本身是多行的。所以我在输入字符之间输出终端转义序列,包括查询终端仿真器的转义序列(光标位置和屏幕大小)。为了进行跨平台的单字符输入,我使用了http://code.activestate.com/recipes/134892/,这非常有帮助。
这与粘贴工作正常......直到我需要在粘贴的第一个字符后得到终端响应。似乎粘贴的文本与转义序列的响应混合在一起。我想我会在启动转义序列查询之前通过刷新输入缓冲区来修复它:等待10ms,如果没有输入继续;如果有输入,请缓冲它并再次等待直到没有输入。基于this post,我尝试使用select()
轮询stdin。好主意,但它没有用,它产生了非常奇怪的行为。我在这个问题的原始版本中发布了这种奇怪的行为,认为我误解了选择并且有办法解决它。似乎没有,但我找到了另一种方法来刷新(并保存)输入流。我决定保留这个问题,并将该方法作为答案发布。
解释select()
的问题here。在粘贴的第一个字符之后,其他字符已经被缓冲,并且select
仅在已经缓冲的新输入之后才返回新输入。我无法让自己删除我所产生的MWE这种行为,所以你可以在下面看到它。
不幸的是,该帖子中提出的答案要么对我不起作用,要么需要更多解释。 @slowdog建议使用无缓冲的输入(os.read(stdin.fileno(),1)而不是stdin.read(1))。这解决了选择问题,但它破坏了粘贴:似乎第一个之后粘贴的所有字符都被缓冲,无论什么,所以你永远不会看到它们。它似乎也没有与转义序列响应很好地工作,它们似乎也得到了缓冲。它也很烦人,因为你需要刷新输出缓冲区,但这并不是那么糟糕。 @Omnafarious在一篇评论中说:“虽然,处理Python缓冲问题的另一种方法是简单地进行无参数读取,这应该读取当前可用的所有内容。”这最终是我所做的,如下所示,但“简单地”结果并非如此简单。还有另一个解决方案here,但我认为必须是一种无需线程即可实现此目的的方法。
顺便提一下,有一个相对简单的解决方法,因为事实证明,粘贴不是随机散布的对逃逸序列的响应。在转义序列响应之前读取整个粘贴的剩余部分,这样当您查找转义序列响应(它本身以转义开始)时,您可以只缓冲在转义之前读取的所有字符,并处理他们以后。如果您可能在终端上键入ESC字符,则此操作仅会失败。在任何情况下,到目前为止我都非常喜欢解决这个问题,我认为其他人可能会觉得答案很有价值。
无论如何,这里的FWIW是select
问题的MWE,它只是回应文本而不是缓冲它:
def flush():
import sys, tty, termios
from select import select
tty.setraw(sys.stdin.fileno())
while True:
rlist, wlist, xlist = select([sys.stdin], [], [], 1)
if rlist == []: return
sys.stdout.write(sys.stdin.read(1))
将其粘贴到Python提示符(2.7.9)中,并在末尾添加另一个空行。如果您调用flush()
并输入一些文本的速度超过每秒一个字母,则会将其键入给您。例如,我键入“hello”然后暂停,得到了这个结果:
>>> flush()
hello>>>
在OSX终端应用程序中(至少),如果你将单词text
复制到剪贴板,调用该函数并在一秒内点击粘贴,这是你得到的:
>>> flush()
t>>>
奇!只有第一个字母。再试一次,什么都不输入:
>>> flush()
>>>
暂停一秒钟,什么都不做,就像没有输入等等,对吗?再试一次,点击?
:
>>> flush()
ext?>>>
你可以在?
之前获得剩余的粘贴,保存起来!另外,奇怪的是,在键入我不理解的?
之前有1秒的暂停。如果你在这一点上再试一次,它就像正常一样。
好的,让我们再试一次,首先粘贴text
,然后粘贴WTF
,然后输入!
:
>>> flush()
t>>> flush()
extW>>> flush()
TF!>>>
因此,粘贴仅提供第一个字母,并将其他字符保留在输入缓冲区中,并在W
和!
之前暂停一秒。还有一件奇怪的事情:缓冲的字符不会在Python >>>
提示符下输入。
一个挥之不去的问题:为什么在下一个字母回显之前你会得到额外的1秒暂停?选择并不总是等待整个时间段......
答案 0 :(得分:0)
"无参数读取"通常被认为是一种读取所有可用字节的方法,这听起来非常适合这个应用程序。不幸的是,当你查看documentation时,read()与readall()相同,它会阻塞直到EOF。所以你需要将stdin设置为非阻塞模式。
一旦你这样做,你就会开始:
IOError: [Errno 35] Resource temporarily unavailable
当你谷歌这一点时,绝大多数回答说这个问题的解决方案是摆脱非阻塞模式...所以这里没有帮助。但是,this post解释说这只是在没有要返回的字符时非阻塞read()
所做的事情。
这是我的flush()
函数,其中大部分都是从该帖子中复制的:
def flush():
import sys, tty, termios, fcntl, os
fd = sys.stdin.fileno()
old_attr = termios.tcgetattr(fd)
old_fl = fcntl.fcntl(fd, fcntl.F_GETFL)
try:
tty.setraw(fd)
fcntl.fcntl(fd, fcntl.F_SETFL, old_fl | os.O_NONBLOCK)
inp = sys.stdin.read()
except IOError, ex1: #if no chars available generates exception
try: #need to catch correct exception
errno = ex1.args[0] #if args not sequence get TypeError
if errno == 35:
return '' #No characters available
else:
raise #re-raise exception ex1
except TypeError, ex2: #catch args[0] mismatch above
raise ex1 #ignore TypeError, re-raise exception ex1
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_attr)
fcntl.fcntl(fd, fcntl.F_SETFL, old_fl)
return inp
希望它对某人有帮助!