可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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
When running this code, I get an error:
AttributeError: StringIO instance has no attribute 'fileno'
Why am I getting this error and how can I correct it?
回答1:
Are you using the standard plain python interpreter? This error may appear when you use an interpreter that overrides stdout/stderr, such as IDLE (though IDLE itself would give you a different error). It may also be caused by a library which overrides stdout/stderr.
Sometimes you can reset your stdout the the default stdout by writing sys.stdout = sys.__stdout__
, but don't count on it working always. It doesn't work in Pythonwin for instance.
Anyway, it seems that what you're trying to do with your code is to redirect stdout/stderr yourself. If that's the case, you should just go ahead and do it. I think this should work, if you have file descriptors outfd
and errfd
:
sys.stdout = os.fdopen(outfd, 'w') sys.stderr = os.fdopen(errfd, 'w')
Edit:
Now that I can see your entire code, I wouldn't use temporary files at all.
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
回答2:
The fileno()
method is not implemented in StringIO, as it is not a real file (so has no associated file descriptor). From the source:
- fileno() is left unimplemented so that code which uses it triggers an exception early.
It is possible that someone replaced sys.stdout
with a StringIO instance, to capture output.
For example, when I run your code this way I get the same exception:
from StringIO import StringIO sys.stdout = StringIO() captureOutput(testfunc)
Error:
oldstdout = os.dup(sys.stdout.fileno()) AttributeError: StringIO instance has no attribute 'fileno'
It might be best to trace your code from end to end, looking for points where sys.stdout
is being overwritten. Here's a link to another answer I gave, showing how to execute your code with tracing active:
ares% python -m trace -c -t -C ./coverage test_sio.py | grep sys.stdout test_sio.py(47): sys.stdout = StringIO()
回答3:
My guess would be somewhere else in the code, sys.stdout or sys.stderr was reassigned to be an instance of StringIO. What environment (like inside some web framework, from command line) is this code running under? That might give someone familiar with that environment a clue as to the proper answer.
回答4:
The short answer is that you ran across a bug in standard library. StringIO does not fulfill the contract of its IOBase base class. Some class wrote to the IOBase class interface, which then fails.
More specifically, Subprocess.run() or some other function used the IOBase fileno function. The subclass StringIO throws this exception because it is not a true subclass. Somewhere, one of the many users of IOBase fails. Documenting StringIO does not help this problem.
You can code around it, maybe. Or maybe not. All sorts of functions like contextlib.redirect_stdout() will fail.