@@ -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
849880class 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
0 commit comments