mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
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:
parent
ebea9beab3
commit
141f767d46
2 changed files with 355 additions and 310 deletions
649
Lib/io.py
649
Lib/io.py
|
@ -15,22 +15,25 @@ __author__ = ("Guido van Rossum <guido@python.org>, "
|
||||||
"Mike Verdone <mike.verdone@gmail.com>, "
|
"Mike Verdone <mike.verdone@gmail.com>, "
|
||||||
"Mark Russell <mark.russell@zen.co.uk>")
|
"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",
|
"BufferedReader", "BufferedWriter", "BufferedRWPair",
|
||||||
"BufferedRandom"]
|
"BufferedRandom", "TextIOBase", "TextIOWrapper"]
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import codecs
|
import codecs
|
||||||
|
import _fileio
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
|
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)
|
IOError.__init__(self, errno, strerror)
|
||||||
self.characters_written = characters_written
|
self.characters_written = characters_written
|
||||||
|
|
||||||
|
@ -128,25 +131,158 @@ def open(file, mode="r", buffering=None, *, encoding=None):
|
||||||
return textio
|
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
|
derived classes can override selectively; the default
|
||||||
implementations represent a file that cannot be read, written or
|
implementations represent a file that cannot be read, written or
|
||||||
seeked.
|
seeked.
|
||||||
|
|
||||||
The read() method is implemented by calling readinto(); derived
|
This does not define read(), readinto() and write(), nor
|
||||||
classes that want to support read() only need to implement
|
readline() and friends, since their signatures vary per layer.
|
||||||
readinto() as a primitive operation.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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__,
|
raise IOError("%s.%s() not supported" % (self.__class__.__name__,
|
||||||
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.
|
"""read(n: int) -> bytes. Read and return up to n bytes.
|
||||||
|
|
||||||
Returns an empty bytes array on EOF, or None if the object is
|
Returns an empty bytes array on EOF, or None if the object is
|
||||||
|
@ -157,179 +293,31 @@ class RawIOBase:
|
||||||
del b[n:]
|
del b[n:]
|
||||||
return b
|
return b
|
||||||
|
|
||||||
def readinto(self, b):
|
def readinto(self, b: bytes) -> int:
|
||||||
"""readinto(b: bytes) -> None. Read up to len(b) bytes into b.
|
"""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
|
Returns number of bytes read (0 for EOF), or None if the object
|
||||||
is set not to block as has no data to read.
|
is set not to block as has no data to read.
|
||||||
"""
|
"""
|
||||||
self._unsupported("readinto")
|
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.
|
"""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).
|
Returns the number of bytes written, which may be less than len(b).
|
||||||
"""
|
"""
|
||||||
self._unsupported("write")
|
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")
|
|
||||||
|
|
||||||
def tell(self):
|
|
||||||
"""tell() -> int. Return current stream position."""
|
|
||||||
self._unsupported("tell")
|
|
||||||
|
|
||||||
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):
|
class FileIO(_fileio._FileIO, RawIOBase):
|
||||||
"""Raw I/O implementation for OS files."""
|
|
||||||
# XXX More docs
|
"""Raw I/O implementation for OS files.
|
||||||
|
|
||||||
|
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):
|
class SocketIO(RawIOBase):
|
||||||
|
@ -337,14 +325,13 @@ class SocketIO(RawIOBase):
|
||||||
"""Raw I/O implementation for stream sockets."""
|
"""Raw I/O implementation for stream sockets."""
|
||||||
|
|
||||||
# XXX More docs
|
# XXX More docs
|
||||||
|
# XXX Hook this up to socket.py
|
||||||
_closed = True
|
|
||||||
|
|
||||||
def __init__(self, sock, mode):
|
def __init__(self, sock, mode):
|
||||||
assert mode in ("r", "w", "rw")
|
assert mode in ("r", "w", "rw")
|
||||||
|
RawIOBase.__init__(self)
|
||||||
self._sock = sock
|
self._sock = sock
|
||||||
self._mode = mode
|
self._mode = mode
|
||||||
self._closed = False
|
|
||||||
|
|
||||||
def readinto(self, b):
|
def readinto(self, b):
|
||||||
return self._sock.recv_into(b)
|
return self._sock.recv_into(b)
|
||||||
|
@ -353,13 +340,10 @@ class SocketIO(RawIOBase):
|
||||||
return self._sock.send(b)
|
return self._sock.send(b)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self._closed = True
|
if not self.closed:
|
||||||
|
RawIOBase.close()
|
||||||
self._sock.close()
|
self._sock.close()
|
||||||
|
|
||||||
@property
|
|
||||||
def closed(self):
|
|
||||||
return self._closed
|
|
||||||
|
|
||||||
def readable(self):
|
def readable(self):
|
||||||
return "r" in self._mode
|
return "r" in self._mode
|
||||||
|
|
||||||
|
@ -370,7 +354,126 @@ class SocketIO(RawIOBase):
|
||||||
return self._sock.fileno()
|
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
|
# XXX docstring
|
||||||
|
|
||||||
|
@ -381,11 +484,10 @@ class _MemoryIOBase(RawIOBase):
|
||||||
def getvalue(self):
|
def getvalue(self):
|
||||||
return self._buffer
|
return self._buffer
|
||||||
|
|
||||||
def read(self, n=None):
|
def read(self, n=-1):
|
||||||
# XXX Shouldn't this support n < 0 too?
|
assert n is not None
|
||||||
if n is None:
|
if n < 0:
|
||||||
n = len(self._buffer)
|
n = len(self._buffer)
|
||||||
assert n >= 0
|
|
||||||
newpos = min(len(self._buffer), self._pos + n)
|
newpos = min(len(self._buffer), self._pos + n)
|
||||||
b = self._buffer[self._pos : newpos]
|
b = self._buffer[self._pos : newpos]
|
||||||
self._pos = newpos
|
self._pos = newpos
|
||||||
|
@ -434,7 +536,7 @@ class _MemoryIOBase(RawIOBase):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class BytesIO(_MemoryIOBase):
|
class BytesIO(_MemoryIOMixin):
|
||||||
|
|
||||||
"""Buffered I/O implementation using a bytes buffer, like StringIO."""
|
"""Buffered I/O implementation using a bytes buffer, like StringIO."""
|
||||||
|
|
||||||
|
@ -444,49 +546,35 @@ class BytesIO(_MemoryIOBase):
|
||||||
buffer = b""
|
buffer = b""
|
||||||
if inital_bytes is not None:
|
if inital_bytes is not None:
|
||||||
buffer += inital_bytes
|
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."""
|
"""Buffered I/O implementation using a string buffer, like StringIO."""
|
||||||
|
|
||||||
# XXX More docs
|
# XXX More docs
|
||||||
|
|
||||||
# XXX Reuses the same code as BytesIO, just with a string rather
|
# Reuses the same code as BytesIO, just with a string rather that
|
||||||
# that bytes as the _buffer value. That won't work in C of course.
|
# 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):
|
def __init__(self, inital_string=None):
|
||||||
buffer = ""
|
buffer = ""
|
||||||
if inital_string is not None:
|
if inital_string is not None:
|
||||||
buffer += inital_string
|
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 BufferedReader(_BufferedIOMixin):
|
||||||
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):
|
|
||||||
|
|
||||||
"""Buffer for a readable sequential RawIO object."""
|
"""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.
|
"""Create a new buffered reader using the given readable raw IO object.
|
||||||
"""
|
"""
|
||||||
assert raw.readable()
|
assert raw.readable()
|
||||||
self.raw = raw
|
_BufferedIOMixin.__init__(self, raw)
|
||||||
self._read_buf = b""
|
self._read_buf = b""
|
||||||
self.buffer_size = buffer_size
|
self.buffer_size = buffer_size
|
||||||
|
|
||||||
def read(self, n=None):
|
def read(self, n=-1):
|
||||||
"""Read n bytes.
|
"""Read n bytes.
|
||||||
|
|
||||||
Returns exactly n bytes of data unless the underlying raw IO
|
Returns exactly n bytes of data unless the underlying raw IO
|
||||||
stream reaches EOF of if the call would block in non-blocking
|
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.
|
block.
|
||||||
"""
|
"""
|
||||||
# XXX n == 0 should return b""?
|
assert n is not None
|
||||||
# XXX n < 0 should be the same as n is None?
|
|
||||||
assert n is None or n > 0, '.read(): Bad read size %r' % n
|
|
||||||
nodata_val = b""
|
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,
|
to_read = max(self.buffer_size,
|
||||||
n if n is not None else 2*len(self._read_buf))
|
n if n is not None else 2*len(self._read_buf))
|
||||||
current = self.raw.read(to_read)
|
current = self.raw.read(to_read)
|
||||||
|
@ -520,7 +606,7 @@ class BufferedReader(BufferedIOBase):
|
||||||
break
|
break
|
||||||
self._read_buf += current
|
self._read_buf += current
|
||||||
if self._read_buf:
|
if self._read_buf:
|
||||||
if n is None:
|
if n < 0:
|
||||||
n = len(self._read_buf)
|
n = len(self._read_buf)
|
||||||
out = self._read_buf[:n]
|
out = self._read_buf[:n]
|
||||||
self._read_buf = self._read_buf[n:]
|
self._read_buf = self._read_buf[n:]
|
||||||
|
@ -528,13 +614,6 @@ class BufferedReader(BufferedIOBase):
|
||||||
out = nodata_val
|
out = nodata_val
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def readable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
# Flush is a no-op
|
|
||||||
pass
|
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return self.raw.tell() - len(self._read_buf)
|
return self.raw.tell() - len(self._read_buf)
|
||||||
|
|
||||||
|
@ -545,16 +624,18 @@ class BufferedReader(BufferedIOBase):
|
||||||
self._read_buf = b""
|
self._read_buf = b""
|
||||||
|
|
||||||
|
|
||||||
class BufferedWriter(BufferedIOBase):
|
class BufferedWriter(_BufferedIOMixin):
|
||||||
|
|
||||||
# XXX docstring
|
# XXX docstring
|
||||||
|
|
||||||
def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE,
|
def __init__(self, raw,
|
||||||
max_buffer_size=DEFAULT_MAX_BUFFER_SIZE):
|
buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None):
|
||||||
assert raw.writable()
|
assert raw.writable()
|
||||||
self.raw = raw
|
_BufferedIOMixin.__init__(self, raw)
|
||||||
self.buffer_size = buffer_size
|
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""
|
self._write_buf = b""
|
||||||
|
|
||||||
def write(self, b):
|
def write(self, b):
|
||||||
|
@ -564,24 +645,21 @@ class BufferedWriter(BufferedIOBase):
|
||||||
# We're full, so let's pre-flush the buffer
|
# We're full, so let's pre-flush the buffer
|
||||||
try:
|
try:
|
||||||
self.flush()
|
self.flush()
|
||||||
except BlockingIO as e:
|
except BlockingIOError as e:
|
||||||
# We can't accept anything else.
|
# We can't accept anything else.
|
||||||
# XXX Why not just let the exception pass through?
|
# 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)
|
self._write_buf.extend(b)
|
||||||
if len(self._write_buf) > self.buffer_size:
|
if len(self._write_buf) > self.buffer_size:
|
||||||
try:
|
try:
|
||||||
self.flush()
|
self.flush()
|
||||||
except BlockingIO as e:
|
except BlockingIOError as e:
|
||||||
if (len(self._write_buf) > self.max_buffer_size):
|
if (len(self._write_buf) > self.max_buffer_size):
|
||||||
# We've hit max_buffer_size. We have to accept a partial
|
# We've hit max_buffer_size. We have to accept a partial
|
||||||
# write and cut back our buffer.
|
# write and cut back our buffer.
|
||||||
overage = len(self._write_buf) - self.max_buffer_size
|
overage = len(self._write_buf) - self.max_buffer_size
|
||||||
self._write_buf = self._write_buf[:self.max_buffer_size]
|
self._write_buf = self._write_buf[:self.max_buffer_size]
|
||||||
raise BlockingIO(e.errno, e.strerror, overage)
|
raise BlockingIOError(e.errno, e.strerror, overage)
|
||||||
|
|
||||||
def writable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
written = 0
|
written = 0
|
||||||
|
@ -590,11 +668,11 @@ class BufferedWriter(BufferedIOBase):
|
||||||
n = self.raw.write(self._write_buf)
|
n = self.raw.write(self._write_buf)
|
||||||
del self._write_buf[:n]
|
del self._write_buf[:n]
|
||||||
written += n
|
written += n
|
||||||
except BlockingIO as e:
|
except BlockingIOError as e:
|
||||||
n = e.characters_written
|
n = e.characters_written
|
||||||
del self._write_buf[:n]
|
del self._write_buf[:n]
|
||||||
written += n
|
written += n
|
||||||
raise BlockingIO(e.errno, e.strerror, written)
|
raise BlockingIOError(e.errno, e.strerror, written)
|
||||||
|
|
||||||
def tell(self):
|
def tell(self):
|
||||||
return self.raw.tell() + len(self._write_buf)
|
return self.raw.tell() + len(self._write_buf)
|
||||||
|
@ -603,40 +681,37 @@ class BufferedWriter(BufferedIOBase):
|
||||||
self.flush()
|
self.flush()
|
||||||
self.raw.seek(pos, whence)
|
self.raw.seek(pos, whence)
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.flush()
|
|
||||||
self.raw.close()
|
|
||||||
|
|
||||||
def __del__(self):
|
class BufferedRWPair(BufferedIOBase):
|
||||||
try:
|
|
||||||
self.flush()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# XXX Maybe use containment instead of multiple inheritance?
|
|
||||||
class BufferedRWPair(BufferedReader, BufferedWriter):
|
|
||||||
|
|
||||||
"""A buffered reader and writer object together.
|
"""A buffered reader and writer object together.
|
||||||
|
|
||||||
A buffered reader object and buffered writer object put together to
|
A buffered reader object and buffered writer object put together
|
||||||
form a sequential IO object that can read and write.
|
to form a sequential IO object that can read and write.
|
||||||
|
|
||||||
This is typically used with a socket or two-way pipe.
|
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,
|
def __init__(self, reader, writer,
|
||||||
max_buffer_size=DEFAULT_MAX_BUFFER_SIZE):
|
buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None):
|
||||||
|
"""Constructor.
|
||||||
|
|
||||||
|
The arguments are two RawIO instances.
|
||||||
|
"""
|
||||||
assert reader.readable()
|
assert reader.readable()
|
||||||
assert writer.writable()
|
assert writer.writable()
|
||||||
BufferedReader.__init__(self, reader, buffer_size)
|
self.reader = BufferedReader(reader, buffer_size)
|
||||||
BufferedWriter.__init__(self, writer, buffer_size, max_buffer_size)
|
self.writer = BufferedWriter(writer, buffer_size, max_buffer_size)
|
||||||
self.reader = reader
|
|
||||||
self.writer = writer
|
|
||||||
|
|
||||||
def read(self, n=None):
|
def read(self, n=-1):
|
||||||
return self.reader.read(n)
|
return self.reader.read(n)
|
||||||
|
|
||||||
|
def readinto(self, b):
|
||||||
|
return self.reader.readinto(b)
|
||||||
|
|
||||||
def write(self, b):
|
def write(self, b):
|
||||||
return self.writer.write(b)
|
return self.writer.write(b)
|
||||||
|
|
||||||
|
@ -649,39 +724,28 @@ class BufferedRWPair(BufferedReader, BufferedWriter):
|
||||||
def flush(self):
|
def flush(self):
|
||||||
return self.writer.flush()
|
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):
|
def close(self):
|
||||||
self.reader.close()
|
|
||||||
self.writer.close()
|
self.writer.close()
|
||||||
|
self.reader.close()
|
||||||
|
|
||||||
|
def isatty(self):
|
||||||
|
return self.reader.isatty() or self.writer.isatty()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def closed(self):
|
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(BufferedWriter, BufferedReader):
|
||||||
class BufferedRandom(BufferedReader, BufferedWriter):
|
|
||||||
|
|
||||||
# XXX docstring
|
# XXX docstring
|
||||||
|
|
||||||
def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE,
|
def __init__(self, raw,
|
||||||
max_buffer_size=DEFAULT_MAX_BUFFER_SIZE):
|
buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None):
|
||||||
assert raw.seekable()
|
assert raw.seekable()
|
||||||
BufferedReader.__init__(self, raw, buffer_size)
|
BufferedReader.__init__(self, raw, buffer_size)
|
||||||
BufferedWriter.__init__(self, raw, buffer_size, max_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):
|
def seek(self, pos, whence=0):
|
||||||
self.flush()
|
self.flush()
|
||||||
# First do the raw seek, then empty the read buffer, so that
|
# First do the raw seek, then empty the read buffer, so that
|
||||||
|
@ -700,19 +764,20 @@ class BufferedRandom(BufferedReader, BufferedWriter):
|
||||||
else:
|
else:
|
||||||
return self.raw.tell() - len(self._read_buf)
|
return self.raw.tell() - len(self._read_buf)
|
||||||
|
|
||||||
def read(self, n=None):
|
def read(self, n=-1):
|
||||||
self.flush()
|
self.flush()
|
||||||
return BufferedReader.read(self, n)
|
return BufferedReader.read(self, n)
|
||||||
|
|
||||||
|
def readinto(self, b):
|
||||||
|
self.flush()
|
||||||
|
return BufferedReader.readinto(self, b)
|
||||||
|
|
||||||
def write(self, b):
|
def write(self, b):
|
||||||
if self._read_buf:
|
if self._read_buf:
|
||||||
self.raw.seek(-len(self._read_buf), 1) # Undo readahead
|
self.raw.seek(-len(self._read_buf), 1) # Undo readahead
|
||||||
self._read_buf = b""
|
self._read_buf = b""
|
||||||
return BufferedWriter.write(self, b)
|
return BufferedWriter.write(self, b)
|
||||||
|
|
||||||
def flush(self):
|
|
||||||
BufferedWriter.flush(self)
|
|
||||||
|
|
||||||
|
|
||||||
# XXX That's not the right base class
|
# XXX That's not the right base class
|
||||||
class TextIOBase(BufferedIOBase):
|
class TextIOBase(BufferedIOBase):
|
||||||
|
@ -807,12 +872,6 @@ class TextIOWrapper(TextIOBase):
|
||||||
def closed(self):
|
def closed(self):
|
||||||
return self.buffer.closed
|
return self.buffer.closed
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
try:
|
|
||||||
self.flush()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def fileno(self):
|
def fileno(self):
|
||||||
return self.buffer.fileno()
|
return self.buffer.fileno()
|
||||||
|
|
||||||
|
@ -841,7 +900,7 @@ class TextIOWrapper(TextIOBase):
|
||||||
res = self._pending
|
res = self._pending
|
||||||
if n < 0:
|
if n < 0:
|
||||||
res += decoder.decode(self.buffer.read(), True)
|
res += decoder.decode(self.buffer.read(), True)
|
||||||
self._pending = ''
|
self._pending = ""
|
||||||
return res
|
return res
|
||||||
else:
|
else:
|
||||||
while len(res) < n:
|
while len(res) < n:
|
||||||
|
|
|
@ -64,7 +64,7 @@ class MockNonBlockWriterIO(io.RawIOBase):
|
||||||
self._write_stack.append(b[:])
|
self._write_stack.append(b[:])
|
||||||
n = self.bs.pop(0)
|
n = self.bs.pop(0)
|
||||||
if (n < 0):
|
if (n < 0):
|
||||||
raise io.BlockingIO(0, "test blocking", -n)
|
raise io.BlockingIOError(0, "test blocking", -n)
|
||||||
else:
|
else:
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
@ -145,20 +145,6 @@ class IOTest(unittest.TestCase):
|
||||||
self.read_ops(f)
|
self.read_ops(f)
|
||||||
f.close()
|
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:
|
class MemorySeekTestMixin:
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue