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

Skip to content

Commit f64db9f

Browse files
committed
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.
1 parent 7867208 commit f64db9f

4 files changed

Lines changed: 57 additions & 36 deletions

File tree

Lib/io.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,10 @@ def open(file, mode="r", buffering=None, encoding=None, errors=None,
151151
closefd)
152152
if buffering is None:
153153
buffering = -1
154-
if buffering < 0 and raw.isatty():
155-
buffering = 1
154+
line_buffering = False
155+
if buffering == 1 or buffering < 0 and raw.isatty():
156+
buffering = -1
157+
line_buffering = True
156158
if buffering < 0:
157159
buffering = DEFAULT_BUFFER_SIZE
158160
try:
@@ -182,7 +184,7 @@ def open(file, mode="r", buffering=None, encoding=None, errors=None,
182184
buffer.name = file
183185
buffer.mode = mode
184186
return buffer
185-
text = TextIOWrapper(buffer, encoding, errors, newline)
187+
text = TextIOWrapper(buffer, encoding, errors, newline, line_buffering)
186188
text.name = file
187189
text.mode = mode
188190
return text
@@ -1133,7 +1135,8 @@ class TextIOWrapper(TextIOBase):
11331135

11341136
_CHUNK_SIZE = 128
11351137

1136-
def __init__(self, buffer, encoding=None, errors=None, newline=None):
1138+
def __init__(self, buffer, encoding=None, errors=None, newline=None,
1139+
line_buffering=False):
11371140
if newline not in (None, "", "\n", "\r", "\r\n"):
11381141
raise ValueError("illegal newline value: %r" % (newline,))
11391142
if encoding is None:
@@ -1160,6 +1163,7 @@ def __init__(self, buffer, encoding=None, errors=None, newline=None):
11601163
raise ValueError("invalid errors: %r" % errors)
11611164

11621165
self.buffer = buffer
1166+
self._line_buffering = line_buffering
11631167
self._encoding = encoding
11641168
self._errors = errors
11651169
self._readuniversal = not newline
@@ -1180,6 +1184,10 @@ def encoding(self):
11801184
def errors(self):
11811185
return self._errors
11821186

1187+
@property
1188+
def line_buffering(self):
1189+
return self._line_buffering
1190+
11831191
# A word about _snapshot. This attribute is either None, or a
11841192
# tuple (decoder_state, readahead, pending) where decoder_state is
11851193
# the second (integer) item of the decoder state, readahead is the
@@ -1218,13 +1226,13 @@ def write(self, s: str):
12181226
raise TypeError("can't write %s to text stream" %
12191227
s.__class__.__name__)
12201228
length = len(s)
1221-
haslf = "\n" in s
1229+
haslf = (self._writetranslate or self._line_buffering) and "\n" in s
12221230
if haslf and self._writetranslate and self._writenl != "\n":
12231231
s = s.replace("\n", self._writenl)
12241232
# XXX What if we were just reading?
12251233
b = s.encode(self._encoding, self._errors)
12261234
self.buffer.write(b)
1227-
if haslf and self.isatty():
1235+
if self._line_buffering and (haslf or "\r" in s):
12281236
self.flush()
12291237
self._snapshot = None
12301238
if self._decoder:

Lib/test/output/test_cProfile

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ test_cProfile
55

66
ncalls tottime percall cumtime percall filename:lineno(function)
77
1 0.000 0.000 1.000 1.000 <string>:1(<module>)
8-
2 0.000 0.000 0.000 0.000 io.py:1193(flush)
9-
1 0.000 0.000 0.000 0.000 io.py:257(flush)
10-
1 0.000 0.000 0.000 0.000 io.py:644(closed)
11-
1 0.000 0.000 0.000 0.000 io.py:862(flush)
8+
2 0.000 0.000 0.000 0.000 io.py:1201(flush)
9+
1 0.000 0.000 0.000 0.000 io.py:259(flush)
10+
1 0.000 0.000 0.000 0.000 io.py:646(closed)
11+
1 0.000 0.000 0.000 0.000 io.py:864(flush)
1212
8 0.064 0.008 0.080 0.010 test_cProfile.py:103(subhelper)
1313
28 0.028 0.001 0.028 0.001 test_cProfile.py:115(__getattr__)
1414
1 0.270 0.270 1.000 1.000 test_cProfile.py:30(testfunc)
@@ -30,11 +30,11 @@ test_cProfile
3030
Function called...
3131
ncalls tottime cumtime
3232
<string>:1(<module>) -> 1 0.270 1.000 test_cProfile.py:30(testfunc)
33-
io.py:1193(flush) -> 1 0.000 0.000 io.py:257(flush)
34-
1 0.000 0.000 io.py:862(flush)
35-
io.py:257(flush) ->
36-
io.py:644(closed) ->
37-
io.py:862(flush) -> 1 0.000 0.000 io.py:644(closed)
33+
io.py:1201(flush) -> 1 0.000 0.000 io.py:259(flush)
34+
1 0.000 0.000 io.py:864(flush)
35+
io.py:259(flush) ->
36+
io.py:646(closed) ->
37+
io.py:864(flush) -> 1 0.000 0.000 io.py:646(closed)
3838
test_cProfile.py:103(subhelper) -> 16 0.016 0.016 test_cProfile.py:115(__getattr__)
3939
test_cProfile.py:115(__getattr__) ->
4040
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
5353
test_cProfile.py:93(helper2) -> 8 0.064 0.080 test_cProfile.py:103(subhelper)
5454
8 0.000 0.008 {hasattr}
5555
{exec} -> 1 0.000 1.000 <string>:1(<module>)
56-
2 0.000 0.000 io.py:1193(flush)
56+
2 0.000 0.000 io.py:1201(flush)
5757
{hasattr} -> 12 0.012 0.012 test_cProfile.py:115(__getattr__)
5858
{method 'append' of 'list' objects} ->
5959
{method 'disable' of '_lsprof.Profiler' objects} ->
@@ -65,10 +65,10 @@ test_cProfile.py:93(helper2) -> 8 0.064 0.080
6565
Function was called by...
6666
ncalls tottime cumtime
6767
<string>:1(<module>) <- 1 0.000 1.000 {exec}
68-
io.py:1193(flush) <- 2 0.000 0.000 {exec}
69-
io.py:257(flush) <- 1 0.000 0.000 io.py:1193(flush)
70-
io.py:644(closed) <- 1 0.000 0.000 io.py:862(flush)
71-
io.py:862(flush) <- 1 0.000 0.000 io.py:1193(flush)
68+
io.py:1201(flush) <- 2 0.000 0.000 {exec}
69+
io.py:259(flush) <- 1 0.000 0.000 io.py:1201(flush)
70+
io.py:646(closed) <- 1 0.000 0.000 io.py:864(flush)
71+
io.py:864(flush) <- 1 0.000 0.000 io.py:1201(flush)
7272
test_cProfile.py:103(subhelper) <- 8 0.064 0.080 test_cProfile.py:93(helper2)
7373
test_cProfile.py:115(__getattr__) <- 16 0.016 0.016 test_cProfile.py:103(subhelper)
7474
12 0.012 0.012 {hasattr}

Lib/test/output/test_profile

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ test_profile
1010
12 0.000 0.000 0.012 0.001 :0(hasattr)
1111
1 0.000 0.000 0.000 0.000 :0(setprofile)
1212
1 0.000 0.000 1.000 1.000 <string>:1(<module>)
13-
2 0.000 0.000 0.000 0.000 io.py:1193(flush)
14-
1 0.000 0.000 0.000 0.000 io.py:257(flush)
15-
1 0.000 0.000 0.000 0.000 io.py:644(closed)
16-
1 0.000 0.000 0.000 0.000 io.py:862(flush)
13+
2 0.000 0.000 0.000 0.000 io.py:1201(flush)
14+
1 0.000 0.000 0.000 0.000 io.py:259(flush)
15+
1 0.000 0.000 0.000 0.000 io.py:646(closed)
16+
1 0.000 0.000 0.000 0.000 io.py:864(flush)
1717
0 0.000 0.000 profile:0(profiler)
1818
1 0.000 0.000 1.000 1.000 profile:0(testfunc())
1919
8 0.064 0.008 0.080 0.010 test_profile.py:103(subhelper)
@@ -33,15 +33,15 @@ Function called...
3333
:0(append) ->
3434
:0(exc_info) ->
3535
:0(exec) -> <string>:1(<module>)(1) 1.000
36-
io.py:1193(flush)(2) 0.000
36+
io.py:1201(flush)(2) 0.000
3737
:0(hasattr) -> test_profile.py:115(__getattr__)(12) 0.028
3838
:0(setprofile) ->
3939
<string>:1(<module>) -> test_profile.py:30(testfunc)(1) 1.000
40-
io.py:1193(flush) -> io.py:257(flush)(1) 0.000
41-
io.py:862(flush)(1) 0.000
42-
io.py:257(flush) ->
43-
io.py:644(closed) ->
44-
io.py:862(flush) -> io.py:644(closed)(1) 0.000
40+
io.py:1201(flush) -> io.py:259(flush)(1) 0.000
41+
io.py:864(flush)(1) 0.000
42+
io.py:259(flush) ->
43+
io.py:646(closed) ->
44+
io.py:864(flush) -> io.py:646(closed)(1) 0.000
4545
profile:0(profiler) -> profile:0(testfunc())(1) 1.000
4646
profile:0(testfunc()) -> :0(exec)(1) 1.000
4747
:0(setprofile)(1) 0.000
@@ -74,10 +74,10 @@ Function was called by...
7474
test_profile.py:93(helper2)(8) 0.400
7575
:0(setprofile) <- profile:0(testfunc())(1) 1.000
7676
<string>:1(<module>) <- :0(exec)(1) 1.000
77-
io.py:1193(flush) <- :0(exec)(2) 1.000
78-
io.py:257(flush) <- io.py:1193(flush)(1) 0.000
79-
io.py:644(closed) <- io.py:862(flush)(1) 0.000
80-
io.py:862(flush) <- io.py:1193(flush)(1) 0.000
77+
io.py:1201(flush) <- :0(exec)(2) 1.000
78+
io.py:259(flush) <- io.py:1201(flush)(1) 0.000
79+
io.py:646(closed) <- io.py:864(flush)(1) 0.000
80+
io.py:864(flush) <- io.py:1201(flush)(1) 0.000
8181
profile:0(profiler) <-
8282
profile:0(testfunc()) <- profile:0(profiler)(1) 0.000
8383
test_profile.py:103(subhelper) <- test_profile.py:93(helper2)(8) 0.400

Lib/test/test_io.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,17 @@ def setUp(self):
496496
def tearDown(self):
497497
test_support.unlink(test_support.TESTFN)
498498

499+
def testLineBuffering(self):
500+
r = io.BytesIO()
501+
b = io.BufferedWriter(r, 1000)
502+
t = io.TextIOWrapper(b, newline="\n", line_buffering=True)
503+
t.write("X")
504+
self.assertEquals(r.getvalue(), b"") # No flush happened
505+
t.write("Y\nZ")
506+
self.assertEquals(r.getvalue(), b"XY\nZ") # All got flushed
507+
t.write("A\rB")
508+
self.assertEquals(r.getvalue(), b"XY\nZA\rB")
509+
499510
def testEncodingErrorsReading(self):
500511
# (1) default
501512
b = io.BytesIO(b"abc\n\xff\n")
@@ -525,13 +536,15 @@ def testEncodingErrorsWriting(self):
525536
self.assertRaises(UnicodeError, t.write, "\xff")
526537
# (3) ignore
527538
b = io.BytesIO()
528-
t = io.TextIOWrapper(b, encoding="ascii", errors="ignore", newline="\n")
539+
t = io.TextIOWrapper(b, encoding="ascii", errors="ignore",
540+
newline="\n")
529541
t.write("abc\xffdef\n")
530542
t.flush()
531543
self.assertEquals(b.getvalue(), b"abcdef\n")
532544
# (4) replace
533545
b = io.BytesIO()
534-
t = io.TextIOWrapper(b, encoding="ascii", errors="replace", newline="\n")
546+
t = io.TextIOWrapper(b, encoding="ascii", errors="replace",
547+
newline="\n")
535548
t.write("abc\xffdef\n")
536549
t.flush()
537550
self.assertEquals(b.getvalue(), b"abc?def\n")

0 commit comments

Comments
 (0)