mirror of https://github.com/python/cpython
Fix the rest of issue 1400, by introducing a proper implementation of
line buffering. The TextIOWrapper class no longer calls isatty() on every write() call.
This commit is contained in:
parent
786720876c
commit
f64db9f6db
20
Lib/io.py
20
Lib/io.py
|
@ -151,8 +151,10 @@ def open(file, mode="r", buffering=None, encoding=None, errors=None,
|
||||||
closefd)
|
closefd)
|
||||||
if buffering is None:
|
if buffering is None:
|
||||||
buffering = -1
|
buffering = -1
|
||||||
if buffering < 0 and raw.isatty():
|
line_buffering = False
|
||||||
buffering = 1
|
if buffering == 1 or buffering < 0 and raw.isatty():
|
||||||
|
buffering = -1
|
||||||
|
line_buffering = True
|
||||||
if buffering < 0:
|
if buffering < 0:
|
||||||
buffering = DEFAULT_BUFFER_SIZE
|
buffering = DEFAULT_BUFFER_SIZE
|
||||||
try:
|
try:
|
||||||
|
@ -182,7 +184,7 @@ def open(file, mode="r", buffering=None, encoding=None, errors=None,
|
||||||
buffer.name = file
|
buffer.name = file
|
||||||
buffer.mode = mode
|
buffer.mode = mode
|
||||||
return buffer
|
return buffer
|
||||||
text = TextIOWrapper(buffer, encoding, errors, newline)
|
text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering)
|
||||||
text.name = file
|
text.name = file
|
||||||
text.mode = mode
|
text.mode = mode
|
||||||
return text
|
return text
|
||||||
|
@ -1133,7 +1135,8 @@ class TextIOWrapper(TextIOBase):
|
||||||
|
|
||||||
_CHUNK_SIZE = 128
|
_CHUNK_SIZE = 128
|
||||||
|
|
||||||
def __init__(self, buffer, encoding=None, errors=None, newline=None):
|
def __init__(self, buffer, encoding=None, errors=None, newline=None,
|
||||||
|
line_buffering=False):
|
||||||
if newline not in (None, "", "\n", "\r", "\r\n"):
|
if newline not in (None, "", "\n", "\r", "\r\n"):
|
||||||
raise ValueError("illegal newline value: %r" % (newline,))
|
raise ValueError("illegal newline value: %r" % (newline,))
|
||||||
if encoding is None:
|
if encoding is None:
|
||||||
|
@ -1160,6 +1163,7 @@ class TextIOWrapper(TextIOBase):
|
||||||
raise ValueError("invalid errors: %r" % errors)
|
raise ValueError("invalid errors: %r" % errors)
|
||||||
|
|
||||||
self.buffer = buffer
|
self.buffer = buffer
|
||||||
|
self._line_buffering = line_buffering
|
||||||
self._encoding = encoding
|
self._encoding = encoding
|
||||||
self._errors = errors
|
self._errors = errors
|
||||||
self._readuniversal = not newline
|
self._readuniversal = not newline
|
||||||
|
@ -1180,6 +1184,10 @@ class TextIOWrapper(TextIOBase):
|
||||||
def errors(self):
|
def errors(self):
|
||||||
return self._errors
|
return self._errors
|
||||||
|
|
||||||
|
@property
|
||||||
|
def line_buffering(self):
|
||||||
|
return self._line_buffering
|
||||||
|
|
||||||
# A word about _snapshot. This attribute is either None, or a
|
# A word about _snapshot. This attribute is either None, or a
|
||||||
# tuple (decoder_state, readahead, pending) where decoder_state is
|
# tuple (decoder_state, readahead, pending) where decoder_state is
|
||||||
# the second (integer) item of the decoder state, readahead is the
|
# the second (integer) item of the decoder state, readahead is the
|
||||||
|
@ -1218,13 +1226,13 @@ class TextIOWrapper(TextIOBase):
|
||||||
raise TypeError("can't write %s to text stream" %
|
raise TypeError("can't write %s to text stream" %
|
||||||
s.__class__.__name__)
|
s.__class__.__name__)
|
||||||
length = len(s)
|
length = len(s)
|
||||||
haslf = "\n" in s
|
haslf = (self._writetranslate or self._line_buffering) and "\n" in s
|
||||||
if haslf and self._writetranslate and self._writenl != "\n":
|
if haslf and self._writetranslate and self._writenl != "\n":
|
||||||
s = s.replace("\n", self._writenl)
|
s = s.replace("\n", self._writenl)
|
||||||
# XXX What if we were just reading?
|
# XXX What if we were just reading?
|
||||||
b = s.encode(self._encoding, self._errors)
|
b = s.encode(self._encoding, self._errors)
|
||||||
self.buffer.write(b)
|
self.buffer.write(b)
|
||||||
if haslf and self.isatty():
|
if self._line_buffering and (haslf or "\r" in s):
|
||||||
self.flush()
|
self.flush()
|
||||||
self._snapshot = None
|
self._snapshot = None
|
||||||
if self._decoder:
|
if self._decoder:
|
||||||
|
|
|
@ -5,10 +5,10 @@ test_cProfile
|
||||||
|
|
||||||
ncalls tottime percall cumtime percall filename:lineno(function)
|
ncalls tottime percall cumtime percall filename:lineno(function)
|
||||||
1 0.000 0.000 1.000 1.000 <string>:1(<module>)
|
1 0.000 0.000 1.000 1.000 <string>:1(<module>)
|
||||||
2 0.000 0.000 0.000 0.000 io.py:1193(flush)
|
2 0.000 0.000 0.000 0.000 io.py:1201(flush)
|
||||||
1 0.000 0.000 0.000 0.000 io.py:257(flush)
|
1 0.000 0.000 0.000 0.000 io.py:259(flush)
|
||||||
1 0.000 0.000 0.000 0.000 io.py:644(closed)
|
1 0.000 0.000 0.000 0.000 io.py:646(closed)
|
||||||
1 0.000 0.000 0.000 0.000 io.py:862(flush)
|
1 0.000 0.000 0.000 0.000 io.py:864(flush)
|
||||||
8 0.064 0.008 0.080 0.010 test_cProfile.py:103(subhelper)
|
8 0.064 0.008 0.080 0.010 test_cProfile.py:103(subhelper)
|
||||||
28 0.028 0.001 0.028 0.001 test_cProfile.py:115(__getattr__)
|
28 0.028 0.001 0.028 0.001 test_cProfile.py:115(__getattr__)
|
||||||
1 0.270 0.270 1.000 1.000 test_cProfile.py:30(testfunc)
|
1 0.270 0.270 1.000 1.000 test_cProfile.py:30(testfunc)
|
||||||
|
@ -30,11 +30,11 @@ test_cProfile
|
||||||
Function called...
|
Function called...
|
||||||
ncalls tottime cumtime
|
ncalls tottime cumtime
|
||||||
<string>:1(<module>) -> 1 0.270 1.000 test_cProfile.py:30(testfunc)
|
<string>:1(<module>) -> 1 0.270 1.000 test_cProfile.py:30(testfunc)
|
||||||
io.py:1193(flush) -> 1 0.000 0.000 io.py:257(flush)
|
io.py:1201(flush) -> 1 0.000 0.000 io.py:259(flush)
|
||||||
1 0.000 0.000 io.py:862(flush)
|
1 0.000 0.000 io.py:864(flush)
|
||||||
io.py:257(flush) ->
|
io.py:259(flush) ->
|
||||||
io.py:644(closed) ->
|
io.py:646(closed) ->
|
||||||
io.py:862(flush) -> 1 0.000 0.000 io.py:644(closed)
|
io.py:864(flush) -> 1 0.000 0.000 io.py:646(closed)
|
||||||
test_cProfile.py:103(subhelper) -> 16 0.016 0.016 test_cProfile.py:115(__getattr__)
|
test_cProfile.py:103(subhelper) -> 16 0.016 0.016 test_cProfile.py:115(__getattr__)
|
||||||
test_cProfile.py:115(__getattr__) ->
|
test_cProfile.py:115(__getattr__) ->
|
||||||
test_cProfile.py:30(testfunc) -> 1 0.014 0.130 test_cProfile.py:40(factorial)
|
test_cProfile.py:30(testfunc) -> 1 0.014 0.130 test_cProfile.py:40(factorial)
|
||||||
|
@ -53,7 +53,7 @@ test_cProfile.py:89(helper2_indirect) -> 2 0.006 0.040
|
||||||
test_cProfile.py:93(helper2) -> 8 0.064 0.080 test_cProfile.py:103(subhelper)
|
test_cProfile.py:93(helper2) -> 8 0.064 0.080 test_cProfile.py:103(subhelper)
|
||||||
8 0.000 0.008 {hasattr}
|
8 0.000 0.008 {hasattr}
|
||||||
{exec} -> 1 0.000 1.000 <string>:1(<module>)
|
{exec} -> 1 0.000 1.000 <string>:1(<module>)
|
||||||
2 0.000 0.000 io.py:1193(flush)
|
2 0.000 0.000 io.py:1201(flush)
|
||||||
{hasattr} -> 12 0.012 0.012 test_cProfile.py:115(__getattr__)
|
{hasattr} -> 12 0.012 0.012 test_cProfile.py:115(__getattr__)
|
||||||
{method 'append' of 'list' objects} ->
|
{method 'append' of 'list' objects} ->
|
||||||
{method 'disable' of '_lsprof.Profiler' objects} ->
|
{method 'disable' of '_lsprof.Profiler' objects} ->
|
||||||
|
@ -65,10 +65,10 @@ test_cProfile.py:93(helper2) -> 8 0.064 0.080
|
||||||
Function was called by...
|
Function was called by...
|
||||||
ncalls tottime cumtime
|
ncalls tottime cumtime
|
||||||
<string>:1(<module>) <- 1 0.000 1.000 {exec}
|
<string>:1(<module>) <- 1 0.000 1.000 {exec}
|
||||||
io.py:1193(flush) <- 2 0.000 0.000 {exec}
|
io.py:1201(flush) <- 2 0.000 0.000 {exec}
|
||||||
io.py:257(flush) <- 1 0.000 0.000 io.py:1193(flush)
|
io.py:259(flush) <- 1 0.000 0.000 io.py:1201(flush)
|
||||||
io.py:644(closed) <- 1 0.000 0.000 io.py:862(flush)
|
io.py:646(closed) <- 1 0.000 0.000 io.py:864(flush)
|
||||||
io.py:862(flush) <- 1 0.000 0.000 io.py:1193(flush)
|
io.py:864(flush) <- 1 0.000 0.000 io.py:1201(flush)
|
||||||
test_cProfile.py:103(subhelper) <- 8 0.064 0.080 test_cProfile.py:93(helper2)
|
test_cProfile.py:103(subhelper) <- 8 0.064 0.080 test_cProfile.py:93(helper2)
|
||||||
test_cProfile.py:115(__getattr__) <- 16 0.016 0.016 test_cProfile.py:103(subhelper)
|
test_cProfile.py:115(__getattr__) <- 16 0.016 0.016 test_cProfile.py:103(subhelper)
|
||||||
12 0.012 0.012 {hasattr}
|
12 0.012 0.012 {hasattr}
|
||||||
|
|
|
@ -10,10 +10,10 @@ test_profile
|
||||||
12 0.000 0.000 0.012 0.001 :0(hasattr)
|
12 0.000 0.000 0.012 0.001 :0(hasattr)
|
||||||
1 0.000 0.000 0.000 0.000 :0(setprofile)
|
1 0.000 0.000 0.000 0.000 :0(setprofile)
|
||||||
1 0.000 0.000 1.000 1.000 <string>:1(<module>)
|
1 0.000 0.000 1.000 1.000 <string>:1(<module>)
|
||||||
2 0.000 0.000 0.000 0.000 io.py:1193(flush)
|
2 0.000 0.000 0.000 0.000 io.py:1201(flush)
|
||||||
1 0.000 0.000 0.000 0.000 io.py:257(flush)
|
1 0.000 0.000 0.000 0.000 io.py:259(flush)
|
||||||
1 0.000 0.000 0.000 0.000 io.py:644(closed)
|
1 0.000 0.000 0.000 0.000 io.py:646(closed)
|
||||||
1 0.000 0.000 0.000 0.000 io.py:862(flush)
|
1 0.000 0.000 0.000 0.000 io.py:864(flush)
|
||||||
0 0.000 0.000 profile:0(profiler)
|
0 0.000 0.000 profile:0(profiler)
|
||||||
1 0.000 0.000 1.000 1.000 profile:0(testfunc())
|
1 0.000 0.000 1.000 1.000 profile:0(testfunc())
|
||||||
8 0.064 0.008 0.080 0.010 test_profile.py:103(subhelper)
|
8 0.064 0.008 0.080 0.010 test_profile.py:103(subhelper)
|
||||||
|
@ -33,15 +33,15 @@ Function called...
|
||||||
:0(append) ->
|
:0(append) ->
|
||||||
:0(exc_info) ->
|
:0(exc_info) ->
|
||||||
:0(exec) -> <string>:1(<module>)(1) 1.000
|
:0(exec) -> <string>:1(<module>)(1) 1.000
|
||||||
io.py:1193(flush)(2) 0.000
|
io.py:1201(flush)(2) 0.000
|
||||||
:0(hasattr) -> test_profile.py:115(__getattr__)(12) 0.028
|
:0(hasattr) -> test_profile.py:115(__getattr__)(12) 0.028
|
||||||
:0(setprofile) ->
|
:0(setprofile) ->
|
||||||
<string>:1(<module>) -> test_profile.py:30(testfunc)(1) 1.000
|
<string>:1(<module>) -> test_profile.py:30(testfunc)(1) 1.000
|
||||||
io.py:1193(flush) -> io.py:257(flush)(1) 0.000
|
io.py:1201(flush) -> io.py:259(flush)(1) 0.000
|
||||||
io.py:862(flush)(1) 0.000
|
io.py:864(flush)(1) 0.000
|
||||||
io.py:257(flush) ->
|
io.py:259(flush) ->
|
||||||
io.py:644(closed) ->
|
io.py:646(closed) ->
|
||||||
io.py:862(flush) -> io.py:644(closed)(1) 0.000
|
io.py:864(flush) -> io.py:646(closed)(1) 0.000
|
||||||
profile:0(profiler) -> profile:0(testfunc())(1) 1.000
|
profile:0(profiler) -> profile:0(testfunc())(1) 1.000
|
||||||
profile:0(testfunc()) -> :0(exec)(1) 1.000
|
profile:0(testfunc()) -> :0(exec)(1) 1.000
|
||||||
:0(setprofile)(1) 0.000
|
:0(setprofile)(1) 0.000
|
||||||
|
@ -74,10 +74,10 @@ Function was called by...
|
||||||
test_profile.py:93(helper2)(8) 0.400
|
test_profile.py:93(helper2)(8) 0.400
|
||||||
:0(setprofile) <- profile:0(testfunc())(1) 1.000
|
:0(setprofile) <- profile:0(testfunc())(1) 1.000
|
||||||
<string>:1(<module>) <- :0(exec)(1) 1.000
|
<string>:1(<module>) <- :0(exec)(1) 1.000
|
||||||
io.py:1193(flush) <- :0(exec)(2) 1.000
|
io.py:1201(flush) <- :0(exec)(2) 1.000
|
||||||
io.py:257(flush) <- io.py:1193(flush)(1) 0.000
|
io.py:259(flush) <- io.py:1201(flush)(1) 0.000
|
||||||
io.py:644(closed) <- io.py:862(flush)(1) 0.000
|
io.py:646(closed) <- io.py:864(flush)(1) 0.000
|
||||||
io.py:862(flush) <- io.py:1193(flush)(1) 0.000
|
io.py:864(flush) <- io.py:1201(flush)(1) 0.000
|
||||||
profile:0(profiler) <-
|
profile:0(profiler) <-
|
||||||
profile:0(testfunc()) <- profile:0(profiler)(1) 0.000
|
profile:0(testfunc()) <- profile:0(profiler)(1) 0.000
|
||||||
test_profile.py:103(subhelper) <- test_profile.py:93(helper2)(8) 0.400
|
test_profile.py:103(subhelper) <- test_profile.py:93(helper2)(8) 0.400
|
||||||
|
|
|
@ -496,6 +496,17 @@ class TextIOWrapperTest(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
test_support.unlink(test_support.TESTFN)
|
test_support.unlink(test_support.TESTFN)
|
||||||
|
|
||||||
|
def testLineBuffering(self):
|
||||||
|
r = io.BytesIO()
|
||||||
|
b = io.BufferedWriter(r, 1000)
|
||||||
|
t = io.TextIOWrapper(b, newline="\n", line_buffering=True)
|
||||||
|
t.write("X")
|
||||||
|
self.assertEquals(r.getvalue(), b"") # No flush happened
|
||||||
|
t.write("Y\nZ")
|
||||||
|
self.assertEquals(r.getvalue(), b"XY\nZ") # All got flushed
|
||||||
|
t.write("A\rB")
|
||||||
|
self.assertEquals(r.getvalue(), b"XY\nZA\rB")
|
||||||
|
|
||||||
def testEncodingErrorsReading(self):
|
def testEncodingErrorsReading(self):
|
||||||
# (1) default
|
# (1) default
|
||||||
b = io.BytesIO(b"abc\n\xff\n")
|
b = io.BytesIO(b"abc\n\xff\n")
|
||||||
|
@ -525,13 +536,15 @@ class TextIOWrapperTest(unittest.TestCase):
|
||||||
self.assertRaises(UnicodeError, t.write, "\xff")
|
self.assertRaises(UnicodeError, t.write, "\xff")
|
||||||
# (3) ignore
|
# (3) ignore
|
||||||
b = io.BytesIO()
|
b = io.BytesIO()
|
||||||
t = io.TextIOWrapper(b, encoding="ascii", errors="ignore", newline="\n")
|
t = io.TextIOWrapper(b, encoding="ascii", errors="ignore",
|
||||||
|
newline="\n")
|
||||||
t.write("abc\xffdef\n")
|
t.write("abc\xffdef\n")
|
||||||
t.flush()
|
t.flush()
|
||||||
self.assertEquals(b.getvalue(), b"abcdef\n")
|
self.assertEquals(b.getvalue(), b"abcdef\n")
|
||||||
# (4) replace
|
# (4) replace
|
||||||
b = io.BytesIO()
|
b = io.BytesIO()
|
||||||
t = io.TextIOWrapper(b, encoding="ascii", errors="replace", newline="\n")
|
t = io.TextIOWrapper(b, encoding="ascii", errors="replace",
|
||||||
|
newline="\n")
|
||||||
t.write("abc\xffdef\n")
|
t.write("abc\xffdef\n")
|
||||||
t.flush()
|
t.flush()
|
||||||
self.assertEquals(b.getvalue(), b"abc?def\n")
|
self.assertEquals(b.getvalue(), b"abc?def\n")
|
||||||
|
|
Loading…
Reference in New Issue