SF bug [#460467] file objects should be subclassable.

Preliminary support.  What's here works, but needs fine-tuning.
This commit is contained in:
Tim Peters 2001-09-13 05:38:56 +00:00
parent 93a696f491
commit 59c9a645e2
7 changed files with 170 additions and 53 deletions

View 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);

View 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 *);

View 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()

View 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:

View File

@ -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.

View 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. */

View 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)