Kaydet (Commit) 4f0db6e4 authored tarafından Guido van Rossum's avatar Guido van Rossum

Cleanup.

Add closed attribute.
Support int argument to open() -- wrapping a file descriptor.
For b/w compat, support readline(n).
Support readlines() and readlines(n).
Flush on __del__.
Added some XXX comments.
üst b0428159
...@@ -24,8 +24,8 @@ import sys ...@@ -24,8 +24,8 @@ import sys
import codecs import codecs
import warnings import warnings
DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
DEFAULT_MAX_BUFFER_SIZE = 16 * 1024 # bytes DEFAULT_MAX_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SIZE
class BlockingIO(IOError): class BlockingIO(IOError):
...@@ -35,11 +35,12 @@ class BlockingIO(IOError): ...@@ -35,11 +35,12 @@ class BlockingIO(IOError):
self.characters_written = characters_written self.characters_written = characters_written
def open(filename, mode="r", buffering=None, *, encoding=None): def open(file, mode="r", buffering=None, *, encoding=None):
"""Replacement for the built-in open function. """Replacement for the built-in open function.
Args: Args:
filename: string giving the name of the file to be opened file: string giving the name of the file to be opened;
or integer file descriptor of the file to be wrapped (*)
mode: optional mode string; see below mode: optional mode string; see below
buffering: optional int >= 0 giving the buffer size; values buffering: optional int >= 0 giving the buffer size; values
can be: 0 = unbuffered, 1 = line buffered, can be: 0 = unbuffered, 1 = line buffered,
...@@ -47,6 +48,10 @@ def open(filename, mode="r", buffering=None, *, encoding=None): ...@@ -47,6 +48,10 @@ def open(filename, mode="r", buffering=None, *, encoding=None):
encoding: optional string giving the text encoding (*must* be given encoding: optional string giving the text encoding (*must* be given
as a keyword argument) as a keyword argument)
(*) If a file descriptor is given, it is closed when the returned
I/O object is closed. If you don't want this to happen, use
os.dup() to create a duplicate file descriptor.
Mode strings characters: Mode strings characters:
'r': open for reading (default) 'r': open for reading (default)
'w': open for writing, truncating the file first 'w': open for writing, truncating the file first
...@@ -65,10 +70,10 @@ def open(filename, mode="r", buffering=None, *, encoding=None): ...@@ -65,10 +70,10 @@ def open(filename, mode="r", buffering=None, *, encoding=None):
binary stream, a buffered binary stream, or a buffered text binary stream, a buffered binary stream, or a buffered text
stream, open for reading and/or writing. stream, open for reading and/or writing.
""" """
assert isinstance(filename, basestring) assert isinstance(file, (basestring, int)), repr(file)
assert isinstance(mode, basestring) assert isinstance(mode, basestring), repr(mode)
assert buffering is None or isinstance(buffering, int) assert buffering is None or isinstance(buffering, int), repr(buffering)
assert encoding is None or isinstance(encoding, basestring) assert encoding is None or isinstance(encoding, basestring), repr(encoding)
modes = set(mode) modes = set(mode)
if modes - set("arwb+tU") or len(mode) > len(modes): if modes - set("arwb+tU") or len(mode) > len(modes):
raise ValueError("invalid mode: %r" % mode) raise ValueError("invalid mode: %r" % mode)
...@@ -78,7 +83,7 @@ def open(filename, mode="r", buffering=None, *, encoding=None): ...@@ -78,7 +83,7 @@ def open(filename, mode="r", buffering=None, *, encoding=None):
updating = "+" in modes updating = "+" in modes
text = "t" in modes text = "t" in modes
binary = "b" in modes binary = "b" in modes
if not reading and not writing and not appending and "U" in modes: if "U" in modes and not (reading or writing or appending):
reading = True reading = True
if text and binary: if text and binary:
raise ValueError("can't have text and binary mode at once") raise ValueError("can't have text and binary mode at once")
...@@ -88,7 +93,7 @@ def open(filename, mode="r", buffering=None, *, encoding=None): ...@@ -88,7 +93,7 @@ def open(filename, mode="r", buffering=None, *, encoding=None):
raise ValueError("must have exactly one of read/write/append mode") raise ValueError("must have exactly one of read/write/append mode")
if binary and encoding is not None: if binary and encoding is not None:
raise ValueError("binary mode doesn't take an encoding") raise ValueError("binary mode doesn't take an encoding")
raw = FileIO(filename, raw = FileIO(file,
(reading and "r" or "") + (reading and "r" or "") +
(writing and "w" or "") + (writing and "w" or "") +
(appending and "a" or "") + (appending and "a" or "") +
...@@ -137,6 +142,10 @@ class RawIOBase: ...@@ -137,6 +142,10 @@ class RawIOBase:
readinto() as a primitive operation. readinto() as a primitive operation.
""" """
def _unsupported(self, name):
raise IOError("%s.%s() not supported" % (self.__class__.__name__,
name))
def read(self, n): def read(self, n):
"""read(n: int) -> bytes. Read and return up to n bytes. """read(n: int) -> bytes. Read and return up to n bytes.
...@@ -154,14 +163,14 @@ class RawIOBase: ...@@ -154,14 +163,14 @@ class RawIOBase:
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.
""" """
raise IOError(".readinto() not supported") self._unsupported("readinto")
def write(self, b): def write(self, b):
"""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).
""" """
raise IOError(".write() not supported") self._unsupported("write")
def seek(self, pos, whence=0): def seek(self, pos, whence=0):
"""seek(pos: int, whence: int = 0) -> None. Change stream position. """seek(pos: int, whence: int = 0) -> None. Change stream position.
...@@ -171,23 +180,29 @@ class RawIOBase: ...@@ -171,23 +180,29 @@ class RawIOBase:
1 Current position - whence may be negative; 1 Current position - whence may be negative;
2 End of stream - whence usually negative. 2 End of stream - whence usually negative.
""" """
raise IOError(".seek() not supported") self._unsupported("seek")
def tell(self): def tell(self):
"""tell() -> int. Return current stream position.""" """tell() -> int. Return current stream position."""
raise IOError(".tell() not supported") self._unsupported("tell")
def truncate(self, pos=None): def truncate(self, pos=None):
"""truncate(size: int = None) -> None. Truncate file to size bytes. """truncate(size: int = None) -> None. Truncate file to size bytes.
Size defaults to the current IO position as reported by tell(). Size defaults to the current IO position as reported by tell().
""" """
raise IOError(".truncate() not supported") self._unsupported("truncate")
def close(self): def close(self):
"""close() -> None. Close IO object.""" """close() -> None. Close IO object."""
pass 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): def seekable(self):
"""seekable() -> bool. Return whether object supports random access. """seekable() -> bool. Return whether object supports random access.
...@@ -223,7 +238,7 @@ class RawIOBase: ...@@ -223,7 +238,7 @@ class RawIOBase:
Raises IOError if the IO object does not use a file descriptor. Raises IOError if the IO object does not use a file descriptor.
""" """
raise IOError(".fileno() not supported") self._unsupported("fileno")
class _PyFileIO(RawIOBase): class _PyFileIO(RawIOBase):
...@@ -232,9 +247,12 @@ class _PyFileIO(RawIOBase): ...@@ -232,9 +247,12 @@ class _PyFileIO(RawIOBase):
# XXX More docs # XXX More docs
def __init__(self, filename, mode): def __init__(self, file, mode):
self._seekable = None self._seekable = None
self._mode = mode self._mode = mode
if isinstance(file, int):
self._fd = file
return
if mode == "r": if mode == "r":
flags = os.O_RDONLY flags = os.O_RDONLY
elif mode == "w": elif mode == "w":
...@@ -242,10 +260,10 @@ class _PyFileIO(RawIOBase): ...@@ -242,10 +260,10 @@ class _PyFileIO(RawIOBase):
elif mode == "r+": elif mode == "r+":
flags = os.O_RDWR flags = os.O_RDWR
else: else:
assert 0, "unsupported mode %r (for now)" % mode assert False, "unsupported mode %r (for now)" % mode
if hasattr(os, "O_BINARY"): if hasattr(os, "O_BINARY"):
flags |= os.O_BINARY flags |= os.O_BINARY
self._fd = os.open(filename, flags) self._fd = os.open(file, flags)
def readinto(self, b): def readinto(self, b):
# XXX We really should have os.readinto() # XXX We really should have os.readinto()
...@@ -276,6 +294,10 @@ class _PyFileIO(RawIOBase): ...@@ -276,6 +294,10 @@ class _PyFileIO(RawIOBase):
if fd >= 0: if fd >= 0:
os.close(fd) os.close(fd)
@property
def closed(self):
return self._fd >= 0
def readable(self): def readable(self):
return "r" in self._mode or "+" in self._mode return "r" in self._mode or "+" in self._mode
...@@ -316,10 +338,13 @@ class SocketIO(RawIOBase): ...@@ -316,10 +338,13 @@ class SocketIO(RawIOBase):
# XXX More docs # XXX More docs
_closed = True
def __init__(self, sock, mode): def __init__(self, sock, mode):
assert mode in ("r", "w", "rw") assert mode in ("r", "w", "rw")
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)
...@@ -328,8 +353,13 @@ class SocketIO(RawIOBase): ...@@ -328,8 +353,13 @@ class SocketIO(RawIOBase):
return self._sock.send(b) return self._sock.send(b)
def close(self): def close(self):
self._closed = True
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
...@@ -352,6 +382,7 @@ class _MemoryIOBase(RawIOBase): ...@@ -352,6 +382,7 @@ class _MemoryIOBase(RawIOBase):
return self._buffer return self._buffer
def read(self, n=None): def read(self, n=None):
# XXX Shouldn't this support n < 0 too?
if n is None: if n is None:
n = len(self._buffer) n = len(self._buffer)
assert n >= 0 assert n >= 0
...@@ -432,24 +463,32 @@ class StringIO(_MemoryIOBase): ...@@ -432,24 +463,32 @@ class StringIO(_MemoryIOBase):
_MemoryIOBase.__init__(self, buffer) _MemoryIOBase.__init__(self, buffer)
# XXX Isn't this the wrong base class?
class BufferedIOBase(RawIOBase): class BufferedIOBase(RawIOBase):
"""Base class for buffered IO objects.""" """Base class for buffered IO objects."""
def flush(self): def flush(self):
"""Flush the buffer to the underlying raw IO object.""" """Flush the buffer to the underlying raw IO object."""
raise IOError(".flush() unsupported") self._unsupported("flush")
def seekable(self): def seekable(self):
return self.raw.seekable() return self.raw.seekable()
def fileno(self):
return self.raw.fileno()
class BufferedReader(BufferedIOBase): def close(self):
self.raw.close()
"""Buffer for a readable sequential RawIO object. @property
def closed(self):
return self.raw.closed
Does not allow random access (seek, tell).
""" class BufferedReader(BufferedIOBase):
"""Buffer for a readable sequential RawIO object."""
def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE): def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE):
"""Create a new buffered reader using the given readable raw IO object. """Create a new buffered reader using the given readable raw IO object.
...@@ -458,8 +497,6 @@ class BufferedReader(BufferedIOBase): ...@@ -458,8 +497,6 @@ class BufferedReader(BufferedIOBase):
self.raw = raw self.raw = raw
self._read_buf = b"" self._read_buf = b""
self.buffer_size = buffer_size self.buffer_size = buffer_size
if hasattr(raw, 'fileno'):
self.fileno = raw.fileno
def read(self, n=None): def read(self, n=None):
"""Read n bytes. """Read n bytes.
...@@ -469,7 +506,8 @@ class BufferedReader(BufferedIOBase): ...@@ -469,7 +506,8 @@ class BufferedReader(BufferedIOBase):
mode. If n is None, read until EOF or until read() would mode. If n is None, read until EOF or until read() would
block. block.
""" """
# XXX n == 0 should return b""? n < 0 should be the same as n is None? # 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 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 is None or len(self._read_buf) < n:
...@@ -493,9 +531,6 @@ class BufferedReader(BufferedIOBase): ...@@ -493,9 +531,6 @@ class BufferedReader(BufferedIOBase):
def readable(self): def readable(self):
return True return True
def fileno(self):
return self.raw.fileno()
def flush(self): def flush(self):
# Flush is a no-op # Flush is a no-op
pass pass
...@@ -509,9 +544,6 @@ class BufferedReader(BufferedIOBase): ...@@ -509,9 +544,6 @@ class BufferedReader(BufferedIOBase):
self.raw.seek(pos, whence) self.raw.seek(pos, whence)
self._read_buf = b"" self._read_buf = b""
def close(self):
self.raw.close()
class BufferedWriter(BufferedIOBase): class BufferedWriter(BufferedIOBase):
...@@ -527,7 +559,7 @@ class BufferedWriter(BufferedIOBase): ...@@ -527,7 +559,7 @@ class BufferedWriter(BufferedIOBase):
def write(self, b): def write(self, b):
# XXX we can implement some more tricks to try and avoid partial writes # XXX we can implement some more tricks to try and avoid partial writes
assert issubclass(type(b), bytes) ##assert issubclass(type(b), bytes)
if len(self._write_buf) > self.buffer_size: if len(self._write_buf) > self.buffer_size:
# We're full, so let's pre-flush the buffer # We're full, so let's pre-flush the buffer
try: try:
...@@ -536,7 +568,7 @@ class BufferedWriter(BufferedIOBase): ...@@ -536,7 +568,7 @@ class BufferedWriter(BufferedIOBase):
# 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 BlockingIO(e.errno, e.strerror, 0)
self._write_buf += 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()
...@@ -571,17 +603,18 @@ class BufferedWriter(BufferedIOBase): ...@@ -571,17 +603,18 @@ class BufferedWriter(BufferedIOBase):
self.flush() self.flush()
self.raw.seek(pos, whence) self.raw.seek(pos, whence)
def fileno(self):
return self.raw.fileno()
def close(self): def close(self):
self.flush() self.flush()
self.raw.close() self.raw.close()
def __del__(self): def __del__(self):
self.close() try:
self.flush()
except:
pass
# XXX Maybe use containment instead of multiple inheritance?
class BufferedRWPair(BufferedReader, BufferedWriter): class BufferedRWPair(BufferedReader, BufferedWriter):
"""A buffered reader and writer object together. """A buffered reader and writer object together.
...@@ -596,7 +629,7 @@ class BufferedRWPair(BufferedReader, BufferedWriter): ...@@ -596,7 +629,7 @@ class BufferedRWPair(BufferedReader, BufferedWriter):
max_buffer_size=DEFAULT_MAX_BUFFER_SIZE): max_buffer_size=DEFAULT_MAX_BUFFER_SIZE):
assert reader.readable() assert reader.readable()
assert writer.writable() assert writer.writable()
BufferedReader.__init__(self, reader) BufferedReader.__init__(self, reader, buffer_size)
BufferedWriter.__init__(self, writer, buffer_size, max_buffer_size) BufferedWriter.__init__(self, writer, buffer_size, max_buffer_size)
self.reader = reader self.reader = reader
self.writer = writer self.writer = writer
...@@ -627,7 +660,12 @@ class BufferedRWPair(BufferedReader, BufferedWriter): ...@@ -627,7 +660,12 @@ class BufferedRWPair(BufferedReader, BufferedWriter):
self.reader.close() self.reader.close()
self.writer.close() self.writer.close()
@property
def closed(self):
return self.reader.closed or self.writer.closed
# XXX Maybe use containment instead of multiple inheritance?
class BufferedRandom(BufferedReader, BufferedWriter): class BufferedRandom(BufferedReader, BufferedWriter):
# XXX docstring # XXX docstring
...@@ -635,7 +673,7 @@ class BufferedRandom(BufferedReader, BufferedWriter): ...@@ -635,7 +673,7 @@ class BufferedRandom(BufferedReader, BufferedWriter):
def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE, def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE,
max_buffer_size=DEFAULT_MAX_BUFFER_SIZE): max_buffer_size=DEFAULT_MAX_BUFFER_SIZE):
assert raw.seekable() assert raw.seekable()
BufferedReader.__init__(self, raw) 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): def readable(self):
...@@ -675,10 +713,8 @@ class BufferedRandom(BufferedReader, BufferedWriter): ...@@ -675,10 +713,8 @@ class BufferedRandom(BufferedReader, BufferedWriter):
def flush(self): def flush(self):
BufferedWriter.flush(self) BufferedWriter.flush(self)
def close(self):
self.raw.close()
# XXX That's not the right base class
class TextIOBase(BufferedIOBase): class TextIOBase(BufferedIOBase):
"""Base class for text I/O. """Base class for text I/O.
...@@ -692,19 +728,18 @@ class TextIOBase(BufferedIOBase): ...@@ -692,19 +728,18 @@ class TextIOBase(BufferedIOBase):
Read from underlying buffer until we have n characters or we hit EOF. Read from underlying buffer until we have n characters or we hit EOF.
If n is negative or omitted, read until EOF. If n is negative or omitted, read until EOF.
""" """
raise IOError(".read() not supported") self._unsupported("read")
def write(self, s: str): def write(self, s: str):
"""write(s: str) -> None. Write string s to stream. """write(s: str) -> None. Write string s to stream."""
""" self._unsupported("write")
raise IOError(".write() not supported")
def readline(self) -> str: def readline(self) -> str:
"""readline() -> str. Read until newline or EOF. """readline() -> str. Read until newline or EOF.
Returns an empty string if EOF is hit immediately. Returns an empty string if EOF is hit immediately.
""" """
raise IOError(".readline() not supported") self._unsupported("readline")
def __iter__(self): def __iter__(self):
"""__iter__() -> Iterator. Return line iterator (actually just self). """__iter__() -> Iterator. Return line iterator (actually just self).
...@@ -712,10 +747,9 @@ class TextIOBase(BufferedIOBase): ...@@ -712,10 +747,9 @@ class TextIOBase(BufferedIOBase):
return self return self
def next(self): def next(self):
"""Same as readline() except raises StopIteration on immediate EOF. """Same as readline() except raises StopIteration on immediate EOF."""
"""
line = self.readline() line = self.readline()
if line == '': if not line:
raise StopIteration raise StopIteration
return line return line
...@@ -753,9 +787,7 @@ class TextIOWrapper(TextIOBase): ...@@ -753,9 +787,7 @@ class TextIOWrapper(TextIOBase):
raise IOError("illegal newline %s" % newline) # XXX: ValueError? raise IOError("illegal newline %s" % newline) # XXX: ValueError?
if encoding is None: if encoding is None:
# XXX This is questionable # XXX This is questionable
encoding = sys.getfilesystemencoding() encoding = sys.getfilesystemencoding() or "latin-1"
if encoding is None:
encoding = "latin-1" # XXX, but this is best for transparancy
self.buffer = buffer self.buffer = buffer
self._encoding = encoding self._encoding = encoding
...@@ -764,11 +796,34 @@ class TextIOWrapper(TextIOBase): ...@@ -764,11 +796,34 @@ class TextIOWrapper(TextIOBase):
self._decoder = None self._decoder = None
self._pending = '' self._pending = ''
def flush(self):
self.buffer.flush()
def close(self):
self.flush()
self.buffer.close()
@property
def closed(self):
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()
def write(self, s: str): def write(self, s: str):
return self.buffer.write(s.encode(self._encoding)) b = s.encode(self._encoding)
if isinstance(b, str):
b = bytes(b)
n = self.buffer.write(b)
if "\n" in s:
self.flush()
return n
def _get_decoder(self): def _get_decoder(self):
make_decoder = codecs.getincrementaldecoder(self._encoding) make_decoder = codecs.getincrementaldecoder(self._encoding)
...@@ -797,7 +852,15 @@ class TextIOWrapper(TextIOBase): ...@@ -797,7 +852,15 @@ class TextIOWrapper(TextIOBase):
self._pending = res[n:] self._pending = res[n:]
return res[:n] return res[:n]
def readline(self): def readline(self, limit=None):
if limit is not None:
# XXX Hack to support limit arg
line = self.readline()
if len(line) <= limit:
return line
line, self._pending = line[:limit], line[limit:] + self._pending
return line
line = self._pending line = self._pending
start = 0 start = 0
decoder = self._decoder or self._get_decoder() decoder = self._decoder or self._get_decoder()
...@@ -833,11 +896,11 @@ class TextIOWrapper(TextIOBase): ...@@ -833,11 +896,11 @@ class TextIOWrapper(TextIOBase):
while True: while True:
data = self.buffer.read(64) data = self.buffer.read(64)
more_line = decoder.decode(data, not data) more_line = decoder.decode(data, not data)
if more_line != "" or not data: if more_line or not data:
break break
if more_line == "": if not more_line:
ending = '' ending = ""
endpos = len(line) endpos = len(line)
break break
...@@ -848,7 +911,7 @@ class TextIOWrapper(TextIOBase): ...@@ -848,7 +911,7 @@ class TextIOWrapper(TextIOBase):
# XXX Update self.newlines here if we want to support that # XXX Update self.newlines here if we want to support that
if self._fix_newlines and ending != "\n" and ending != '': if self._fix_newlines and ending not in ("\n", ""):
return line[:endpos] + "\n" return line[:endpos] + "\n"
else: else:
return line[:nextpos] return line[:nextpos]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment