Skip to content

Commit df02c65

Browse files
author
alexandre.vassalotti
committed
Added fast alternate io.BytesIO implementation and its test suite.
Removed old test suite for StringIO. Modified truncate() to imply a seek to given argument value. git-svn-id: http://svn.python.org/projects/python/branches/py3k@62778 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 2ac7f09 commit df02c65

10 files changed

Lines changed: 1245 additions & 193 deletions

File tree

Lib/io.py

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ def readline(self, limit: int = -1) -> bytes:
490490
terminator(s) recognized.
491491
"""
492492
# For backwards compatibility, a (slowish) readline().
493+
self._checkClosed()
493494
if hasattr(self, "peek"):
494495
def nreadahead():
495496
readahead = self.peek(1)
@@ -531,7 +532,7 @@ def readlines(self, hint=None):
531532
lines will be read if the total size (in bytes/characters) of all
532533
lines so far exceeds hint.
533534
"""
534-
if hint is None:
535+
if hint is None or hint <= 0:
535536
return list(self)
536537
n = 0
537538
lines = []
@@ -726,6 +727,8 @@ def truncate(self, pos=None):
726727

727728
if pos is None:
728729
pos = self.tell()
730+
# XXX: Should seek() be used, instead of passing the position
731+
# XXX directly to truncate?
729732
return self.raw.truncate(pos)
730733

731734
### Flush and close ###
@@ -765,7 +768,7 @@ def isatty(self):
765768
return self.raw.isatty()
766769

767770

768-
class BytesIO(BufferedIOBase):
771+
class _BytesIO(BufferedIOBase):
769772

770773
"""Buffered I/O implementation using an in-memory bytes buffer."""
771774

@@ -779,13 +782,19 @@ def __init__(self, initial_bytes=None):
779782
def getvalue(self):
780783
"""Return the bytes value (contents) of the buffer
781784
"""
785+
if self.closed:
786+
raise ValueError("getvalue on closed file")
782787
return bytes(self._buffer)
783788

784789
def read(self, n=None):
790+
if self.closed:
791+
raise ValueError("read from closed file")
785792
if n is None:
786793
n = -1
787794
if n < 0:
788795
n = len(self._buffer)
796+
if len(self._buffer) <= self._pos:
797+
return self._buffer[:0]
789798
newpos = min(len(self._buffer), self._pos + n)
790799
b = self._buffer[self._pos : newpos]
791800
self._pos = newpos
@@ -802,6 +811,8 @@ def write(self, b):
802811
if isinstance(b, str):
803812
raise TypeError("can't write str to binary stream")
804813
n = len(b)
814+
if n == 0:
815+
return 0
805816
newpos = self._pos + n
806817
if newpos > len(self._buffer):
807818
# Inserts null bytes between the current end of the file
@@ -813,28 +824,38 @@ def write(self, b):
813824
return n
814825

815826
def seek(self, pos, whence=0):
827+
if self.closed:
828+
raise ValueError("seek on closed file")
816829
try:
817830
pos = pos.__index__()
818831
except AttributeError as err:
819832
raise TypeError("an integer is required") from err
820833
if whence == 0:
821834
self._pos = max(0, pos)
835+
if pos < 0:
836+
raise ValueError("negative seek position %r" % (pos,))
822837
elif whence == 1:
823838
self._pos = max(0, self._pos + pos)
824839
elif whence == 2:
825840
self._pos = max(0, len(self._buffer) + pos)
826841
else:
827-
raise IOError("invalid whence value")
842+
raise ValueError("invalid whence value")
828843
return self._pos
829844

830845
def tell(self):
846+
if self.closed:
847+
raise ValueError("tell on closed file")
831848
return self._pos
832849

833850
def truncate(self, pos=None):
851+
if self.closed:
852+
raise ValueError("truncate on closed file")
834853
if pos is None:
835854
pos = self._pos
855+
elif pos < 0:
856+
raise ValueError("negative truncate position %r" % (pos,))
836857
del self._buffer[pos:]
837-
return pos
858+
return self.seek(pos)
838859

839860
def readable(self):
840861
return True
@@ -845,6 +866,16 @@ def writable(self):
845866
def seekable(self):
846867
return True
847868

869+
# Use the faster implementation of BytesIO if available
870+
try:
871+
import _bytesio
872+
873+
class BytesIO(_bytesio._BytesIO, BufferedIOBase):
874+
__doc__ = _bytesio._BytesIO.__doc__
875+
876+
except ImportError:
877+
BytesIO = _BytesIO
878+
848879

849880
class BufferedReader(_BufferedIOMixin):
850881

@@ -978,6 +1009,12 @@ def write(self, b):
9781009
raise BlockingIOError(e.errno, e.strerror, overage)
9791010
return written
9801011

1012+
def truncate(self, pos=None):
1013+
self.flush()
1014+
if pos is None:
1015+
pos = self.raw.tell()
1016+
return self.raw.truncate(pos)
1017+
9811018
def flush(self):
9821019
if self.closed:
9831020
raise ValueError("flush of closed file")
@@ -1097,6 +1134,13 @@ def tell(self):
10971134
else:
10981135
return self.raw.tell() - len(self._read_buf)
10991136

1137+
def truncate(self, pos=None):
1138+
if pos is None:
1139+
pos = self.tell()
1140+
# Use seek to flush the read buffer.
1141+
self.seek(pos)
1142+
return BufferedWriter.truncate(self)
1143+
11001144
def read(self, n=None):
11011145
if n is None:
11021146
n = -1
@@ -1145,11 +1189,7 @@ def write(self, s: str) -> int:
11451189

11461190
def truncate(self, pos: int = None) -> int:
11471191
"""Truncate size to pos."""
1148-
self.flush()
1149-
if pos is None:
1150-
pos = self.tell()
1151-
self.seek(pos)
1152-
return self.buffer.truncate()
1192+
self._unsupported("truncate")
11531193

11541194
def readline(self) -> str:
11551195
"""Read until newline or EOF.
@@ -1346,6 +1386,12 @@ def line_buffering(self):
13461386
def seekable(self):
13471387
return self._seekable
13481388

1389+
def readable(self):
1390+
return self.buffer.readable()
1391+
1392+
def writable(self):
1393+
return self.buffer.writable()
1394+
13491395
def flush(self):
13501396
self.buffer.flush()
13511397
self._telling = self._seekable
@@ -1539,7 +1585,16 @@ def tell(self):
15391585
finally:
15401586
decoder.setstate(saved_state)
15411587

1588+
def truncate(self, pos=None):
1589+
self.flush()
1590+
if pos is None:
1591+
pos = self.tell()
1592+
self.seek(pos)
1593+
return self.buffer.truncate()
1594+
15421595
def seek(self, cookie, whence=0):
1596+
if self.closed:
1597+
raise ValueError("tell on closed file")
15431598
if not self._seekable:
15441599
raise IOError("underlying stream is not seekable")
15451600
if whence == 1: # seek relative to current position
@@ -1626,6 +1681,8 @@ def __next__(self):
16261681
return line
16271682

16281683
def readline(self, limit=None):
1684+
if self.closed:
1685+
raise ValueError("read from closed file")
16291686
if limit is None:
16301687
limit = -1
16311688

Lib/test/test_StringIO.py

Lines changed: 0 additions & 127 deletions
This file was deleted.

Lib/test/test_io.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def write_ops(self, f):
9898
self.assertEqual(f.seek(-1, 2), 13)
9999
self.assertEqual(f.tell(), 13)
100100
self.assertEqual(f.truncate(12), 12)
101-
self.assertEqual(f.tell(), 13)
101+
self.assertEqual(f.tell(), 12)
102102
self.assertRaises(TypeError, f.seek, 0.0)
103103

104104
def read_ops(self, f, buffered=False):
@@ -143,7 +143,7 @@ def large_file_ops(self, f):
143143
self.assertEqual(f.tell(), self.LARGE + 2)
144144
self.assertEqual(f.seek(0, 2), self.LARGE + 2)
145145
self.assertEqual(f.truncate(self.LARGE + 1), self.LARGE + 1)
146-
self.assertEqual(f.tell(), self.LARGE + 2)
146+
self.assertEqual(f.tell(), self.LARGE + 1)
147147
self.assertEqual(f.seek(0, 2), self.LARGE + 1)
148148
self.assertEqual(f.seek(-1, 2), self.LARGE)
149149
self.assertEqual(f.read(2), b"x")
@@ -727,6 +727,7 @@ def testNewlinesOutput(self):
727727
txt.write("BB\nCCC\n")
728728
txt.write("X\rY\r\nZ")
729729
txt.flush()
730+
self.assertEquals(buf.closed, False)
730731
self.assertEquals(buf.getvalue(), expected)
731732

732733
def testNewlines(self):
@@ -807,7 +808,8 @@ def testNewlinesOutput(self):
807808
txt = io.TextIOWrapper(buf, encoding="ascii", newline=newline)
808809
txt.write(data)
809810
txt.close()
810-
self.assertEquals(buf.getvalue(), expected)
811+
self.assertEquals(buf.closed, True)
812+
self.assertRaises(ValueError, buf.getvalue)
811813
finally:
812814
os.linesep = save_linesep
813815

Lib/test/test_largefile.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,15 @@ def test_truncate(self):
120120
newsize -= 1
121121
f.seek(42)
122122
f.truncate(newsize)
123-
self.assertEqual(f.tell(), 42) # else pointer moved
124-
f.seek(0, 2)
125123
self.assertEqual(f.tell(), newsize) # else wasn't truncated
124+
f.seek(0, 2)
125+
self.assertEqual(f.tell(), newsize)
126126
# XXX truncate(larger than true size) is ill-defined
127127
# across platform; cut it waaaaay back
128128
f.seek(0)
129129
f.truncate(1)
130-
self.assertEqual(f.tell(), 0) # else pointer moved
130+
self.assertEqual(f.tell(), 1) # else pointer moved
131+
f.seek(0)
131132
self.assertEqual(len(f.read()), 1) # else wasn't truncated
132133

133134
def test_main():

0 commit comments

Comments
 (0)