AttributeError: StringIO实例没有'fileno'属性

20 投票
4 回答
24409 浏览
提问于 2025-04-16 17:04
def captureOutput(self, func, *args, **kwargs):
    pass
    sys.stdout.flush()
    sys.stderr.flush()
    (outfd, fn) = tempfile.mkstemp()
    fout = os.fdopen(outfd, 'r')
    os.unlink(fn)
    (errfd, fn) = tempfile.mkstemp()
    ferr = os.fdopen(errfd, 'r')
    os.unlink(fn)
    try:
        oldstdout = os.dup(sys.stdout.fileno())
        oldstderr = os.dup(sys.stderr.fileno())
        os.dup2(outfd, sys.stdout.fileno())
        os.dup2(errfd, sys.stderr.fileno())
        try:
            ret = func(*args, **kwargs)
        finally:
            sys.stderr.flush()
            sys.stdout.flush()
            os.dup2(oldstdout, sys.stdout.fileno())
            os.close(oldstdout)
            os.dup2(oldstderr, sys.stderr.fileno())
            os.close(oldstderr)

        os.lseek(outfd, 0, 0)
        out = fout.read()
        os.lseek(errfd, 0, 0)
        err = ferr.read()
    finally:
        fout.close()
        ferr.close()
    return ret, out, err 

当我运行这段代码时,出现了一个错误:

AttributeError: StringIO instance has no attribute 'fileno'

我为什么会遇到这个错误,我该怎么修正它呢?

4 个回答

2

简单来说,你遇到了标准库中的一个错误。StringIO这个东西没有按照它的基础类IOBase的规定来工作。有些类在使用IOBase类的接口时出现了问题,导致了失败。

更具体一点,Subprocess.run()或者其他某个函数使用了IOBase的fileno函数。而StringIO这个子类因为不是一个真正的子类,所以抛出了这个异常。在某个地方,使用IOBase的用户出现了问题。即使你把StringIO的文档写得再详细,也解决不了这个问题。

你可能可以通过编程来绕过这个问题,或者也可能不行。像contextlib.redirect_stdout()这样的函数都会失败。

16

fileno()这个方法在StringIO中没有实现,因为StringIO并不是真正的文件(所以没有文件描述符)。从源代码来看:

- fileno() is left unimplemented so that code which uses it 
triggers an exception early.

可能有人把sys.stdout替换成了一个StringIO实例,以便捕捉输出。

举个例子,当我这样运行你的代码时,我也遇到了同样的错误:

from StringIO import StringIO
sys.stdout = StringIO()
captureOutput(testfunc)

错误:

    oldstdout = os.dup(sys.stdout.fileno())
AttributeError: StringIO instance has no attribute 'fileno'

最好从头到尾检查一下你的代码,找找哪里可能把sys.stdout给覆盖了。这里有一个链接到我给的另一个回答,里面展示了如何在跟踪模式下执行你的代码:

ares% python -m trace -c -t -C ./coverage test_sio.py | grep sys.stdout
test_sio.py(47): sys.stdout = StringIO()
5

你是在用标准的普通Python解释器吗?这个错误可能会出现在你使用了一个覆盖了标准输出和标准错误输出的解释器,比如IDLE(不过IDLE本身会给你一个不同的错误)。这个问题也可能是因为某个库覆盖了标准输出和标准错误输出。

有时候,你可以通过写 sys.stdout = sys.__stdout__ 来把标准输出重置为默认的标准输出,但不要指望它总是有效。在Pythonwin中就不管用。

无论如何,看起来你想用代码自己重定向标准输出和标准错误输出。如果是这样的话,你就可以直接去做。我觉得如果你有文件描述符 outfderrfd,这样做应该是有效的:

sys.stdout = os.fdopen(outfd, 'w')
sys.stderr = os.fdopen(errfd, 'w')

补充:

现在我能看到你完整的代码了,我觉得根本不需要使用临时文件。

def captureOutput(self, func, *args, **kwargs):
    import cStringIO # You can also use StringIO instead

    sys.stderr.flush()
    sys.stdout.flush()
    olderr, oldout = sys.stderr, sys.stdout
    try:
        sys.stderr = cStringIO.StringIO()
        sys.stdout = cStringIO.StringIO()
        try:
            ret = func(*args, **kwargs)
        finally:
            stderr.seek(0)
            stdout.seek(0)            
            err = stderr.read()
            out = stdout.read()
    finally:
        sys.stderr = olderr
        sys.stdout = oldout

    return ret, out, err

撰写回答