From 59c9a645e2f44b0b546225678d704787d9eae35d Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Thu, 13 Sep 2001 05:38:56 +0000 Subject: SF bug [#460467] file objects should be subclassable. Preliminary support. What's here works, but needs fine-tuning. --- Include/fileobject.h | 3 +- Include/moduleobject.h | 1 + Lib/test/test_descr.py | 49 +++++++++++++++++++- Lib/types.py | 7 +-- Misc/NEWS | 5 ++ Objects/fileobject.c | 123 ++++++++++++++++++++++++++++++++++++++++--------- Python/bltinmodule.c | 35 ++++++-------- 7 files changed, 170 insertions(+), 53 deletions(-) diff --git a/Include/fileobject.h b/Include/fileobject.h index a3670c2..94d3591 100644 --- a/Include/fileobject.h +++ b/Include/fileobject.h @@ -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); diff --git a/Include/moduleobject.h b/Include/moduleobject.h index 8c3ba61..533567c 100644 --- a/Include/moduleobject.h +++ b/Include/moduleobject.h @@ -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 *); diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 7ff0ab3..2e6748e 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -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() diff --git a/Lib/types.py b/Lib/types.py index 6c23e24..9419405 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -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: diff --git a/Misc/NEWS b/Misc/NEWS index 518e809..1265ee9 100644 --- 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. diff --git a/Objects/fileobject.c b/Objects/fileobject.c index c8e3ae4..3cadff5 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -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. */ diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 2d8c024..ea9ae63 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -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) -- cgit v0.12