Skip to content

Commit 942f7a2

Browse files
authored
bpo-39674: Revert "bpo-37330: open() no longer accept 'U' in file mode (GH-16959)" (GH-18767)
This reverts commit e471e72. The mode will be removed from Python 3.10.
1 parent 00c77ae commit 942f7a2

File tree

13 files changed

+99
-54
lines changed

13 files changed

+99
-54
lines changed

Doc/library/codecs.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,6 @@ wider range of codecs when working with binary files:
197197
*buffering* has the same meaning as for the built-in :func:`open` function.
198198
It defaults to -1 which means that the default buffer size will be used.
199199

200-
.. versionchanged:: 3.9
201-
The ``'U'`` mode has been removed.
202-
203200

204201
.. function:: EncodedFile(file, data_encoding, file_encoding=None, errors='strict')
205202

Doc/library/fileinput.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ available for subclassing as well:
148148
The sequence must be accessed in strictly sequential order; random access
149149
and :meth:`~io.TextIOBase.readline` cannot be mixed.
150150

151-
With *mode* you can specify which file mode will be passed to :func:`open`.
152-
It must be ``'r'`` or ``'rb'``.
151+
With *mode* you can specify which file mode will be passed to :func:`open`. It
152+
must be one of ``'r'``, ``'rU'``, ``'U'`` and ``'rb'``.
153153

154154
The *openhook*, when given, must be a function that takes two arguments,
155155
*filename* and *mode*, and returns an accordingly opened file-like object. You
@@ -166,14 +166,15 @@ available for subclassing as well:
166166
.. versionchanged:: 3.2
167167
Can be used as a context manager.
168168

169+
.. deprecated:: 3.4
170+
The ``'rU'`` and ``'U'`` modes.
171+
169172
.. deprecated:: 3.8
170173
Support for :meth:`__getitem__` method is deprecated.
171174

172175
.. versionchanged:: 3.8
173176
The keyword parameter *mode* and *openhook* are now keyword-only.
174177

175-
.. versionchanged:: 3.9
176-
The ``'rU'`` and ``'U'`` modes have been removed.
177178

178179

179180
**Optional in-place filtering:** if the keyword argument ``inplace=True`` is

Doc/library/functions.rst

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,6 +1090,12 @@ are always available. They are listed here in alphabetical order.
10901090
first decoded using a platform-dependent encoding or using the specified
10911091
*encoding* if given.
10921092

1093+
There is an additional mode character permitted, ``'U'``, which no longer
1094+
has any effect, and is considered deprecated. It previously enabled
1095+
:term:`universal newlines` in text mode, which became the default behaviour
1096+
in Python 3.0. Refer to the documentation of the
1097+
:ref:`newline <open-newline-parameter>` parameter for further details.
1098+
10931099
.. note::
10941100

10951101
Python doesn't depend on the underlying operating system's notion of text
@@ -1246,6 +1252,10 @@ are always available. They are listed here in alphabetical order.
12461252

12471253
* The file is now non-inheritable.
12481254

1255+
.. deprecated-removed:: 3.4 3.10
1256+
1257+
The ``'U'`` mode.
1258+
12491259
.. versionchanged::
12501260
3.5
12511261

@@ -1261,10 +1271,6 @@ are always available. They are listed here in alphabetical order.
12611271
* On Windows, opening a console buffer may return a subclass of
12621272
:class:`io.RawIOBase` other than :class:`io.FileIO`.
12631273

1264-
.. versionchanged:: 3.9
1265-
The ``'U'`` mode has been removed.
1266-
1267-
12681274
.. function:: ord(c)
12691275

12701276
Given a string representing one Unicode character, return an integer

Doc/whatsnew/3.9.rst

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -625,14 +625,6 @@ that may require changes to your code.
625625
Changes in the Python API
626626
-------------------------
627627

628-
* :func:`open`, :func:`io.open`, :func:`codecs.open` and
629-
:class:`fileinput.FileInput` no longer accept ``'U'`` ("universal newline")
630-
in the file mode. This flag was deprecated since Python 3.3. In Python 3, the
631-
"universal newline" is used by default when a file is open in text mode. The
632-
:ref:`newline parameter <open-newline-parameter>` of :func:`open` controls
633-
how universal newlines works.
634-
(Contributed by Victor Stinner in :issue:`37330`.)
635-
636628
* :func:`__import__` and :func:`importlib.util.resolve_name` now raise
637629
:exc:`ImportError` where it previously raised :exc:`ValueError`. Callers
638630
catching the specific exception type and supporting both Python 3.9 and

Lib/_pyio.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
7171
'b' binary mode
7272
't' text mode (default)
7373
'+' open a disk file for updating (reading and writing)
74+
'U' universal newline mode (deprecated)
7475
========= ===============================================================
7576
7677
The default mode is 'rt' (open for reading text). For binary random
@@ -86,6 +87,10 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
8687
returned as strings, the bytes having been first decoded using a
8788
platform-dependent encoding or using the specified encoding if given.
8889
90+
'U' mode is deprecated and will raise an exception in future versions
91+
of Python. It has no effect in Python 3. Use newline to control
92+
universal newlines mode.
93+
8994
buffering is an optional integer used to set the buffering policy.
9095
Pass 0 to switch buffering off (only allowed in binary mode), 1 to select
9196
line buffering (only usable in text mode), and an integer > 1 to indicate
@@ -171,7 +176,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
171176
if errors is not None and not isinstance(errors, str):
172177
raise TypeError("invalid errors: %r" % errors)
173178
modes = set(mode)
174-
if modes - set("axrwb+t") or len(mode) > len(modes):
179+
if modes - set("axrwb+tU") or len(mode) > len(modes):
175180
raise ValueError("invalid mode: %r" % mode)
176181
creating = "x" in modes
177182
reading = "r" in modes
@@ -180,6 +185,13 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
180185
updating = "+" in modes
181186
text = "t" in modes
182187
binary = "b" in modes
188+
if "U" in modes:
189+
if creating or writing or appending or updating:
190+
raise ValueError("mode U cannot be combined with 'x', 'w', 'a', or '+'")
191+
import warnings
192+
warnings.warn("'U' mode is deprecated",
193+
DeprecationWarning, 2)
194+
reading = True
183195
if text and binary:
184196
raise ValueError("can't have text and binary mode at once")
185197
if creating + reading + writing + appending > 1:

Lib/fileinput.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,15 @@ def __init__(self, files=None, inplace=False, backup="", *,
209209
self._isstdin = False
210210
self._backupfilename = None
211211
# restrict mode argument to reading modes
212-
if mode not in ('r', 'rb'):
213-
raise ValueError("FileInput opening mode must be 'r' or 'rb'")
212+
if mode not in ('r', 'rU', 'U', 'rb'):
213+
raise ValueError("FileInput opening mode must be one of "
214+
"'r', 'rU', 'U' and 'rb'")
215+
if 'U' in mode:
216+
import warnings
217+
warnings.warn("'U' mode is deprecated",
218+
DeprecationWarning, 2)
214219
self._mode = mode
215-
self._write_mode = mode.replace('r', 'w')
220+
self._write_mode = mode.replace('r', 'w') if 'U' not in mode else 'w'
216221
if openhook:
217222
if inplace:
218223
raise ValueError("FileInput cannot use an opening hook in inplace mode")

Lib/imp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def load_module(name, file, filename, details):
225225
226226
"""
227227
suffix, mode, type_ = details
228-
if mode and (not mode.startswith('r') or '+' in mode):
228+
if mode and (not mode.startswith(('r', 'U')) or '+' in mode):
229229
raise ValueError('invalid file open mode {!r}'.format(mode))
230230
elif file is None and type_ in {PY_SOURCE, PY_COMPILED}:
231231
msg = 'file object required for import (type code {})'.format(type_)

Lib/test/test_codecs.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -712,23 +712,11 @@ def test_bug691291(self):
712712
self.addCleanup(support.unlink, support.TESTFN)
713713
with open(support.TESTFN, 'wb') as fp:
714714
fp.write(s)
715-
with codecs.open(support.TESTFN, 'r',
716-
encoding=self.encoding) as reader:
715+
with support.check_warnings(('', DeprecationWarning)):
716+
reader = codecs.open(support.TESTFN, 'U', encoding=self.encoding)
717+
with reader:
717718
self.assertEqual(reader.read(), s1)
718719

719-
def test_invalid_modes(self):
720-
for mode in ('U', 'rU', 'r+U'):
721-
with self.assertRaises(ValueError) as cm:
722-
codecs.open(support.TESTFN, mode, encoding=self.encoding)
723-
self.assertIn('invalid mode', str(cm.exception))
724-
725-
for mode in ('rt', 'wt', 'at', 'r+t'):
726-
with self.assertRaises(ValueError) as cm:
727-
codecs.open(support.TESTFN, mode, encoding=self.encoding)
728-
self.assertIn("can't have text and binary mode at once",
729-
str(cm.exception))
730-
731-
732720
class UTF16LETest(ReadTest, unittest.TestCase):
733721
encoding = "utf-16-le"
734722
ill_formed_sequence = b"\x80\xdc"

Lib/test/test_fileinput.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,19 @@ def test_fileno(self):
226226
self.assertEqual(fi.fileno(), -1)
227227

228228
def test_opening_mode(self):
229-
# invalid modes
230-
for mode in ('w', 'rU', 'U'):
231-
with self.subTest(mode=mode):
232-
with self.assertRaises(ValueError):
233-
FileInput(mode=mode)
229+
try:
230+
# invalid mode, should raise ValueError
231+
fi = FileInput(mode="w")
232+
self.fail("FileInput should reject invalid mode argument")
233+
except ValueError:
234+
pass
235+
# try opening in universal newline mode
236+
t1 = self.writeTmp(b"A\nB\r\nC\rD", mode="wb")
237+
with check_warnings(('', DeprecationWarning)):
238+
fi = FileInput(files=t1, mode="U")
239+
with check_warnings(('', DeprecationWarning)):
240+
lines = list(fi)
241+
self.assertEqual(lines, ["A\n", "B\n", "C\n", "D"])
234242

235243
def test_stdin_binary_mode(self):
236244
with mock.patch('sys.stdin') as m_stdin:
@@ -977,6 +985,10 @@ def check(mode, expected_lines):
977985
self.assertEqual(lines, expected_lines)
978986

979987
check('r', ['A\n', 'B\n', 'C\n', 'D\u20ac'])
988+
with self.assertWarns(DeprecationWarning):
989+
check('rU', ['A\n', 'B\n', 'C\n', 'D\u20ac'])
990+
with self.assertWarns(DeprecationWarning):
991+
check('U', ['A\n', 'B\n', 'C\n', 'D\u20ac'])
980992
with self.assertRaises(ValueError):
981993
check('rb', ['A\n', 'B\r\n', 'C\r', 'D\u20ac'])
982994

Lib/test/test_io.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3900,6 +3900,16 @@ def test_attributes(self):
39003900
self.assertEqual(f.mode, "wb")
39013901
f.close()
39023902

3903+
with support.check_warnings(('', DeprecationWarning)):
3904+
f = self.open(support.TESTFN, "U")
3905+
self.assertEqual(f.name, support.TESTFN)
3906+
self.assertEqual(f.buffer.name, support.TESTFN)
3907+
self.assertEqual(f.buffer.raw.name, support.TESTFN)
3908+
self.assertEqual(f.mode, "U")
3909+
self.assertEqual(f.buffer.mode, "rb")
3910+
self.assertEqual(f.buffer.raw.mode, "rb")
3911+
f.close()
3912+
39033913
f = self.open(support.TESTFN, "w+")
39043914
self.assertEqual(f.mode, "w+")
39053915
self.assertEqual(f.buffer.mode, "rb+") # Does it really matter?
@@ -3913,13 +3923,6 @@ def test_attributes(self):
39133923
f.close()
39143924
g.close()
39153925

3916-
def test_removed_u_mode(self):
3917-
# "U" mode has been removed in Python 3.9
3918-
for mode in ("U", "rU", "r+U"):
3919-
with self.assertRaises(ValueError) as cm:
3920-
self.open(support.TESTFN, mode)
3921-
self.assertIn('invalid mode', str(cm.exception))
3922-
39233926
def test_open_pipe_with_append(self):
39243927
# bpo-27805: Ignore ESPIPE from lseek() in open().
39253928
r, w = os.pipe()

0 commit comments

Comments
 (0)