]> granicus.if.org Git - python/commitdiff
SF bug [#460467] file objects should be subclassable.
authorTim Peters <tim.peters@gmail.com>
Thu, 13 Sep 2001 05:38:56 +0000 (05:38 +0000)
committerTim Peters <tim.peters@gmail.com>
Thu, 13 Sep 2001 05:38:56 +0000 (05:38 +0000)
Preliminary support.  What's here works, but needs fine-tuning.

Include/fileobject.h
Include/moduleobject.h
Lib/test/test_descr.py
Lib/types.py
Misc/NEWS
Objects/fileobject.c
Python/bltinmodule.c

index a3670c2f6a0c6945b2d46f4623ad5e6b26c35f62..94d35918c5fa6bc9b9fe8584889bfdeb8f1a2feb 100644 (file)
@@ -9,7 +9,8 @@ extern "C" {
 
 extern DL_IMPORT(PyTypeObject) PyFile_Type;
 
-#define PyFile_Check(op) ((op)->ob_type == &PyFile_Type)
+#define PyFile_Check(op) PyObject_TypeCheck(op, &PyFile_Type)
+#define PyFile_CheckExact(op) ((op)->ob_type == &PyFile_Type)
 
 extern DL_IMPORT(PyObject *) PyFile_FromString(char *, char *);
 extern DL_IMPORT(void) PyFile_SetBufSize(PyObject *, int);
index 8c3ba61227c90118cbcfb02df6d8042b17b6dcc3..533567c3dacb4df17c65b7ad4013a3d344783efc 100644 (file)
@@ -10,6 +10,7 @@ extern "C" {
 extern DL_IMPORT(PyTypeObject) PyModule_Type;
 
 #define PyModule_Check(op) PyObject_TypeCheck(op, &PyModule_Type)
+#define PyModule_CheckExact(op) ((op)->ob_type == &PyModule_Type)
 
 extern DL_IMPORT(PyObject *) PyModule_New(char *);
 extern DL_IMPORT(PyObject *) PyModule_GetDict(PyObject *);
index 7ff0ab3beefba4b4e7f9d36f865b62af25955620..2e6748e34c9ddd984c60bb770c92015b1835639f 100644 (file)
@@ -1,6 +1,6 @@
 # Test descriptor-related enhancements
 
-from test_support import verify, verbose, TestFailed
+from test_support import verify, verbose, TestFailed, TESTFN
 from copy import deepcopy
 
 def testunop(a, res, expr="len(a)", meth="__len__"):
@@ -1636,6 +1636,53 @@ def inherits():
     verify(u[0:0].__class__ is unicode)
     verify(u[0:0] == u"")
 
+    class CountedInput(file):
+        """Counts lines read by self.readline().
+
+        self.lineno is the 0-based ordinal of the last line read, up to
+        a maximum of one greater than the number of lines in the file.
+
+        self.ateof is true if and only if the final "" line has been read,
+        at which point self.lineno stops incrementing, and further calls
+        to readline() continue to return "".
+        """
+
+        lineno = 0
+        ateof = 0
+        def readline(self):
+            if self.ateof:
+                return ""
+            s = file.readline(self)
+            # Next line works too.
+            # s = super(CountedInput, self).readline()
+            self.lineno += 1
+            if s == "":
+                self.ateof = 1
+            return s
+
+    f = open(TESTFN, 'w')
+    lines = ['a\n', 'b\n', 'c\n']
+    try:
+        f.writelines(lines)
+        f.close()
+        f = CountedInput(TESTFN)
+        for (i, expected) in zip(range(1, 5) + [4], lines + 2 * [""]):
+            got = f.readline()
+            verify(expected == got)
+            verify(f.lineno == i)
+            verify(f.ateof == (i > len(lines)))
+        f.close()
+    finally:
+        try:
+            f.close()
+        except:
+            pass
+        try:
+            import os
+            os.unlink(TESTFN)
+        except:
+            pass
+
 def all():
     lists()
     dicts()
index 6c23e244e2059a7e7fd8b15561a81f51bb5a8ab2..941940531b313175ff07aff348f68ea135444f65 100644 (file)
@@ -57,12 +57,7 @@ BuiltinFunctionType = type(len)
 BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
 
 ModuleType = type(sys)
-
-try:
-    FileType = type(sys.__stdin__)
-except AttributeError:
-    # Not available in restricted mode
-    pass
+FileType = file
 XRangeType = type(xrange(0))
 
 try:
index 518e809e3c6e315e6b13f60a43f2015c2f3e7918..1265ee986e120f089d8025e09298aa5cb481c03b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -3,6 +3,11 @@ What's New in Python 2.2a4?
 
 Core
 
+- The builtin file type can be subclassed now.  In the usual pattern,
+  "file" is the name of the builtin type, and file() is a new builtin
+  constructor, with the same signature as the builtin open() function.
+  file() is now the preferred way to open a file.
+
 - In 2.2a3, hash() applied to an instance of a subclass of str or unicode
   always returned 0.  This has been repaired.
 
index c8e3ae4ad8051b01a43727423f416be5c40c5fc5..3cadff51568a83443905f6f0a4af74db66b1796d 100644 (file)
@@ -65,37 +65,33 @@ PyFile_Name(PyObject *f)
                return ((PyFileObject *)f)->f_name;
 }
 
-PyObject *
-PyFile_FromFile(FILE *fp, char *name, char *mode, int (*close)(FILE *))
+
+static PyObject *
+fill_file_fields(PyFileObject *f, FILE *fp, char *name, char *mode,
+                int (*close)(FILE *))
 {
-       PyFileObject *f = PyObject_NEW(PyFileObject, &PyFile_Type);
-       if (f == NULL)
-               return NULL;
+       assert(f != NULL);
+       assert(PyFile_Check(f));
        f->f_fp = NULL;
        f->f_name = PyString_FromString(name);
        f->f_mode = PyString_FromString(mode);
        f->f_close = close;
        f->f_softspace = 0;
-       if (strchr(mode,'b') != NULL)
-           f->f_binary = 1;
-       else
-           f->f_binary = 0;
-       if (f->f_name == NULL || f->f_mode == NULL) {
-               Py_DECREF(f);
+       f->f_binary = strchr(mode,'b') != NULL;
+       if (f->f_name == NULL || f->f_mode == NULL)
                return NULL;
-       }
        f->f_fp = fp;
        return (PyObject *) f;
 }
 
-PyObject *
-PyFile_FromString(char *name, char *mode)
+static PyObject *
+open_the_file(PyFileObject *f, char *name, char *mode)
 {
-       extern int fclose(FILE *);
-       PyFileObject *f;
-       f = (PyFileObject *) PyFile_FromFile((FILE *)NULL, name, mode, fclose);
-       if (f == NULL)
-               return NULL;
+       assert(f != NULL);
+       assert(PyFile_Check(f));
+       assert(name != NULL);
+       assert(mode != NULL);
+
 #ifdef HAVE_FOPENRF
        if (*mode == '*') {
                FILE *fopenRF();
@@ -118,8 +114,36 @@ PyFile_FromString(char *name, char *mode)
                }
 #endif
                PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
-               Py_DECREF(f);
-               return NULL;
+               f = NULL;
+       }
+       return (PyObject *)f;
+}
+
+PyObject *
+PyFile_FromFile(FILE *fp, char *name, char *mode, int (*close)(FILE *))
+{
+       PyFileObject *f = PyObject_NEW(PyFileObject, &PyFile_Type);
+       if (f != NULL) {
+               if (fill_file_fields(f, fp, name, mode, close) == NULL) {
+                       Py_DECREF(f);
+                       f = NULL;
+               }
+       }
+       return (PyObject *) f;
+}
+
+PyObject *
+PyFile_FromString(char *name, char *mode)
+{
+       extern int fclose(FILE *);
+       PyFileObject *f;
+
+       f = (PyFileObject *)PyFile_FromFile((FILE *)NULL, name, mode, fclose);
+       if (f != NULL) {
+               if (open_the_file(f, name, mode) == NULL) {
+                       Py_DECREF(f);
+                       f = NULL;
+               }
        }
        return (PyObject *)f;
 }
@@ -1293,6 +1317,52 @@ file_getiter(PyObject *f)
        return PyObject_CallMethod(f, "xreadlines", "");
 }
 
+static PyObject *
+file_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+       /* XXX As for all XXX_new functions, file_new is called
+          with kwds=NULL by type_call(), so the kwlist is impotent. */
+       static char *kwlist[] = {"name", "mode", "buffering", 0};
+       char *name = NULL;
+       char *mode = "r";
+       int bufsize = -1;
+       PyObject *f;
+       extern int fclose(FILE *);
+
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|si:file", kwlist,
+                                        Py_FileSystemDefaultEncoding, &name,
+                                        &mode, &bufsize))
+               return NULL;
+       f = PyType_GenericAlloc(type, 0);
+       if (f != NULL) {
+               PyFileObject *g = (PyFileObject *)f;
+               if (fill_file_fields(g, NULL, name, mode, fclose) == NULL) {
+                       Py_DECREF(f);
+                       f = NULL;
+               }
+               if (f != NULL && open_the_file(g, name, mode) == NULL) {
+                       Py_DECREF(f);
+                       f = NULL;
+               }
+               if (f != NULL)
+                       PyFile_SetBufSize(f, bufsize);
+       }
+       PyMem_Free(name); /* free the encoded string */
+       return f;
+}
+
+/* XXX Keep this in synch with open_doc in bltinmodule.c. */
+static char file_doc[] =
+"file(name[, mode[, buffering]]) -> file object\n"
+"\n"
+"Open a file.  The mode can be 'r', 'w' or 'a' for reading (default),\n"
+"writing or appending.  The file will be created if it doesn't exist\n"
+"when opened for writing or appending; it will be truncated when\n"
+"opened for writing.  Add a 'b' to the mode for binary files.\n"
+"Add a '+' to the mode to allow simultaneous reading and writing.\n"
+"If the buffering argument is given, 0 means unbuffered, 1 means line\n"
+"buffered, and larger numbers specify the buffer size.";
+
 PyTypeObject PyFile_Type = {
        PyObject_HEAD_INIT(&PyType_Type)
        0,
@@ -1314,8 +1384,9 @@ PyTypeObject PyFile_Type = {
        PyObject_GenericGetAttr,                /* tp_getattro */
        0,                                      /* tp_setattro */
        0,                                      /* tp_as_buffer */
-       Py_TPFLAGS_DEFAULT,                     /* tp_flags */
-       0,                                      /* tp_doc */
+       Py_TPFLAGS_DEFAULT |
+                       Py_TPFLAGS_BASETYPE,    /* tp_flags */
+       file_doc,                               /* tp_doc */
        0,                                      /* tp_traverse */
        0,                                      /* tp_clear */
        0,                                      /* tp_richcompare */
@@ -1327,6 +1398,12 @@ PyTypeObject PyFile_Type = {
        file_getsetlist,                        /* tp_getset */
        0,                                      /* tp_base */
        0,                                      /* tp_dict */
+       0,                                      /* tp_descr_get */
+       0,                                      /* tp_descr_set */
+       0,                                      /* tp_dictoffset */
+       0,                                      /* tp_init */
+       0,                                      /* tp_alloc */
+       file_new,                               /* tp_new */
 };
 
 /* Interface for the 'soft space' between print items. */
index 2d8c024a58a3ea3ae509c50a9dfce0ec2575a5cd..ea9ae63477d72f9ea4c33177406cb97060eab24b 100644 (file)
@@ -1192,31 +1192,20 @@ Return the octal representation of an integer or long integer.";
 static PyObject *
 builtin_open(PyObject *self, PyObject *args)
 {
-       char *name = NULL;
-       char *mode = "r";
-       int bufsize = -1;
-       PyObject *f;
-
-       if (!PyArg_ParseTuple(args, "et|si:open", Py_FileSystemDefaultEncoding, 
-                             &name, &mode, &bufsize))
-               return NULL;
-       f = PyFile_FromString(name, mode);
-       PyMem_Free(name); /* free the encoded string */
-       if (f != NULL)
-               PyFile_SetBufSize(f, bufsize);
-       return f;
+       return PyFile_Type.tp_new(&PyFile_Type, args, NULL);
 }
 
+/* XXX Keep this in synch with file_doc in fileobject.c. */
 static char open_doc[] =
-"open(filename[, mode[, buffering]]) -> file object\n\
-\n\
-Open a file.  The mode can be 'r', 'w' or 'a' for reading (default),\n\
-writing or appending.  The file will be created if it doesn't exist\n\
-when opened for writing or appending; it will be truncated when\n\
-opened for writing.  Add a 'b' to the mode for binary files.\n\
-Add a '+' to the mode to allow simultaneous reading and writing.\n\
-If the buffering argument is given, 0 means unbuffered, 1 means line\n\
-buffered, and larger numbers specify the buffer size.";
+"open(name[, mode[, buffering]]) -> file object\n"
+"\n"
+"Open a file.  The mode can be 'r', 'w' or 'a' for reading (default),\n"
+"writing or appending.  The file will be created if it doesn't exist\n"
+"when opened for writing or appending; it will be truncated when\n"
+"opened for writing.  Add a 'b' to the mode for binary files.\n"
+"Add a '+' to the mode to allow simultaneous reading and writing.\n"
+"If the buffering argument is given, 0 means unbuffered, 1 means line\n"
+"buffered, and larger numbers specify the buffer size.";
 
 
 static PyObject *
@@ -1894,6 +1883,8 @@ _PyBuiltin_Init(void)
                return NULL;
        if (PyDict_SetItemString(dict, "type", (PyObject *) &PyType_Type) < 0)
                return NULL;
+       if (PyDict_SetItemString(dict, "file", (PyObject *) &PyFile_Type) < 0)
+               return NULL;
 #ifdef Py_USING_UNICODE
        if (PyDict_SetItemString(dict, "unicode",
                                 (PyObject *) &PyUnicode_Type) < 0)