os.close(0) 与 sys.stdin.close() 的区别

4 投票
1 回答
8090 浏览
提问于 2025-04-18 09:29

我正在写一些Python代码,这段代码是一个通过Apache调用的CGI脚本。

这段代码的第一步是(我认为)尝试关闭标准输入、标准输出和标准错误,具体代码如下:

    for fd in [0, 1, 2]:
    try:
        os.close(fd)
    except Exception:
        pass

通常情况下,这样做是没问题的,不过如果这些输入输出没有打开,我就会遇到“python.exe已停止工作”,“程序出现问题,无法正常工作”的错误提示(这是Win32异常)。

我有几个问题:

  • 通过os.close(描述符编号)和sys.stdin.close()等关闭的区别是什么?
  • 假设我应该同时使用这两种方式来关闭,那我怎么检查描述符实际上是打开的(也就是说,调用os.close不会导致Python崩溃)?

1 个回答

8

我不太确定,但我猜 os.close() 只是 Python 对操作系统的 close() 系统调用的一个封装。Python 的文件对象为用户处理了一些很酷的事情,比如内部的数据缓冲等,而这些在调用它的 close() 方法时就会处理好。

最终,操作系统的 close() 系统调用会在文件对象的文件描述符上被调用。所以,某个时候调用 sys.stdin.close() 相当于调用 os.close(sys.stdin.fileno())

根据文档,你可以多次调用文件的 close() 方法,Python 不会在意。文件对象甚至提供了一个属性 closed 来检查文件是否打开:

>>> import os

>>> f = open(os.devnull)
>>> f.close()
>>> f.close()
>>> f.close()
>>> f.close()
>>> f.close()
>>> print f.closed
True

如果可以的话,我建议调用 sys.FD 的 close() 方法,因为这样更简洁,更符合 Python 的风格。

更新

在查看了 Python 的源代码后,我发现了关于文件对象的一些内容 (fileobjects.c):

static PyObject *
file_close(PyFileObject *f)
{
    PyObject *sts = close_the_file(f);
    if (sts) {
        PyMem_Free(f->f_setbuf);
        f->f_setbuf = NULL;
    }
    return sts;
}

PyDoc_STRVAR(close_doc,
"close() -> None or (perhaps) an integer.  Close the file.\n"
"\n"
"Sets data attribute .closed to True.  A closed file cannot be used for\n"
"further I/O operations.  close() may be called more than once without\n"
"error.  Some kinds of file objects (for example, opened by popen())\n"
"may return an exit status upon closing.");

在 close_the_file(f); 内部:

static PyObject *
close_the_file(PyFileObject *f)
{
    int sts = 0;
    int (*local_close)(FILE *);
    FILE *local_fp = f->f_fp;
    char *local_setbuf = f->f_setbuf;
    if (local_fp != NULL) {
        local_close = f->f_close; // get fs close() method

        /* SOME CONCURRENCY STUFF HERE... */

        f->f_fp = NULL;
        if (local_close != NULL) {
            f->f_setbuf = NULL;
            Py_BEGIN_ALLOW_THREADS
            errno = 0;
            sts = (*local_close)(local_fp); // Call the close()
            Py_END_ALLOW_THREADS
            f->f_setbuf = local_setbuf;
            if (sts == EOF)
                return PyErr_SetFromErrno(PyExc_IOError);
            if (sts != 0)
                return PyInt_FromLong((long)sts);
        }
    }
    Py_RETURN_NONE;
}

文件的 close() 方法是什么?

static PyObject *
fill_file_fields(PyFileObject *f, FILE *fp, PyObject *name, char *mode,
                 int (*close)(FILE *))
{
    ...
    f->f_close = close;
    ...
}

对于 os 模块(使用 posixmodule.c):

/* This file is also used for Windows NT/MS-Win and OS/2.  In that case the
   module actually calls itself 'nt' or 'os2', not 'posix', and a few
   functions are either unimplemented or implemented differently.  The source
   assumes that for Windows NT, the macro 'MS_WINDOWS' is defined independent
   of the compiler used.  Different compilers define their own feature
   test macro, e.g. '__BORLANDC__' or '_MSC_VER'.  For OS/2, the compiler
   independent macro PYOS_OS2 should be defined.  On OS/2 the default
   compiler is assumed to be IBMs VisualAge C++ (VACPP).  PYCC_GCC is used
   as the compiler specific macro for the EMX port of gcc to OS/2. */

PyDoc_STRVAR(posix_close__doc__,
"close(fd)\n\n\
Close a file descriptor (for low level IO).");

/*
The underscore at end of function name avoids a name clash with the libc
function posix_close.
*/
static PyObject *
posix_close_(PyObject *self, PyObject *args)
{
    int fd, res;
    if (!PyArg_ParseTuple(args, "i:close", &fd))
        return NULL;
    if (!_PyVerify_fd(fd))
        return posix_error();
    Py_BEGIN_ALLOW_THREADS
    res = close(fd);  // close the file descriptor fd
    Py_END_ALLOW_THREADS
    if (res < 0)
        return posix_error(); // AKA raise OSError()
    Py_INCREF(Py_None);
    return Py_None;
}

如果文件描述符已经被关闭,那么会抛出一个 OSError 错误:

static PyObject *
posix_error(void)
{
    return PyErr_SetFromErrno(PyExc_OSError);
}

所以,如果在同一个文件描述符上调用 os.close(fd) 两次,应该会抛出一个 OSError 错误。调用 file.close() 最终会调用 fclose(FILE *f),并且会处理多次调用的情况。

撰写回答