summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Peters <tim.peters@gmail.com>2001-09-13 05:38:56 (GMT)
committerTim Peters <tim.peters@gmail.com>2001-09-13 05:38:56 (GMT)
commit59c9a645e2f44b0b546225678d704787d9eae35d (patch)
tree56179a5250e747d330a93b4f5fe66eeb65859329
parent93a696f49171c0593b23817bbf56ab44269a9587 (diff)
downloadcpython-59c9a645e2f44b0b546225678d704787d9eae35d.zip
cpython-59c9a645e2f44b0b546225678d704787d9eae35d.tar.gz
cpython-59c9a645e2f44b0b546225678d704787d9eae35d.tar.bz2
SF bug [#460467] file objects should be subclassable.
Preliminary support. What's here works, but needs fine-tuning.
-rw-r--r--Include/fileobject.h3
-rw-r--r--Include/moduleobject.h1
-rw-r--r--Lib/test/test_descr.py49
-rw-r--r--Lib/types.py7
-rw-r--r--Misc/NEWS5
-rw-r--r--Objects/fileobject.c123
-rw-r--r--Python/bltinmodule.c35
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)