More cleanup. Renamed BlockingIO to BlockingIOError.

Removed unused _PyFileIO class.
Changed inheritance structure.
TODO: do the same kinds of things to TextIO.
This commit is contained in:
Guido van Rossum 2007-04-10 00:22:16 +00:00
parent ebea9beab3
commit 141f767d46
2 changed files with 355 additions and 310 deletions

649
Lib/io.py
View File

@ -15,22 +15,25 @@ __author__ = ("Guido van Rossum <guido@python.org>, "
"Mike Verdone <mike.verdone@gmail.com>, "
"Mark Russell <mark.russell@zen.co.uk>")
__all__ = ["open", "RawIOBase", "FileIO", "SocketIO", "BytesIO",
__all__ = ["BlockingIOError", "open", "IOBase", "RawIOBase", "FileIO",
"SocketIO", "BytesIO", "StringIO", "BufferedIOBase",
"BufferedReader", "BufferedWriter", "BufferedRWPair",
"BufferedRandom"]
"BufferedRandom", "TextIOBase", "TextIOWrapper"]
import os
import sys
import codecs
import _fileio
import warnings
DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
DEFAULT_MAX_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SIZE
class BlockingIO(IOError):
class BlockingIOError(IOError):
def __init__(self, errno, strerror, characters_written):
"""Exception raised when I/O would block on a non-blocking I/O stream."""
def __init__(self, errno, strerror, characters_written=0):
IOError.__init__(self, errno, strerror)
self.characters_written = characters_written
@ -128,25 +131,158 @@ def open(file, mode="r", buffering=None, *, encoding=None):
return textio
class RawIOBase:
class IOBase:
"""Base class for raw binary I/O.
"""Base class for all I/O classes.
This class provides dummy implementations for all methods that
This class provides dummy implementations for many methods that
derived classes can override selectively; the default
implementations represent a file that cannot be read, written or
seeked.
The read() method is implemented by calling readinto(); derived
classes that want to support read() only need to implement
readinto() as a primitive operation.
This does not define read(), readinto() and write(), nor
readline() and friends, since their signatures vary per layer.
"""
def _unsupported(self, name):
### Internal ###
def _unsupported(self, name: str) -> IOError:
"""Internal: raise an exception for unsupported operations."""
raise IOError("%s.%s() not supported" % (self.__class__.__name__,
name))
def read(self, n):
### Positioning ###
def seek(self, pos: int, whence: int = 0) -> None:
"""seek(pos: int, whence: int = 0) -> None. 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.
"""
self._unsupported("seek")
def tell(self) -> int:
"""tell() -> int. Return current stream position."""
self._unsupported("tell")
def truncate(self, pos: int = None) -> None:
"""truncate(size: int = None) -> None. Truncate file to size bytes.
Size defaults to the current IO position as reported by tell().
"""
self._unsupported("truncate")
### Flush and close ###
def flush(self) -> None:
"""flush() -> None. Flushes write buffers, if applicable.
This is a no-op for read-only and non-blocking streams.
"""
__closed = False
def close(self) -> None:
"""close() -> None. Flushes and closes the IO object.
This must be idempotent. It should also set a flag for the
'closed' property (see below) to test.
"""
if not self.__closed:
self.__closed = True
self.flush()
def __del__(self) -> None:
"""Destructor. Calls close()."""
# The try/except block is in case this is called at program
# exit time, when it's possible that globals have already been
# deleted, and then the close() call might fail. Since
# there's nothing we can do about such failures and they annoy
# the end users, we suppress the traceback.
try:
self.close()
except:
pass
### Inquiries ###
def seekable(self) -> bool:
"""seekable() -> bool. Return whether object supports random access.
If False, seek(), tell() and truncate() will raise IOError.
This method may need to do a test seek().
"""
return False
def readable(self) -> bool:
"""readable() -> bool. Return whether object was opened for reading.
If False, read() will raise IOError.
"""
return False
def writable(self) -> bool:
"""writable() -> bool. Return whether object was opened for writing.
If False, write() and truncate() will raise IOError.
"""
return False
@property
def closed(self):
"""closed: bool. True iff the file has been closed.
For backwards compatibility, this is a property, not a predicate.
"""
return self.__closed
### Context manager ###
def __enter__(self) -> "IOBase": # That's a forward reference
"""Context management protocol. Returns self."""
return self
def __exit__(self, *args) -> None:
"""Context management protocol. Calls close()"""
self.close()
### Lower-level APIs ###
# XXX Should these be present even if unimplemented?
def fileno(self) -> int:
"""fileno() -> int. Returns underlying file descriptor if one exists.
Raises IOError if the IO object does not use a file descriptor.
"""
self._unsupported("fileno")
def isatty(self) -> bool:
"""isatty() -> int. Returns whether this is an 'interactive' stream.
Returns False if we don't know.
"""
return False
class RawIOBase(IOBase):
"""Base class for raw binary I/O.
The read() method is implemented by calling readinto(); derived
classes that want to support read() only need to implement
readinto() as a primitive operation. In general, readinto()
can be more efficient than read().
(It would be tempting to also provide an implementation of
readinto() in terms of read(), in case the latter is a more
suitable primitive operation, but that would lead to nasty
recursion in case a subclass doesn't implement either.)
"""
def read(self, n: int) -> bytes:
"""read(n: int) -> bytes. Read and return up to n bytes.
Returns an empty bytes array on EOF, or None if the object is
@ -157,179 +293,31 @@ class RawIOBase:
del b[n:]
return b
def readinto(self, b):
"""readinto(b: bytes) -> None. Read up to len(b) bytes into b.
def readinto(self, b: bytes) -> int:
"""readinto(b: bytes) -> int. Read up to len(b) bytes into b.
Returns number of bytes read (0 for EOF), or None if the object
is set not to block as has no data to read.
"""
self._unsupported("readinto")
def write(self, b):
def write(self, b: bytes) -> int:
"""write(b: bytes) -> int. Write the given buffer to the IO stream.
Returns the number of bytes written, which may be less than len(b).
"""
self._unsupported("write")
def seek(self, pos, whence=0):
"""seek(pos: int, whence: int = 0) -> None. 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.
"""
self._unsupported("seek")
class FileIO(_fileio._FileIO, RawIOBase):
def tell(self):
"""tell() -> int. Return current stream position."""
self._unsupported("tell")
"""Raw I/O implementation for OS files.
def truncate(self, pos=None):
"""truncate(size: int = None) -> None. Truncate file to size bytes.
Size defaults to the current IO position as reported by tell().
"""
self._unsupported("truncate")
def close(self):
"""close() -> None. Close IO object."""
pass
@property
def closed(self):
"""closed: bool. True iff the file has been closed."""
# This is a property for backwards compatibility
return False
def seekable(self):
"""seekable() -> bool. Return whether object supports random access.
If False, seek(), tell() and truncate() will raise IOError.
This method may need to do a test seek().
"""
return False
def readable(self):
"""readable() -> bool. Return whether object was opened for reading.
If False, read() will raise IOError.
"""
return False
def writable(self):
"""writable() -> bool. Return whether object was opened for writing.
If False, write() and truncate() will raise IOError.
"""
return False
def __enter__(self):
"""Context management protocol. Returns self."""
return self
def __exit__(self, *args):
"""Context management protocol. Same as close()"""
self.close()
def fileno(self):
"""fileno() -> int. Return underlying file descriptor if there is one.
Raises IOError if the IO object does not use a file descriptor.
"""
self._unsupported("fileno")
class _PyFileIO(RawIOBase):
"""Raw I/O implementation for OS files."""
# XXX More docs
def __init__(self, file, mode):
self._seekable = None
self._mode = mode
if isinstance(file, int):
self._fd = file
return
if mode == "r":
flags = os.O_RDONLY
elif mode == "w":
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
elif mode == "r+":
flags = os.O_RDWR
else:
assert False, "unsupported mode %r (for now)" % mode
if hasattr(os, "O_BINARY"):
flags |= os.O_BINARY
self._fd = os.open(file, flags)
def readinto(self, b):
# XXX We really should have os.readinto()
tmp = os.read(self._fd, len(b))
n = len(tmp)
b[:n] = tmp
return n
def write(self, b):
return os.write(self._fd, b)
def seek(self, pos, whence=0):
os.lseek(self._fd, pos, whence)
def tell(self):
return os.lseek(self._fd, 0, 1)
def truncate(self, pos=None):
if pos is None:
pos = self.tell()
os.ftruncate(self._fd, pos)
def close(self):
# Must be idempotent
# XXX But what about thread-safe?
fd = self._fd
self._fd = -1
if fd >= 0:
os.close(fd)
@property
def closed(self):
return self._fd >= 0
def readable(self):
return "r" in self._mode or "+" in self._mode
def writable(self):
return "w" in self._mode or "+" in self._mode or "a" in self._mode
def seekable(self):
if self._seekable is None:
try:
os.lseek(self._fd, 0, 1)
except os.error:
self._seekable = False
else:
self._seekable = True
return self._seekable
def fileno(self):
return self._fd
try:
import _fileio
except ImportError:
# Let's use the Python version
warnings.warn("Can't import _fileio, using slower Python lookalike",
RuntimeWarning)
FileIO = _PyFileIO
else:
# Create a trivial subclass with the proper inheritance structure
class FileIO(_fileio._FileIO, RawIOBase):
"""Raw I/O implementation for OS files."""
# XXX More docs
This multiply inherits from _FileIO and RawIOBase to make
isinstance(io.FileIO(), io.RawIOBase) return True without
requiring that _fileio._FileIO inherits from io.RawIOBase (which
would be hard to do since _fileio.c is written in C).
"""
class SocketIO(RawIOBase):
@ -337,14 +325,13 @@ class SocketIO(RawIOBase):
"""Raw I/O implementation for stream sockets."""
# XXX More docs
_closed = True
# XXX Hook this up to socket.py
def __init__(self, sock, mode):
assert mode in ("r", "w", "rw")
RawIOBase.__init__(self)
self._sock = sock
self._mode = mode
self._closed = False
def readinto(self, b):
return self._sock.recv_into(b)
@ -353,12 +340,9 @@ class SocketIO(RawIOBase):
return self._sock.send(b)
def close(self):
self._closed = True
self._sock.close()
@property
def closed(self):
return self._closed
if not self.closed:
RawIOBase.close()
self._sock.close()
def readable(self):
return "r" in self._mode
@ -370,7 +354,126 @@ class SocketIO(RawIOBase):
return self._sock.fileno()
class _MemoryIOBase(RawIOBase):
class BufferedIOBase(RawIOBase):
"""Base class for buffered IO objects.
The main difference with RawIOBase is that the read() method
supports omitting the size argument, and does not have a default
implementation that defers to readinto().
In addition, read(), readinto() and write() may raise
BlockingIOError if the underlying raw stream is in non-blocking
mode and not ready; unlike their raw counterparts, they will never
return None.
A typical implementation should not inherit from a RawIOBase
implementation, but wrap one.
"""
def read(self, n: int = -1) -> bytes:
"""read(n: int = -1) -> bytes. Read and return up to n bytes.
If the argument is omitted, or negative, reads and returns all
data until EOF.
If the argument is positive, and the underlying raw stream is
not 'interactive', multiple raw reads may be issued to satisfy
the byte count (unless EOF is reached first). But for
interactive raw streams (XXX and for pipes?), at most one raw
read will be issued, and a short result does not imply that
EOF is imminent.
Returns an empty bytes array on EOF.
Raises BlockingIOError if the underlying raw stream has no
data at the moment.
"""
self._unsupported("read")
def readinto(self, b: bytes) -> int:
"""readinto(b: bytes) -> int. Read up to len(b) bytes into b.
Like read(), this may issue multiple reads to the underlying
raw stream, unless the latter is 'interactive' (XXX or a
pipe?).
Returns the number of bytes read (0 for EOF).
Raises BlockingIOError if the underlying raw stream has no
data at the moment.
"""
self._unsupported("readinto")
def write(self, b: bytes) -> int:
"""write(b: bytes) -> int. Write the given buffer to the IO stream.
Returns the number of bytes written, which is never less than
len(b).
Raises BlockingIOError if the buffer is full and the
underlying raw stream cannot accept more data at the moment.
"""
self._unsupported("write")
class _BufferedIOMixin(BufferedIOBase):
"""A mixin implementation of BufferedIOBase with an underlying raw stream.
This passes most requests on to the underlying raw stream. It
does *not* provide implementations of read(), readinto() or
write().
"""
def __init__(self, raw):
self.raw = raw
### Positioning ###
def seek(self, pos, whence=0):
self.raw.seek(pos, whence)
def tell(self):
return self.raw.tell()
def truncate(self, pos=None):
self.raw.truncate(pos)
### Flush and close ###
def flush(self):
self.raw.flush()
def close(self):
self.flush()
self.raw.close()
### Inquiries ###
def seekable(self):
return self.raw.seekable()
def readable(self):
return self.raw.readable()
def writable(self):
return self.raw.writable()
@property
def closed(self):
return self.raw.closed
### Lower-level APIs ###
def fileno(self):
return self.raw.fileno()
def isatty(self):
return self.raw.isatty()
class _MemoryIOMixin(BufferedIOBase):
# XXX docstring
@ -381,11 +484,10 @@ class _MemoryIOBase(RawIOBase):
def getvalue(self):
return self._buffer
def read(self, n=None):
# XXX Shouldn't this support n < 0 too?
if n is None:
def read(self, n=-1):
assert n is not None
if n < 0:
n = len(self._buffer)
assert n >= 0
newpos = min(len(self._buffer), self._pos + n)
b = self._buffer[self._pos : newpos]
self._pos = newpos
@ -434,7 +536,7 @@ class _MemoryIOBase(RawIOBase):
return True
class BytesIO(_MemoryIOBase):
class BytesIO(_MemoryIOMixin):
"""Buffered I/O implementation using a bytes buffer, like StringIO."""
@ -444,49 +546,35 @@ class BytesIO(_MemoryIOBase):
buffer = b""
if inital_bytes is not None:
buffer += inital_bytes
_MemoryIOBase.__init__(self, buffer)
_MemoryIOMixin.__init__(self, buffer)
class StringIO(_MemoryIOBase):
# XXX This should inherit from TextIOBase
class StringIO(_MemoryIOMixin):
"""Buffered I/O implementation using a string buffer, like StringIO."""
# XXX More docs
# XXX Reuses the same code as BytesIO, just with a string rather
# that bytes as the _buffer value. That won't work in C of course.
# Reuses the same code as BytesIO, just with a string rather that
# bytes as the _buffer value.
# XXX This doesn't work; _MemoryIOMixin's write() and truncate()
# methods assume the buffer is mutable. Simply redefining those
# to use slice concatenation will make it awfully slow (in fact,
# quadratic in the number of write() calls).
def __init__(self, inital_string=None):
buffer = ""
if inital_string is not None:
buffer += inital_string
_MemoryIOBase.__init__(self, buffer)
_MemoryIOMixin.__init__(self, buffer)
def readinto(self, b: bytes) -> int:
self._unsupported("readinto")
# XXX Isn't this the wrong base class?
class BufferedIOBase(RawIOBase):
"""Base class for buffered IO objects."""
def flush(self):
"""Flush the buffer to the underlying raw IO object."""
self._unsupported("flush")
def seekable(self):
return self.raw.seekable()
def fileno(self):
return self.raw.fileno()
def close(self):
self.raw.close()
@property
def closed(self):
return self.raw.closed
class BufferedReader(BufferedIOBase):
class BufferedReader(_BufferedIOMixin):
"""Buffer for a readable sequential RawIO object."""
@ -494,23 +582,21 @@ class BufferedReader(BufferedIOBase):
"""Create a new buffered reader using the given readable raw IO object.
"""
assert raw.readable()
self.raw = raw
_BufferedIOMixin.__init__(self, raw)
self._read_buf = b""
self.buffer_size = buffer_size
def read(self, n=None):
def read(self, n=-1):
"""Read n bytes.
Returns exactly n bytes of data unless the underlying raw IO
stream reaches EOF of if the call would block in non-blocking
mode. If n is None, read until EOF or until read() would
mode. If n is negative, read until EOF or until read() would
block.
"""
# XXX n == 0 should return b""?
# XXX n < 0 should be the same as n is None?
assert n is None or n > 0, '.read(): Bad read size %r' % n
assert n is not None
nodata_val = b""
while n is None or len(self._read_buf) < n:
while n < 0 or len(self._read_buf) < n:
to_read = max(self.buffer_size,
n if n is not None else 2*len(self._read_buf))
current = self.raw.read(to_read)
@ -520,7 +606,7 @@ class BufferedReader(BufferedIOBase):
break
self._read_buf += current
if self._read_buf:
if n is None:
if n < 0:
n = len(self._read_buf)
out = self._read_buf[:n]
self._read_buf = self._read_buf[n:]
@ -528,13 +614,6 @@ class BufferedReader(BufferedIOBase):
out = nodata_val
return out
def readable(self):
return True
def flush(self):
# Flush is a no-op
pass
def tell(self):
return self.raw.tell() - len(self._read_buf)
@ -545,16 +624,18 @@ class BufferedReader(BufferedIOBase):
self._read_buf = b""
class BufferedWriter(BufferedIOBase):
class BufferedWriter(_BufferedIOMixin):
# XXX docstring
def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE,
max_buffer_size=DEFAULT_MAX_BUFFER_SIZE):
def __init__(self, raw,
buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None):
assert raw.writable()
self.raw = raw
_BufferedIOMixin.__init__(self, raw)
self.buffer_size = buffer_size
self.max_buffer_size = max_buffer_size
self.max_buffer_size = (2*buffer_size
if max_buffer_size is None
else max_buffer_size)
self._write_buf = b""
def write(self, b):
@ -564,24 +645,21 @@ class BufferedWriter(BufferedIOBase):
# We're full, so let's pre-flush the buffer
try:
self.flush()
except BlockingIO as e:
except BlockingIOError as e:
# We can't accept anything else.
# XXX Why not just let the exception pass through?
raise BlockingIO(e.errno, e.strerror, 0)
raise BlockingIOError(e.errno, e.strerror, 0)
self._write_buf.extend(b)
if len(self._write_buf) > self.buffer_size:
try:
self.flush()
except BlockingIO as e:
except BlockingIOError as e:
if (len(self._write_buf) > self.max_buffer_size):
# We've hit max_buffer_size. We have to accept a partial
# write and cut back our buffer.
overage = len(self._write_buf) - self.max_buffer_size
self._write_buf = self._write_buf[:self.max_buffer_size]
raise BlockingIO(e.errno, e.strerror, overage)
def writable(self):
return True
raise BlockingIOError(e.errno, e.strerror, overage)
def flush(self):
written = 0
@ -590,11 +668,11 @@ class BufferedWriter(BufferedIOBase):
n = self.raw.write(self._write_buf)
del self._write_buf[:n]
written += n
except BlockingIO as e:
except BlockingIOError as e:
n = e.characters_written
del self._write_buf[:n]
written += n
raise BlockingIO(e.errno, e.strerror, written)
raise BlockingIOError(e.errno, e.strerror, written)
def tell(self):
return self.raw.tell() + len(self._write_buf)
@ -603,40 +681,37 @@ class BufferedWriter(BufferedIOBase):
self.flush()
self.raw.seek(pos, whence)
def close(self):
self.flush()
self.raw.close()
def __del__(self):
try:
self.flush()
except:
pass
# XXX Maybe use containment instead of multiple inheritance?
class BufferedRWPair(BufferedReader, BufferedWriter):
class BufferedRWPair(BufferedIOBase):
"""A buffered reader and writer object together.
A buffered reader object and buffered writer object put together to
form a sequential IO object that can read and write.
A buffered reader object and buffered writer object put together
to form a sequential IO object that can read and write.
This is typically used with a socket or two-way pipe.
XXX The usefulness of this (compared to having two separate IO
objects) is questionable.
"""
def __init__(self, reader, writer, buffer_size=DEFAULT_BUFFER_SIZE,
max_buffer_size=DEFAULT_MAX_BUFFER_SIZE):
def __init__(self, reader, writer,
buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None):
"""Constructor.
The arguments are two RawIO instances.
"""
assert reader.readable()
assert writer.writable()
BufferedReader.__init__(self, reader, buffer_size)
BufferedWriter.__init__(self, writer, buffer_size, max_buffer_size)
self.reader = reader
self.writer = writer
self.reader = BufferedReader(reader, buffer_size)
self.writer = BufferedWriter(writer, buffer_size, max_buffer_size)
def read(self, n=None):
def read(self, n=-1):
return self.reader.read(n)
def readinto(self, b):
return self.reader.readinto(b)
def write(self, b):
return self.writer.write(b)
@ -649,39 +724,28 @@ class BufferedRWPair(BufferedReader, BufferedWriter):
def flush(self):
return self.writer.flush()
def seekable(self):
return False
def fileno(self):
# XXX whose fileno do we return? Reader's? Writer's? Unsupported?
raise IOError(".fileno() unsupported")
def close(self):
self.reader.close()
self.writer.close()
self.reader.close()
def isatty(self):
return self.reader.isatty() or self.writer.isatty()
@property
def closed(self):
return self.reader.closed or self.writer.closed
return self.writer.closed()
# XXX Maybe use containment instead of multiple inheritance?
class BufferedRandom(BufferedReader, BufferedWriter):
class BufferedRandom(BufferedWriter, BufferedReader):
# XXX docstring
def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE,
max_buffer_size=DEFAULT_MAX_BUFFER_SIZE):
def __init__(self, raw,
buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None):
assert raw.seekable()
BufferedReader.__init__(self, raw, buffer_size)
BufferedWriter.__init__(self, raw, buffer_size, max_buffer_size)
def readable(self):
return self.raw.readable()
def writable(self):
return self.raw.writable()
def seek(self, pos, whence=0):
self.flush()
# First do the raw seek, then empty the read buffer, so that
@ -700,19 +764,20 @@ class BufferedRandom(BufferedReader, BufferedWriter):
else:
return self.raw.tell() - len(self._read_buf)
def read(self, n=None):
def read(self, n=-1):
self.flush()
return BufferedReader.read(self, n)
def readinto(self, b):
self.flush()
return BufferedReader.readinto(self, b)
def write(self, b):
if self._read_buf:
self.raw.seek(-len(self._read_buf), 1) # Undo readahead
self._read_buf = b""
return BufferedWriter.write(self, b)
def flush(self):
BufferedWriter.flush(self)
# XXX That's not the right base class
class TextIOBase(BufferedIOBase):
@ -807,12 +872,6 @@ class TextIOWrapper(TextIOBase):
def closed(self):
return self.buffer.closed
def __del__(self):
try:
self.flush()
except:
pass
def fileno(self):
return self.buffer.fileno()
@ -841,7 +900,7 @@ class TextIOWrapper(TextIOBase):
res = self._pending
if n < 0:
res += decoder.decode(self.buffer.read(), True)
self._pending = ''
self._pending = ""
return res
else:
while len(res) < n:

View File

@ -64,7 +64,7 @@ class MockNonBlockWriterIO(io.RawIOBase):
self._write_stack.append(b[:])
n = self.bs.pop(0)
if (n < 0):
raise io.BlockingIO(0, "test blocking", -n)
raise io.BlockingIOError(0, "test blocking", -n)
else:
return n
@ -145,20 +145,6 @@ class IOTest(unittest.TestCase):
self.read_ops(f)
f.close()
def test_PyFileIO(self):
f = io._PyFileIO(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 = io._PyFileIO(test_support.TESTFN, "r")
self.assertEqual(f.readable(), True)
self.assertEqual(f.writable(), False)
self.assertEqual(f.seekable(), True)
self.read_ops(f)
f.close()
class MemorySeekTestMixin: