Implement long positioning (Unix only, probably).

Etc., etc.
This commit is contained in:
Guido van Rossum 2007-04-10 19:01:47 +00:00
parent cce92b27d6
commit 53807dabf0
3 changed files with 210 additions and 179 deletions

View File

@ -1,9 +1,15 @@
"""New I/O library.
"""New I/O library conforming to PEP 3116.
This is an early prototype; eventually some of this will be
reimplemented in C and the rest may be turned into a package.
See PEP 3116.
Conformance of alternative implementations: all arguments are intended
to be positional-only except the arguments of the open() function.
Argument names except those of the open() function are not part of the
specification. Instance variables and methods whose name starts with
a leading underscore are not part of the specification (except "magic"
names like __iter__). Only the top-level names listed in the __all__
variable are part of the specification.
XXX need to default buffer size to 1 if isatty()
XXX need to support 1 meaning line-buffered
@ -142,6 +148,9 @@ class IOBase:
This does not define read(), readinto() and write(), nor
readline() and friends, since their signatures vary per layer.
Not that calling any method (even inquiries) on a closed file is
undefined. Implementations may raise IOError in this case.
"""
### Internal ###
@ -153,19 +162,20 @@ class IOBase:
### Positioning ###
def seek(self, pos: int, whence: int = 0) -> None:
"""seek(pos: int, whence: int = 0) -> None. Change stream position.
def seek(self, pos: int, whence: int = 0) -> int:
"""seek(pos: int, whence: int = 0) -> int. Change stream position.
Seek to byte offset pos relative to position indicated by whence:
0 Start of stream (the default). pos should be >= 0;
1 Current position - whence may be negative;
2 End of stream - whence usually negative.
Returns the new absolute position.
"""
self._unsupported("seek")
def tell(self) -> int:
"""tell() -> int. Return current stream position."""
self._unsupported("tell")
return self.seek(0, 1)
def truncate(self, pos: int = None) -> None:
"""truncate(size: int = None) -> None. Truncate file to size bytes.
@ -432,7 +442,7 @@ class _BufferedIOMixin(BufferedIOBase):
### Positioning ###
def seek(self, pos, whence=0):
self.raw.seek(pos, whence)
return self.raw.seek(pos, whence)
def tell(self):
return self.raw.tell()
@ -515,6 +525,7 @@ class _MemoryIOMixin(BufferedIOBase):
self._pos = max(0, len(self._buffer) + pos)
else:
raise IOError("invalid whence value")
return self._pos
def tell(self):
return self._pos
@ -620,8 +631,9 @@ class BufferedReader(_BufferedIOMixin):
def seek(self, pos, whence=0):
if whence == 1:
pos -= len(self._read_buf)
self.raw.seek(pos, whence)
pos = self.raw.seek(pos, whence)
self._read_buf = b""
return pos
class BufferedWriter(_BufferedIOMixin):
@ -679,7 +691,7 @@ class BufferedWriter(_BufferedIOMixin):
def seek(self, pos, whence=0):
self.flush()
self.raw.seek(pos, whence)
return self.raw.seek(pos, whence)
class BufferedRWPair(BufferedIOBase):
@ -750,13 +762,9 @@ class BufferedRandom(BufferedWriter, BufferedReader):
self.flush()
# First do the raw seek, then empty the read buffer, so that
# if the raw seek fails, we don't lose buffered data forever.
self.raw.seek(pos, whence)
pos = self.raw.seek(pos, whence)
self._read_buf = b""
# XXX I suppose we could implement some magic here to move through the
# existing read buffer in the case of seek(<some small +ve number>, 1)
# XXX OTOH it might be good to *guarantee* that the buffer is
# empty after a seek or flush; for small relative forward
# seeks one might as well use small reads instead.
return pos
def tell(self):
if (self._write_buf):

View File

@ -4,10 +4,10 @@ import unittest
from itertools import chain
from test import test_support
import io
import io # The module under test
class MockIO(io.RawIOBase):
class MockRawIO(io.RawIOBase):
def __init__(self, read_stack=()):
self._read_stack = list(read_stack)
@ -56,13 +56,13 @@ class MockFileIO(io.BytesIO):
class MockNonBlockWriterIO(io.RawIOBase):
def __init__(self, blockingScript):
self.bs = list(blockingScript)
def __init__(self, blocking_script):
self._blocking_script = list(blocking_script)
self._write_stack = []
def write(self, b):
self._write_stack.append(b[:])
n = self.bs.pop(0)
n = self._blocking_script.pop(0)
if (n < 0):
raise io.BlockingIOError(0, "test blocking", -n)
else:
@ -90,6 +90,23 @@ class IOTest(unittest.TestCase):
f.seek(-2, 2)
f.truncate()
def large_file_ops(self, f):
assert f.readable()
assert f.writable()
self.assertEqual(f.seek(2**32), 2**32)
self.assertEqual(f.tell(), 2**32)
self.assertEqual(f.write(b"xxx"), 3)
self.assertEqual(f.tell(), 2**32 + 3)
self.assertEqual(f.seek(-1, 1), 2**32 + 2)
f.truncate()
self.assertEqual(f.tell(), 2**32 + 2)
self.assertEqual(f.seek(0, 2), 2**32 + 2)
f.truncate(2**32 + 1)
self.assertEqual(f.tell(), 2**32 + 1)
self.assertEqual(f.seek(0, 2), 2**32 + 1)
self.assertEqual(f.seek(-1, 2), 2**32)
self.assertEqual(f.read(2), b"x")
def read_ops(self, f):
data = f.read(5)
self.assertEqual(data, b"hello")
@ -130,19 +147,9 @@ class IOTest(unittest.TestCase):
f = io.BytesIO(data)
self.read_ops(f)
def test_fileio_FileIO(self):
import _fileio
f = _fileio._FileIO(test_support.TESTFN, "w")
self.assertEqual(f.readable(), False)
self.assertEqual(f.writable(), True)
self.assertEqual(f.seekable(), True)
self.write_ops(f)
f.close()
f = _fileio._FileIO(test_support.TESTFN, "r")
self.assertEqual(f.readable(), True)
self.assertEqual(f.writable(), False)
self.assertEqual(f.seekable(), True)
self.read_ops(f)
def test_large_file_ops(self):
f = io.open(test_support.TESTFN, "w+b", buffering=0)
self.large_file_ops(f)
f.close()
@ -205,7 +212,7 @@ class StringIOTest(MemorySeekTestMixin, unittest.TestCase):
class BufferedReaderTest(unittest.TestCase):
def testRead(self):
rawio = MockIO((b"abc", b"d", b"efg"))
rawio = MockRawIO((b"abc", b"d", b"efg"))
bufio = io.BufferedReader(rawio)
self.assertEquals(b"abcdef", bufio.read(6))
@ -231,7 +238,7 @@ class BufferedReaderTest(unittest.TestCase):
def testReadNonBlocking(self):
# Inject some None's in there to simulate EWOULDBLOCK
rawio = MockIO((b"abc", b"d", None, b"efg", None, None))
rawio = MockRawIO((b"abc", b"d", None, b"efg", None, None))
bufio = io.BufferedReader(rawio)
self.assertEquals(b"abcd", bufio.read(6))
@ -241,19 +248,19 @@ class BufferedReaderTest(unittest.TestCase):
self.assertEquals(b"", bufio.read())
def testReadToEof(self):
rawio = MockIO((b"abc", b"d", b"efg"))
rawio = MockRawIO((b"abc", b"d", b"efg"))
bufio = io.BufferedReader(rawio)
self.assertEquals(b"abcdefg", bufio.read(9000))
def testReadNoArgs(self):
rawio = MockIO((b"abc", b"d", b"efg"))
rawio = MockRawIO((b"abc", b"d", b"efg"))
bufio = io.BufferedReader(rawio)
self.assertEquals(b"abcdefg", bufio.read())
def testFileno(self):
rawio = MockIO((b"abc", b"d", b"efg"))
rawio = MockRawIO((b"abc", b"d", b"efg"))
bufio = io.BufferedReader(rawio)
self.assertEquals(42, bufio.fileno())
@ -268,7 +275,7 @@ class BufferedWriterTest(unittest.TestCase):
def testWrite(self):
# Write to the buffered IO but don't overflow the buffer.
writer = MockIO()
writer = MockRawIO()
bufio = io.BufferedWriter(writer, 8)
bufio.write(b"abc")
@ -276,7 +283,7 @@ class BufferedWriterTest(unittest.TestCase):
self.assertFalse(writer._write_stack)
def testWriteOverflow(self):
writer = MockIO()
writer = MockRawIO()
bufio = io.BufferedWriter(writer, 8)
bufio.write(b"abc")
@ -305,13 +312,13 @@ class BufferedWriterTest(unittest.TestCase):
# later.
def testFileno(self):
rawio = MockIO((b"abc", b"d", b"efg"))
rawio = MockRawIO((b"abc", b"d", b"efg"))
bufio = io.BufferedWriter(rawio)
self.assertEquals(42, bufio.fileno())
def testFlush(self):
writer = MockIO()
writer = MockRawIO()
bufio = io.BufferedWriter(writer, 8)
bufio.write(b"abc")
@ -323,8 +330,8 @@ class BufferedWriterTest(unittest.TestCase):
class BufferedRWPairTest(unittest.TestCase):
def testRWPair(self):
r = MockIO(())
w = MockIO()
r = MockRawIO(())
w = MockRawIO()
pair = io.BufferedRWPair(r, w)
# XXX need implementation
@ -333,7 +340,7 @@ class BufferedRWPairTest(unittest.TestCase):
class BufferedRandomTest(unittest.TestCase):
def testReadAndWrite(self):
raw = MockIO((b"asdf", b"ghjk"))
raw = MockRawIO((b"asdf", b"ghjk"))
rw = io.BufferedRandom(raw, 8, 12)
self.assertEqual(b"as", rw.read(2))

View File

@ -18,12 +18,6 @@
* To Do:
*
* - autoconfify header file inclusion
* - Make the ABC RawIO type and inherit from it.
*
* Unanswered questions:
*
* - Add mode and name properties a la Python 2 file objects?
* - Check for readable/writable before attempting to read/write?
*/
#ifdef MS_WINDOWS
@ -36,7 +30,6 @@
typedef struct {
PyObject_HEAD
int fd;
int own_fd; /* 1 means we should close fd */
int readable;
int writable;
int seekable; /* -1 means unknown */
@ -80,8 +73,6 @@ fileio_new(PyTypeObject *type, PyObject *args, PyObject *kews)
if (self != NULL) {
self->fd = -1;
self->weakreflist = NULL;
self->own_fd = 1;
self->seekable = -1;
}
return (PyObject *) self;
@ -107,7 +98,7 @@ dircheck(PyFileIOObject* self)
PyObject *exc;
PyObject *closeresult = fileio_close(self);
Py_DECREF(closeresult);
exc = PyObject_CallFunction(PyExc_IOError, "(is)",
EISDIR, msg);
PyErr_SetObject(PyExc_IOError, exc);
@ -126,7 +117,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
static char *kwlist[] = {"file", "mode", NULL};
char *name = NULL;
char *mode = "r";
char *s;
char *s;
int wideargument = 0;
int ret = 0;
int rwa = 0, plus = 0, append = 0;
@ -183,7 +174,8 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
}
self->readable = self->writable = 0;
s = mode;
self->seekable = -1;
s = mode;
while (*s) {
switch (*s++) {
case 'r':
@ -240,7 +232,6 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
if (fd >= 0) {
self->fd = fd;
/* XXX Should we set self->own_fd = 0 ??? */
}
else {
Py_BEGIN_ALLOW_THREADS
@ -257,7 +248,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
error:
ret = -1;
done:
PyMem_Free(name);
return ret;
@ -269,15 +260,16 @@ fileio_dealloc(PyFileIOObject *self)
if (self->weakreflist != NULL)
PyObject_ClearWeakRefs((PyObject *) self);
if (self->fd >= 0 && self->own_fd) {
if (self->fd >= 0) {
PyObject *closeresult = fileio_close(self);
if (closeresult == NULL) {
#ifdef HAVE_STRERROR
PySys_WriteStderr("close failed: [Errno %d] %s\n", errno, strerror(errno));
PySys_WriteStderr("close failed: [Errno %d] %s\n",
errno, strerror(errno));
#else
PySys_WriteStderr("close failed: [Errno %d]\n", errno);
#endif
} else
} else
Py_DECREF(closeresult);
}
@ -291,6 +283,13 @@ err_closed(void)
return NULL;
}
static PyObject *
err_mode(char *action)
{
PyErr_Format(PyExc_ValueError, "File not open for %s", action);
return NULL;
}
static PyObject *
fileio_fileno(PyFileIOObject *self)
{
@ -338,9 +337,12 @@ fileio_readinto(PyFileIOObject *self, PyObject *args)
{
char *ptr;
Py_ssize_t n;
if (self->fd < 0)
return err_closed();
if (!self->readable)
return err_mode("reading");
if (!PyArg_ParseTuple(args, "w#", &ptr, &n))
return NULL;
@ -367,6 +369,8 @@ fileio_read(PyFileIOObject *self, PyObject *args)
if (self->fd < 0)
return err_closed();
if (!self->readable)
return err_mode("reading");
if (!PyArg_ParseTuple(args, "i", &size))
return NULL;
@ -391,9 +395,9 @@ fileio_read(PyFileIOObject *self, PyObject *args)
if (n != size) {
if (PyBytes_Resize(bytes, n) < 0) {
Py_DECREF(bytes);
return NULL;
return NULL;
}
}
}
return (PyObject *) bytes;
}
@ -406,6 +410,9 @@ fileio_write(PyFileIOObject *self, PyObject *args)
if (self->fd < 0)
return err_closed();
if (!self->writable)
return err_mode("writing");
if (!PyArg_ParseTuple(args, "s#", &ptr, &n))
return NULL;
@ -424,146 +431,163 @@ fileio_write(PyFileIOObject *self, PyObject *args)
return PyInt_FromLong(n);
}
/* XXX Windows support below is likely incomplete */
#if defined(MS_WIN64) || defined(MS_WINDOWS)
typedef PY_LONG_LONG Py_off_t;
#else
typedef off_t Py_off_t;
#endif
/* Cribbed from posix_lseek() */
static PyObject *
portable_lseek(int fd, PyObject *posobj, int whence)
{
Py_off_t pos, res;
#ifdef SEEK_SET
/* Turn 0, 1, 2 into SEEK_{SET,CUR,END} */
switch (whence) {
#if SEEK_SET != 0
case 0: whence = SEEK_SET; break;
#endif
#if SEEK_CUR != 1
case 1: whence = SEEK_CUR; break;
#endif
#if SEEL_END != 2
case 2: whence = SEEK_END; break;
#endif
}
#endif /* SEEK_SET */
if (posobj == NULL)
pos = 0;
else {
#if !defined(HAVE_LARGEFILE_SUPPORT)
pos = PyInt_AsLong(posobj);
#else
pos = PyLong_Check(posobj) ?
PyLong_AsLongLong(posobj) : PyInt_AsLong(posobj);
#endif
if (PyErr_Occurred())
return NULL;
}
Py_BEGIN_ALLOW_THREADS
#if defined(MS_WIN64) || defined(MS_WINDOWS)
res = _lseeki64(fd, pos, whence);
#else
res = lseek(fd, pos, whence);
#endif
Py_END_ALLOW_THREADS
if (res < 0)
return PyErr_SetFromErrno(PyExc_IOError);
#if !defined(HAVE_LARGEFILE_SUPPORT)
return PyInt_FromLong(res);
#else
return PyLong_FromLongLong(res);
#endif
}
static PyObject *
fileio_seek(PyFileIOObject *self, PyObject *args)
{
Py_ssize_t offset;
Py_ssize_t whence = 0;
PyObject *posobj;
int whence = 0;
if (self->fd < 0)
return err_closed();
if (!PyArg_ParseTuple(args, "i|i", &offset, &whence))
if (!PyArg_ParseTuple(args, "O|i", &posobj, &whence))
return NULL;
Py_BEGIN_ALLOW_THREADS
errno = 0;
offset = lseek(self->fd, offset, whence);
Py_END_ALLOW_THREADS
if (offset < 0) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
Py_RETURN_NONE;
return portable_lseek(self->fd, posobj, whence);
}
static PyObject *
fileio_tell(PyFileIOObject *self, PyObject *args)
{
Py_ssize_t offset;
if (self->fd < 0)
return err_closed();
Py_BEGIN_ALLOW_THREADS
errno = 0;
offset = lseek(self->fd, 0, SEEK_CUR);
Py_END_ALLOW_THREADS
if (offset < 0) {
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
return PyInt_FromLong(offset);
return portable_lseek(self->fd, NULL, 1);
}
#ifdef HAVE_FTRUNCATE
static PyObject *
fileio_truncate(PyFileIOObject *self, PyObject *args)
{
Py_ssize_t length;
int ret;
PyObject *posobj = NULL;
Py_off_t pos;
int fd, whence;
if (self->fd < 0)
fd = self->fd;
if (fd < 0)
return err_closed();
if (!self->writable)
return err_mode("writing");
if (!PyArg_ParseTuple(args, "|O", &posobj))
return NULL;
if (posobj == NULL)
whence = 1;
else
whence = 0;
posobj = portable_lseek(fd, posobj, whence);
if (posobj == NULL)
return NULL;
#if !defined(HAVE_LARGEFILE_SUPPORT)
pos = PyInt_AsLong(posobj);
#else
pos = PyLong_Check(posobj) ?
PyLong_AsLongLong(posobj) : PyInt_AsLong(posobj);
#endif
Py_DECREF(posobj);
if (PyErr_Occurred())
return NULL;
/* Setup default value */
Py_BEGIN_ALLOW_THREADS
errno = 0;
length = lseek(self->fd, 0, SEEK_CUR);
pos = ftruncate(fd, pos);
Py_END_ALLOW_THREADS
if (length < 0) {
if (errno < 0)
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
if (!PyArg_ParseTuple(args, "|i", &length))
return NULL;
#ifdef MS_WINDOWS
/* MS _chsize doesn't work if newsize doesn't fit in 32 bits,
so don't even try using it. */
{
HANDLE hFile;
Py_ssize_t initialpos;
/* Have to move current pos to desired endpoint on Windows. */
Py_BEGIN_ALLOW_THREADS
errno = 0;
ret = _portable_fseek(f->f_fp, newsize, SEEK_SET) != 0;
Py_END_ALLOW_THREADS
if (ret)
goto onioerror;
/* Truncate. Note that this may grow the file! */
Py_BEGIN_ALLOW_THREADS
errno = 0;
hFile = (HANDLE)_get_osfhandle(fileno(f->f_fp));
ret = hFile == (HANDLE)-1;
if (ret == 0) {
ret = SetEndOfFile(hFile) == 0;
if (ret)
errno = EACCES;
}
Py_END_ALLOW_THREADS
if (ret)
goto onioerror;
}
#else
Py_BEGIN_ALLOW_THREADS
errno = 0;
ret = ftruncate(self->fd, length);
Py_END_ALLOW_THREADS
#endif /* !MS_WINDOWS */
if (ret < 0) {
onioerror:
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
/* Return to initial position */
Py_BEGIN_ALLOW_THREADS
errno = 0;
ret = lseek(self->fd, length, SEEK_SET);
Py_END_ALLOW_THREADS
if (ret < 0)
goto onioerror;
Py_RETURN_NONE;
}
#endif
static char *
mode_string(PyFileIOObject *self)
{
if (self->readable) {
if (self->writable)
return "r+";
else
return "r";
}
else
return "w";
}
static PyObject *
fileio_repr(PyFileIOObject *self)
{
PyObject *ret = NULL;
if (self->fd < 0)
return PyString_FromFormat("_fileio._FileIO(-1)");
ret = PyString_FromFormat("<%s file at %p>",
self->fd < 0 ? "closed" : "open",
self);
return ret;
return PyString_FromFormat("_fileio._FileIO(%d, '%s')",
self->fd, mode_string(self));
}
static PyObject *
fileio_isatty(PyFileIOObject *self)
{
long res;
if (self->fd < 0)
return err_closed();
Py_BEGIN_ALLOW_THREADS
@ -572,14 +596,6 @@ fileio_isatty(PyFileIOObject *self)
return PyBool_FromLong(res);
}
static PyObject *
fileio_self(PyFileIOObject *self)
{
if (self->fd < 0)
return err_closed();
Py_INCREF(self);
return (PyObject *)self;
}
PyDoc_STRVAR(fileio_doc,
"file(name: str[, mode: str]) -> file IO object\n"
@ -639,12 +655,6 @@ PyDoc_STRVAR(close_doc,
PyDoc_STRVAR(isatty_doc,
"isatty() -> bool. True if the file is connected to a tty device.");
PyDoc_STRVAR(enter_doc,
"__enter__() -> self.");
PyDoc_STRVAR(exit_doc,
"__exit__(*excinfo) -> None. Closes the file.");
PyDoc_STRVAR(seekable_doc,
"seekable() -> bool. True if file supports random-access.");
@ -667,20 +677,26 @@ static PyMethodDef fileio_methods[] = {
{"writable", (PyCFunction)fileio_writable, METH_NOARGS, writable_doc},
{"fileno", (PyCFunction)fileio_fileno, METH_NOARGS, fileno_doc},
{"isatty", (PyCFunction)fileio_isatty, METH_NOARGS, isatty_doc},
{"__enter__",(PyCFunction)fileio_self, METH_NOARGS, enter_doc},
{"__exit__", (PyCFunction)fileio_close, METH_VARARGS, exit_doc},
{NULL, NULL} /* sentinel */
};
/* 'closed' is an attribute for backwards compatibility reasons. */
/* 'closed' and 'mode' are attributes for backwards compatibility reasons. */
static PyObject *
get_closed(PyFileIOObject *f, void *closure)
get_closed(PyFileIOObject *self, void *closure)
{
return PyBool_FromLong((long)(f->fd < 0));
return PyBool_FromLong((long)(self->fd < 0));
}
static PyObject *
get_mode(PyFileIOObject *self, void *closure)
{
return PyString_FromString(mode_string(self));
}
static PyGetSetDef fileio_getsetlist[] = {
{"closed", (getter)get_closed, NULL, "True if the file is closed"},
{"mode", (getter)get_mode, NULL, "String giving the file mode"},
{0},
};