Python 如果我';我没有使用子进程?

Python 如果我';我没有使用子进程?,python,file-descriptor,stderr,wiimote,Python,File Descriptor,Stderr,Wiimote,我正在使用cwiid库,这是一个用C编写的库,但在python中使用。图书馆允许我用Wiimote控制机器人上的一些马达。该代码作为守护进程在没有监视器、键盘或鼠标的嵌入式设备上运行 当我尝试初始化对象时: import cwiid while True: try: wm = cwiid.Wiimote() except RuntimeError: # RuntimeError exception thrown if no Wiimote is

我正在使用cwiid库,这是一个用C编写的库,但在python中使用。图书馆允许我用Wiimote控制机器人上的一些马达。该代码作为守护进程在没有监视器、键盘或鼠标的嵌入式设备上运行

当我尝试初始化对象时:

import cwiid

while True:
    try:
        wm = cwiid.Wiimote()
    except RuntimeError:
        # RuntimeError exception thrown if no Wiimote is trying to connect

        # Wait a second
        time.sleep(1)

        # Try again
        continue
99%的情况下,一切正常,但有时,库会进入某种奇怪的状态,调用
cwiid.Wiimote()
会导致库将“Socket connect error(control channel)”写入stderr,python会抛出异常。发生这种情况时,对
cwiid.Wiimote()
的每次后续调用都会导致将相同的内容写入stderr,并引发相同的异常,直到我重新启动设备

我想做的是检测这个问题,让python自动重启设备

cwiid库在异常状态下引发的异常类型也是
RuntimeError
,这与连接超时异常(非常常见)没有区别,因此我似乎无法以这种方式区分它。我要做的是在运行
cwiid.Wiimote()
后立即读取stderr,查看是否出现消息“套接字连接错误(控制通道)”,如果出现,请重新启动

到目前为止,我可以通过使用一些
os.dup()
os.dup2()
方法重定向stderr以防止消息显示,但这似乎无助于我阅读stderr

如果您正在使用子流程运行某些东西,大多数联机示例都涉及读取stderr,但在本例中不适用

如何读取stderr来检测正在写入的消息

我想我要找的是:

while True:
    try:
        r, w = os.pipe()
        os.dup2(sys.stderr.fileno(), r)

        wm = cwiid.Wiimote()
    except RuntimeError:
        # RuntimeError exception thrown if no Wiimote is trying to connect

        if ('Socket connect error (control channel)' in os.read(r, 100)):
            # Reboot

        # Wait a second
        time.sleep(1)

        # Try again
        continue

这似乎不是我认为应该的工作方式。

作为与stderr抗争的替代方案,在放弃之前,下面的方法可以快速连续重试几次(应该可以处理连接错误):

while True:
    for i in range(50):  # try 50 times
        try:
            wm = cwiid.Wiimote()
            break        # break out of "for" and re-loop in "while"
        except RuntimeError:
            time.sleep(1)
    else:
        raise RuntimeError("permanent Wiimote failure... reboot!")

在后台,
子流程
除了使用DUP之外,还使用匿名管道重定向子流程输出。要让进程读取自己的stderr,需要手动执行此操作。它包括获取匿名管道、将标准错误重定向到管道的输入、运行有问题的stderr编写操作、读取管道另一端的输出以及清理所有备份。这一切都很微妙,但我想我在下面的代码中得到了正确的答案

以下用于
cwiid.Wiimote
调用的包装器将返回一个元组,该元组由函数调用返回的结果(
None
(如果
RuntimeError
)和生成的stderr输出(如果有的话)组成。请参见
测试
功能,了解它在各种条件下的工作原理。我尝试过修改您的示例循环,但不太明白当
cwiid.Wiimote
调用成功时会发生什么。在示例代码中,您只需立即重新循环

编辑:哎呀!修复了
example_loop()
中调用
Wiimote
而不是作为参数传递的错误

import time

import os
import fcntl

def capture_runtime_stderr(action):
    """Handle runtime errors and capture stderr"""
    (r,w) = os.pipe()
    fcntl.fcntl(w, fcntl.F_SETFL, os.O_NONBLOCK)
    saved_stderr = os.dup(2)
    os.dup2(w, 2)
    try:
        result = action()
    except RuntimeError:
        result = None
    finally:
        os.close(w)
        os.dup2(saved_stderr, 2)
        with os.fdopen(r) as o:
            output = o.read()
    return (result, output)

## some tests

def return_value():
    return 5

def return_value_with_stderr():
    os.system("echo >&2 some output")
    return 10

def runtime_error():
    os.system("echo >&2 runtime error occurred")
    raise RuntimeError()

def tests():
    print(capture_runtime_stderr(return_value))
    print(capture_runtime_stderr(return_value_with_stderr))
    print(capture_runtime_stderr(runtime_error))
    os.system("echo >&2 never fear, stderr is back to normal")

## possible code for your loop

def example_loop():
    while True:
        (wm, output) = capture_runtime_stderr(cmiid.Wiimote)
        if wm == None:
            if "Socket connect error" in output:
                raise RuntimeError("library borked, time to reboot")
            time.sleep(1)
            continue
        ## do something with wm??

通常会出现带有异常的错误消息。您可以执行
操作,但RuntimeError除外,如e:
,然后使用
str(e)
读取错误消息,并根据消息做出不同的反应。浏览github上的库代码后,您可能会运气不佳。现在,像您报告的那样的特定连接错误只会打印到
stderr
,然后与消息字符串
“Error opening wiimote connection”
集中在一个
RuntimeError
中。SethMMorton所说的是正确的,但您可能找不到stderr字符串。至少现在还没有。根据最初的问题,截取另一个进程的
stderr
,这可能太多了,但值得检查:祝你好运。好的,我可以告诉你如何按照你想要的方式在进程内重定向stderr,但是你能澄清一下循环中的一些事情吗?例如:你对
wm
做了什么?如果对cwiid.Wiimote()的调用成功,您只需立即重新循环-
while True
中没有
break
。我的程序的其余部分在while循环中,再往下一点。如果我不想缩进整个程序,我可能会重构它,但它对我有效。您发布的解决方案无效,因为您将stderr复制到了错误的管道末端。一旦你解决了这个问题,它将无法工作,因为如果没有任何输出到stderr(例如,成功的Wii调用),你的读取将被阻塞。一旦你解决了这个问题,你会遇到程序神秘地退出而不打印任何错误消息(因为stderr仍然被重定向)等问题。如果你已经阅读了我下面的答案,你可能会想“一定有更简单的方法!”但在这种情况下,可能没有。cwiid.Wiimote()可能会失败数千次。它的工作就是永远失败,直到有人拿起一个wiimote并点击按钮使其连接。这将基本上使机器人每隔x分钟不断重新启动一次,以防它被锁定。理论上可行,但并非最佳解决方案。