gpt4 book ai didi

python - 在 Python cmd 模块中处理 CTRL-C

转载 作者:行者123 更新时间:2023-12-02 03:35:54 24 4
gpt4 key购买 nike

我使用 cmd 模块编写了一个 Python 3.5 应用程序。我想要实现的最后一件事是正确处理 CTRL-C(sigint)信号。我希望它的行为或多或少像 Bash 那样:

  • 在光标所在处打印 ^C
  • 清除缓冲区以便删除输入文本
  • 跳到下一行,打印提示,然后等待输入

基本上:

/test $ bla bla bla|
# user types CTRL-C
/test $ bla bla bla^C
/test $

以下是作为可运行示例的简化代码:

import cmd
import signal


class TestShell(cmd.Cmd):
def __init__(self):
super().__init__()

self.prompt = '$ '

signal.signal(signal.SIGINT, handler=self._ctrl_c_handler)
self._interrupted = False

def _ctrl_c_handler(self, signal, frame):
print('^C')
self._interrupted = True

def precmd(self, line):
if self._interrupted:
self._interrupted = False
return ''

if line == 'EOF':
return 'exit'

return line

def emptyline(self):
pass

def do_exit(self, line):
return True


TestShell().cmdloop()

这几乎可以工作了。当我按 CTRL-C 时,^C 会打印在光标处,但我仍然必须按 Enter 键。然后,precmd 方法注意到处理程序设置的 self._interrupted 标志,并返回一个空行。这是我所能接受的,但我想以某种方式不必按该输入键。

我想我需要以某种方式强制 input() 返回,有人有想法吗?

最佳答案

我发现了一些奇怪的方法来使用 Ctrl-C 实现您想要的行为。

设置use_rawinput=False并替换stdin

这个(或多或少......)坚持cmd.Cmd的公共(public)接口(interface)。不幸的是,它禁用了 readline 支持。

您可以将 use_rawinput 设置为 false 并传递不同的类文件对象来替换 Cmd.__init__() 中的 stdin。实际上,只有 readline() 在此对象上被调用。因此,您可以为 stdin 创建一个包装器来捕获 KeyboardInterrupt 并执行您想要的行为:

class _Wrapper:

def __init__(self, fd):
self.fd = fd

def readline(self, *args):
try:
return self.fd.readline(*args)
except KeyboardInterrupt:
print()
return '\n'


class TestShell(cmd.Cmd):

def __init__(self):
super().__init__(stdin=_Wrapper(sys.stdin))
self.use_rawinput = False
self.prompt = '$ '

def precmd(self, line):
if line == 'EOF':
return 'exit'
return line

def emptyline(self):
pass

def do_exit(self, line):
return True


TestShell().cmdloop()

当我在终端上运行此命令时,Ctrl-C 显示 ^C 并切换到新行。

猴子补丁input()

如果您想要 input() 的结果,但您希望 Ctrl-C 具有不同的行为,一种方法是使用不同的函数而不是 input() :

def my_input(*args):   # input() takes either no args or one non-keyword arg
try:
return input(*args)
except KeyboardInterrupt:
print('^C') # on my system, input() doesn't show the ^C
return '\n'

但是,如果你只是盲目地设置 input = my_input,你会得到无限递归,因为 my_input() 会调用 input(),这现在是它自己。但这是可以修复的,您可以修补 cmd 模块中的 __builtins__ 字典,以在 Cmd 期间使用修改后的 input() 方法。 cmdloop():

def input_swallowing_interrupt(_input):
def _input_swallowing_interrupt(*args):
try:
return _input(*args)
except KeyboardInterrupt:
print('^C')
return '\n'
return _input_swallowing_interrupt


class TestShell(cmd.Cmd):

def cmdloop(self, *args, **kwargs):
old_input_fn = cmd.__builtins__['input']
cmd.__builtins__['input'] = input_swallowing_interrupt(old_input_fn)
try:
super().cmdloop(*args, **kwargs)
finally:
cmd.__builtins__['input'] = old_input_fn

# ...

请注意,这会更改所有Cmd对象input(),而不仅仅是TestShell对象。如果您不能接受,您可以...

复制Cmd.cmdloop()源并修改它

由于您要对其进行子类化,因此您可以使 cmdloop() 执行您想要的任何操作。 “任何你想要的”可能包括复制 Cmd.cmdloop() 的部分内容并重写其他部分。要么用对另一个函数的调用替换对 input() 的调用,要么在重写的 cmdloop() 中捕获并处理 KeyboardInterrupt

如果您担心新版本的 Python 会改变底层实现,您可以将整个 cmd 模块复制到新模块中,然后更改您想要的内容。

关于python - 在 Python cmd 模块中处理 CTRL-C,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37378185/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com