SF bug [#460467] file objects should be subclassable.
Preliminary support. What's here works, but needs fine-tuning.
This commit is contained in:
parent
93a696f491
commit
59c9a645e2
|
@ -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);
|
||||
|
|
|
@ -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 *);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue