很好的问题。
发生这种情况是因为 Python 优化了对write
on file
绕过Python级别的对象write
方法及调用fputs
直接地。
要查看此操作的实际效果,请考虑:
$ 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
方法从未被调用!
现在,当该对象不是 的子类时file
,事情按预期进行:
$ 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!
仔细研究一下Python源代码,看起来罪魁祸首是PyFile_WriteString
函数,由print
语句,它检查正在写入的对象是否是以下对象的实例file
,如果是,则绕过对象的方法并调用fputs
直接地:
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;
}