POSIX I/O函数是Python文件对象的基础,它们有两种模式:阻塞和非阻塞,由名为
O_NONBLOCK
的标志控制。特别地,
read
函数表示:
当尝试读取一个当前没有数据可用的文件时…如果设置了O_NONBLOCK
,则read()将返回-1并将errno设置为[EAGAIN]
。
在Python中,该标志在
os
模块中可用。
sys.stdin
已经打开,因此你不能只是将O_NONBLOCK
传递给os.open
函数,那么你该怎么办?嗯,你实际上可能希望打开/dev/tty
;这有点取决于你实际在做什么。在那种情况下,答案很明显。但是让我们假设你不需要这样做。所以,你想要更改已经打开文件的标志。这正是fcntl
的用途。你使用F_GETFL
操作来读取当前标志,或者在O_NONBLOCK
位中设置,然后F_SETFL
结果。当然,如果你想恢复原状,可以记住当前标志。
在Python中,fcntl
函数和操作常量都在fcntl
模块中可用。
最后一个问题: sys.stdin
不是原始文件对象,它是一个TextIOWrapper
,在BufferedReader
上进行Unicode解码,而FileIO
则添加了缓冲。因此,sys.stdin.read()
并没有直接调用 POSIX 的 read
函数。为了做到这一点,你需要使用 sys.stdin.buffer.raw
。如果你想在原始和普通输入之间来回切换,可能还需要进行大量的小心清除。(请注意,这意味着你放弃了 Unicode,并获得了一个单字节的 bytes
对象,这可能是UTF-8字符的一半或终端转义字符的四分之一。希望你已经预料到了。)
现在,当没有可用内容时,
FileIO.read
返回什么?嗯,它是
RawIOBase
的一个实现,
RawIOBase.read
说:
如果返回 0 字节,并且大小不为 0,则表示文件结束。如果对象处于非阻塞模式并且没有可用的字节,则返回 None
。
换句话说,如果没有可用内容,则会得到 None
,对于 EOF,将获得 b''
,对于其他任何内容,都将获得一个单字节的 bytes
。
因此,将所有内容组合在一起:
old_settings = termios.tcgetattr(fd)
old_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
try:
tty.setraw(fd)
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags | os.O_NONBLOCK)
return sys.stdin.buffer.raw.read(1)
finally:
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags)
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
最后一件事:有没有一种方法可以在不读取输入的情况下判断输入是否已准备好?是的,但不具有可移植性。根据您的平台,select
、poll
、epoll
和/或kqueue
可能可用,并且可能适用于常规文件。read(0)
可能保证返回b''
而不是None
等等。您可以阅读本地手册页面。但更简单的解决方案是与C的stdio相同:添加一个1字节最大缓冲区,并使用它来实现自己的read
和peek
或read
和unread
包装器。
O_NONBLOCK
打开/重新打开/fcntl文件(您可能会选择使用/dev/tty
而不是标准输入),然后要么使用select.select
,要么处理read
返回的EAGAIN
错误或None
(如果您正在使用类似于sys.stdin
的TextIOWrapper
而不是其底层的.buffer.raw
,则在任一情况下您可能会得到一个''
或其他内容;据我所知,它实际上没有被指定...)。 - abarnertrichinput
这样的库的源代码可能展示了你需要的所有东西,即使你不想直接使用这样的库。 - abarnertcurses
,所以...如果你从未听说过它,请查看一下。 - abarnert