有人可以提供一个代码示例,它使用asynio以非阻塞方式监听按键,并在每次点击时将键码放入控制台吗?
这不是关于某些图形工具包的问题
答案 0 :(得分:6)
因此,Andrea Corbellini提供的链接是解决问题的一个聪明而彻底的解决方案,但也非常复杂。如果您只想提示您的用户输入一些输入(或模拟raw_input),我更喜欢使用更简单的解决方案:
import sys
import functools
import asyncio as aio
class Prompt:
def __init__(self, loop=None):
self.loop = loop or aio.get_event_loop()
self.q = aio.Queue(loop=self.loop)
self.loop.add_reader(sys.stdin, self.got_input)
def got_input(self):
aio.ensure_future(self.q.put(sys.stdin.readline()), loop=self.loop)
async def __call__(self, msg, end='\n', flush=False):
print(msg, end=end, flush=flush)
return (await self.q.get()).rstrip('\n')
prompt = Prompt()
raw_input = functools.partial(prompt, end='', flush=True)
async def main():
# wait for user to press enter
await prompt("press enter to continue")
# simulate raw_input
print(await raw_input('enter something:'))
loop = aio.get_event_loop()
loop.run_until_complete(main())
loop.close()
答案 1 :(得分:2)
我写了类似于名为aioconsole的软件包的一部分。
它提供了一个名为get_standard_streams
的协程,它返回与stdin
和stdout
对应的两个asyncio streams。
以下是一个例子:
import asyncio
import aioconsole
async def echo():
stdin, stdout = await aioconsole.get_standard_streams()
async for line in stdin:
stdout.write(line)
loop = asyncio.get_event_loop()
loop.run_until_complete(echo())
它还包含与input
:
something = await aioconsole.ainput('Entrer something: ')
它应该适用于文件流和非文件流。请参阅实施here。
答案 2 :(得分:2)
使用队列的另一种方法是使命令行成为异步生成器,并在它们进入时处理命令,如下所示:
import asyncio
import sys
class UserInterface(object):
def __init__(self, task, loop):
self.task = task
self.loop = loop
def get_ui(self):
return asyncio.ensure_future(self._ui_task())
async def _ui_cmd(self):
while True:
cmd = sys.stdin.readline()
cmd = cmd.strip()
if cmd == 'exit':
self.loop.stop()
return
yield cmd
async def _ui_task(self):
async for cmd in self._ui_cmd():
if cmd == 'stop_t':
self.task.stop()
elif cmd == 'start_t':
self.task.start()
答案 3 :(得分:2)
执行此操作的高级纯异步方法如下。
import asyncio
import sys
async def main():
# Create a StreamReader with the default buffer limit of 64 KiB.
reader = asyncio.StreamReader()
pipe = sys.stdin
loop = asyncio.get_event_loop()
await loop.connect_read_pipe(lambda: asyncio.StreamReaderProtocol(reader), pipe)
async for line in reader:
print(f'Got: {line.decode()!r}')
asyncio.run(main())
async for line in reader
循环可以更明确地编写,例如如果要在循环内打印提示或捕获异常:
while True:
print('Prompt: ', end='', flush=True)
try:
line = await reader.readline()
if not line:
break
except ValueError:
print('Line length went over StreamReader buffer limit.')
else:
print(f'Got: {line.decode()!r}')
一个空的 line
(不是 '\n'
而是一个实际的空字符串 ''
)表示文件结束。请注意,await reader.readline()
可能会在 ''
返回 False 后立即返回 reader.at_eof()
。有关详细信息,请参阅 Python asyncio: StreamReader。
这里的 readline()
是异步收集一行输入。也就是说,事件循环可以在读取器等待更多字符时运行。相比之下,在其他答案中,事件循环可能会阻塞:它可以检测到某些输入可用,进入调用 sys.stdin.readline()
的函数,然后阻塞它直到有一个结束线可用(阻止任何其他任务进入循环)。当然,在大多数情况下这不是问题,因为结束线与(在行缓冲的情况下,这是默认值)一起可用或很快(在其他情况下,假设行相当短)的任何初始字符线。
从管道读取时,您还可以使用 await reader.readexactly(1)
读取单个字节以逐字节读取。从终端读取按键时,需要正确设置,查看 Key Listeners in python? 了解更多信息。在 UNIX 上:
import asyncio
import contextlib
import sys
import termios
@contextlib.contextmanager
def raw_mode(file):
old_attrs = termios.tcgetattr(file.fileno())
new_attrs = old_attrs[:]
new_attrs[3] = new_attrs[3] & ~(termios.ECHO | termios.ICANON)
try:
termios.tcsetattr(file.fileno(), termios.TCSADRAIN, new_attrs)
yield
finally:
termios.tcsetattr(file.fileno(), termios.TCSADRAIN, old_attrs)
async def main():
with raw_mode(sys.stdin):
reader = asyncio.StreamReader()
loop = asyncio.get_event_loop()
await loop.connect_read_pipe(lambda: asyncio.StreamReaderProtocol(reader), sys.stdin)
while not reader.at_eof():
ch = await reader.read(1)
# '' means EOF, chr(4) means EOT (sent by CTRL+D on UNIX terminals)
if not ch or ord(ch) <= 4:
break
print(f'Got: {ch!r}')
asyncio.run(main())
请注意,这并不是一次一个字符或一个键:如果用户按下一个组合键,给出一个多字节字符,如 ALT+E,按下 ALT 时不会发生任何事情,两个字节将被发送按下 E 时的终端,这将导致循环的两次迭代。但对于字母和 ESC 等 ASCII 字符已经足够了。
如果您需要像 ALT 这样的实际按键,我想唯一的方法是使用 a suitable library 并通过在单独的线程中调用它来使其与 asyncio 一起使用,例如 here。事实上,在其他情况下,库+线程方法可能也更简单。
如果您想要更好的控制,您可以实现自己的协议来代替 StreamReaderProtocol
:一个实现任意数量的 asyncio.Protocol
函数的类。最小示例:
class MyReadProtocol(asyncio.Protocol):
def __init__(self, reader: asyncio.StreamReader):
self.reader = reader
def connection_made(self, pipe_transport):
self.reader.set_transport(pipe_transport)
def data_received(self, data: bytes):
self.reader.feed_data(data)
def connection_lost(self, exc):
if exc is None:
self.reader.feed_eof()
else:
self.reader.set_exception(exc)
您可以用自己的缓冲机制替换 StreamReader。在您调用 connect_read_pipe(lambda: MyReadProtocol(reader), pipe)
之后,将恰好有一次对 connection_made
的调用,然后是对 data_received
的任意多次调用(数据取决于终端和 Python 缓冲选项),最后恰好是一次对 connection_lost
的调用connect_read_pipe
(在文件结尾或错误时)。如果您需要它们,(transport, protocol)
会返回一个元组 protocol
,其中 MyReadProtocol
是 transport
的一个实例(由协议工厂创建,在我们的例子中是一个微不足道的lambda),而 asyncio.ReadTransport
是 _UnixReadPipeTransport
的实例(特别是一些私有实现,如 UNIX 上的 StreamReader
)。
但最终这些都是最终依赖于 loop.add_reader
(与 ProactorEventLoop
无关)的样板。
对于 Windows,您可能需要选择 LookerNodeSDK
(自 Python 3.8 以来的默认设置),请参阅 Python asyncio: Platform Support。