views:

52

answers:

3

I have a program that uses curses, and then returns to the main script for further processing. After it returns, my subsequent output to stdout does not appear until there's a large amount of it (e.g. thousands of bytes).

I've reduced the problem to a very simple program that fails reliably:

import curses
import time

curses.initscr()
curses.endwin()

print "Hello world!"
time.sleep(5)

If I comment out the two curses calls, the 'Hello world!' prints before the delay. If I put them in, it prints after the delay (when the script exits).

+2  A: 

The curses.endwin() call "sets standard I/O back to normal"... which unfortunately means "buffered" (you could consider that a bug in curses and file the bug on Python's tracker).

To ensure standard-output is unbuffered,

import os

sys.stdout = os.fdopen(0, 'w', 0)

(You can put the import os at the start, or course; the reassignment of sys.stdout is meant to go right after the endwin call).

Alex Martelli
So does stdout begin life as unbuffered? I thought that's what the -u flag was for. But yes, this fix works, thanks.
Neil Baylis
A: 

I have found that tis is system/library dependent. On my Windows machine (curses from http://www.lfd.uci.edu/~gohlke/pythonlibs/) it flushes and then sleeps, but on Linux it flushes after sleep. I think you can simply use: sys.stdout.flush() like:

print "Hello world!"
sys.stdout.flush()
time.sleep(5)

(it may be better to make some kind of "print & flush" function)

Michał Niklas
+1  A: 

Alex beat me to it (he knew, I had to figure it out) but I wrote this nifty context manager that you might be able to use.

import curses
import time
import os
import sys


class CursesStdout(object):
    def __enter__(self):
        pass

    def __exit__(self, *args):
        sys.stdout = sys.__stdout__ = os.fdopen(sys.__stdout__.fileno(), 'w', 0)

with CursesStdout():
    curses.initscr()
    curses.endwin()


print "Hello world!"
time.sleep(2)

I modify sys.__stdout__ directly because when you look at the source in curses/__init__.py you see that the initscr function passes that file descriptor to the C code and so I store that to be reset to normal.

aaronasterling
This is nice too, but I can't use it because this project is on python 2.4. Thanks, though.
Neil Baylis