在Python中任意时间捕获用户输入

发布于 2024-09-19 12:51:17 字数 191 浏览 2 评论 0原文

当用户在控制台中输入内容时,有没有办法向 python 模块发送中断?例如,如果我正在运行无限 while 循环,我可以用 try/ except for KeyboardInterrupt 包围它,然后在 except 块中执行我需要执行的操作。

有没有办法用任意输入复制此功能?控制序列还是标准字符?

编辑:抱歉,这是在Linux上

Is there a way to send an interrupt to a python module when the user inputs something in the console? For example, if I'm running an infinite while loop, i can surround it with a try/except for KeyboardInterrupt and then do what I need to do in the except block.

Is there a way to duplicate this functionality with any arbitrary input? Either a control sequence or a standard character?

Edit: Sorry, this is on linux

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

远山浅 2024-09-26 12:51:17

根据操作系统和可用的库,有不同的方法可以实现这一目标。 这个答案提供了其中的一些。

这是从那里复制的 Linux/OS X 部分,使用转义字符终止无限循环。对于 Windows 解决方案,您可以检查答案本身。

import sys
import select
import tty
import termios

from curses import ascii

def isData():
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

old_settings = termios.tcgetattr(sys.stdin)
try:
    tty.setcbreak(sys.stdin.fileno())

    i = 0
    while 1:
        print i
        i += 1

        if isData():
            c = sys.stdin.read(1)
            if c == chr(ascii.ESC):
                break

finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

编辑:将字符检测更改为使用由 curses.ascii 定义的字符,这要归功于 Daenyth 对我所分享的魔法值的不满。

Dependent on the operating system and the libraries available, there are different ways of achieving that. This answer provides a few of them.

Here is the Linux/OS X part copied from there, with in infinite loop terminated using the escape character. For the Windows solution you can check the answer itself.

import sys
import select
import tty
import termios

from curses import ascii

def isData():
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

old_settings = termios.tcgetattr(sys.stdin)
try:
    tty.setcbreak(sys.stdin.fileno())

    i = 0
    while 1:
        print i
        i += 1

        if isData():
            c = sys.stdin.read(1)
            if c == chr(ascii.ESC):
                break

finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

Edit: Changed the character detection to use characters as defined by curses.ascii, thanks to Daenyth's unhappiness with magic values which I share.

夜巴黎 2024-09-26 12:51:17

您需要一个单独的进程(或者可能是一个线程)来读取终端并通过某种形式的进程间通信(IPC)将其发送到感兴趣的进程(线程间通信可能更困难 - 基本上是您所做的唯一事情是从辅助线程向主线程发送一个KeyboardInterrupt)。既然你说“我希望有一种简单的方法可以将用户输入注入到循环中,而不仅仅是 ^C”,我很遗憾让你失望,但这是事实:有很多方法可以完成你的请求,但是简单它们并不简单。

You need a separate process (or possibly a thread) to read the terminal and send it to the process of interest via some form of inter-process communication (IPC) (inter-thread communication may be harder -- basically the only thing you do is send a KeyboardInterrupt from a secondary thread to the main thread). Since you say "I was hoping there would be a simple way to inject user input into the loop other than just ^C", I'm sad to disappoint you, but that's the truth: there are ways to do what you request, but simple they aren't.

甜味超标? 2024-09-26 12:51:17

KeyboardInterrupt 的特殊之处在于它可以被捕获(即在具有相应 POSIX 支持的操作系统下的 SIGINT,在 Windows 上的 SetConsoleCtrlHandler)并进行相应的处理。如果您想在处理用户输入的同时在阻塞循环中工作,请查看 threadingsubprocess 模块,了解如何在两个不同的线程/进程之间交换数据。还要确保了解与并行计算相关的问题(即竞争条件、线程安全/同步等)

KeyboardInterrupt is special in that it can be trapped (i.e. SIGINT under operating systems with respective POSIX support, SetConsoleCtrlHandler on Windows) and handled accordingly. If you want to process user input while at the same time doing work in a otherwise blocking loop, have a look at the threading or subprocess modules, taking a look at how to exchange data between two different threads / processes. Also be sure to get an understanding of the issues that go hand in hand with parallel computing (i.e. race conditions, thread safety / synchronization etc.)

甜中书 2024-09-26 12:51:17

我不确定这是否是最佳解决方案,但您可以创建一个执行 while True: sys.stdin.read(1) 的线程,

这样您始终可以读取所有输入,但它会速度要慢,你必须自己组合琴弦。

示例:

import os
import sys
import time
import threading

class StdinReader(threading.Thread):
    def run(self):
        while True:
            print repr(sys.stdin.read(1))

os.system('stty raw')

stdin_reader = StdinReader()
stdin_reader.start()

while True:
    time.sleep(1)

os.system('stty sane')

stty raw 的情况取决于您运行它的位置。它不会在所有地方都有效。

I'm not sure if it's the most optimal solution but you can create a thread that does a while True: sys.stdin.read(1)

That way you can always read all input, but it will be slow and you'll have to combine the strings yourself.

Example:

import os
import sys
import time
import threading

class StdinReader(threading.Thread):
    def run(self):
        while True:
            print repr(sys.stdin.read(1))

os.system('stty raw')

stdin_reader = StdinReader()
stdin_reader.start()

while True:
    time.sleep(1)

os.system('stty sane')

The stty raw thing depends on where you're running it though. It won't work everywhere.

天煞孤星 2024-09-26 12:51:17

编辑:Guido 为我对已接受的答案进行了编辑,因此不再需要这个答案。

由于穆罕默德所做的更改,已接受的答案不再有效。我尝试提交更正,但一直被拒绝,所以我将其作为单独的答案发布。我的代码与他的几乎相同,只有 1 个微小的变化:

import sys
import select
import tty
import termios

from curses import ascii

def isData():
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

old_settings = termios.tcgetattr(sys.stdin)
try:
    tty.setcbreak(sys.stdin.fileno())

    i = 0
    while 1:
        print i
        i += 1

        if isData():
            c = sys.stdin.read(1)
            if c == chr(ascii.ESC):
                break

finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

唯一的区别是我将“c == ascii.ESC”改为“c == chr(ascii.ESC)”。我和其他 1 名开发人员都已经测试过并确认此更改是必要的,否则程序将无法正常工作,

直到您按下 ESC,然后退出,否则程序将显示越来越大的数字。未检测到您的 ESC 按键。

EDIT: Guido made the edit for me on the accepted answer, so this answer is no longer necessary.

The accepted answer is no longer working because of a change Muhammed made. I have tried to submit a correction, but it keeps getting rejected, so I will post it as a separate answer. My code is almost identical to his, just 1 tiny change:

import sys
import select
import tty
import termios

from curses import ascii

def isData():
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

old_settings = termios.tcgetattr(sys.stdin)
try:
    tty.setcbreak(sys.stdin.fileno())

    i = 0
    while 1:
        print i
        i += 1

        if isData():
            c = sys.stdin.read(1)
            if c == chr(ascii.ESC):
                break

finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)

The only difference is instead of "c == ascii.ESC" I changed it to "c == chr(ascii.ESC). I and 1 other developer both have tested and confirmed that this change is necessary and that otherwise the program will not work correctly.

The program is supposed to show bigger and bigger numbers until you hit ESC, and then exit. But without the chr() around the ascii.ESC, it will not detect your ESC keypress.

唱一曲作罢 2024-09-26 12:51:17

这就是我实现我想要的功能的方式,但这并不完全是我的问题所在。因此,如果有人正在寻找相同的概念,但接受更直接的答案之一,我将发布此内容。


while True:
        try:
            time.sleep( 1 )
            #do stuff
        except KeyboardInterrupt:
            inp = raw_input( '>' )
            if inp == 'i':
                obj.integrate()
            elif inp == 'r':
                obj.reset()

This is how I implemented the functionality I wanted, but it's not EXACTLY what my question is about. So I'll post this in case anyone is looking for the same concept, but accept one of the more direct answers.


while True:
        try:
            time.sleep( 1 )
            #do stuff
        except KeyboardInterrupt:
            inp = raw_input( '>' )
            if inp == 'i':
                obj.integrate()
            elif inp == 'r':
                obj.reset()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文