Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 77250f4

Browse files
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.
1 parent 5d8da20 commit 77250f4

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)