Great question.
This is because Python optimizes calls to write on file objects by traversing the write method at the Python level and calling fputs directly.
To see this in action, consider:
$ cat file_subclass.py import sys class FileSubclass(file): def write(self, *a, **kw): raise Exception("write called!") writelines = write sys.stdout = FileSubclass("/dev/null", "w") print "foo" sys.stderr.write("print succeeded!\n") $ python print_magic.py print succeeded!
The write method has never been called!
Now that the object is not a subclass of file , everything works as expected:
$ cat object_subclass.py import sys class ObjectSubclass(object): def __init__(self): pass def write(self, *a, **kw): raise Exception("write called!") writelines = write sys.stdout = ObjectSubclass() print "foo" sys.stderr.write("print succeeded!\n") $ python object_subclass.py Traceback (most recent call last): File "x.py", line 13, in <module> print "foo" File "x.py", line 8, in write raise Exception("write called!") Exception: write called!
Digging through the Python source a little, it seems that the culprit is the PyFile_WriteString function, called by the print statement, which checks if the object being written is an instance of file , and if so, it bypasses the methods of the object and calls fputs directly:
int PyFile_WriteString(const char *s, PyObject *f) { if (f == NULL) { /* … snip … */ } else if (PyFile_Check(f)) { //-- `isinstance(f, file)` PyFileObject *fobj = (PyFileObject *) f; FILE *fp = PyFile_AsFile(f); if (fp == NULL) { err_closed(); return -1; } FILE_BEGIN_ALLOW_THREADS(fobj) fputs(s, fp); //-- fputs, bypassing the Python object entirely FILE_END_ALLOW_THREADS(fobj) return 0; } else if (!PyErr_Occurred()) { PyObject *v = PyString_FromString(s); int err; if (v == NULL) return -1; err = PyFile_WriteObject(v, f, Py_PRINT_RAW); Py_DECREF(v); return err; } else return -1; }