https://github.com/python/cpython/commit/02c1abfc54954ec89a0c728823efde5ce7918a0f
commit: 02c1abfc54954ec89a0c728823efde5ce7918a0f
branch: main
author: Stan Ulbrych <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-10-21T20:33:30+03:00
summary:

gh-69528: Distinguish between file modes "wb+" and "rb+" (GH-137834)

Co-authored-by: Xiang Zhang <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-08-15-20-35-30.gh-issue-69528.qc-Eh_.rst
M Lib/_pyio.py
M Lib/test/test_gzip.py
M Lib/test/test_io/test_fileio.py
M Lib/test/test_io/test_general.py
M Lib/test/test_tempfile.py
M Modules/_io/fileio.c

diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index 5db8ce9244b5ba..9ae72743919a32 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -1498,6 +1498,7 @@ class FileIO(RawIOBase):
     _writable = False
     _appending = False
     _seekable = None
+    _truncate = False
     _closefd = True
 
     def __init__(self, file, mode='r', closefd=True, opener=None):
@@ -1553,6 +1554,7 @@ def __init__(self, file, mode='r', closefd=True, 
opener=None):
             flags = 0
         elif 'w' in mode:
             self._writable = True
+            self._truncate = True
             flags = os.O_CREAT | os.O_TRUNC
         elif 'a' in mode:
             self._writable = True
@@ -1877,7 +1879,10 @@ def mode(self):
                 return 'ab'
         elif self._readable:
             if self._writable:
-                return 'rb+'
+                if self._truncate:
+                    return 'wb+'
+                else:
+                    return 'rb+'
             else:
                 return 'rb'
         else:
diff --git a/Lib/test/test_gzip.py b/Lib/test/test_gzip.py
index 9a2e1dd248fe94..f14a882d386866 100644
--- a/Lib/test/test_gzip.py
+++ b/Lib/test/test_gzip.py
@@ -639,7 +639,7 @@ def test_fileobj_mode(self):
             with open(self.filename, mode) as f:
                 with gzip.GzipFile(fileobj=f) as g:
                     self.assertEqual(g.mode, gzip.READ)
-        for mode in "wb", "ab", "xb":
+        for mode in "wb", "ab", "xb", "wb+", "ab+", "xb+":
             if "x" in mode:
                 os_helper.unlink(self.filename)
             with open(self.filename, mode) as f:
diff --git a/Lib/test/test_io/test_fileio.py b/Lib/test/test_io/test_fileio.py
index e3d54f6315aade..e53c4749f58cf2 100644
--- a/Lib/test/test_io/test_fileio.py
+++ b/Lib/test/test_io/test_fileio.py
@@ -567,8 +567,8 @@ def testModeStrings(self):
         # test that the mode attribute is correct for various mode strings
         # given as init args
         try:
-            for modes in [('w', 'wb'), ('wb', 'wb'), ('wb+', 'rb+'),
-                          ('w+b', 'rb+'), ('a', 'ab'), ('ab', 'ab'),
+            for modes in [('w', 'wb'), ('wb', 'wb'), ('wb+', 'wb+'),
+                          ('w+b', 'wb+'), ('a', 'ab'), ('ab', 'ab'),
                           ('ab+', 'ab+'), ('a+b', 'ab+'), ('r', 'rb'),
                           ('rb', 'rb'), ('rb+', 'rb+'), ('r+b', 'rb+')]:
                 # read modes are last so that TESTFN will exist first
diff --git a/Lib/test/test_io/test_general.py b/Lib/test/test_io/test_general.py
index 7cea0392d0d261..ac9c5a425d7ea2 100644
--- a/Lib/test/test_io/test_general.py
+++ b/Lib/test/test_io/test_general.py
@@ -960,8 +960,8 @@ def test_attributes(self):
 
         f = self.open(os_helper.TESTFN, "w+", encoding="utf-8")
         self.assertEqual(f.mode,            "w+")
-        self.assertEqual(f.buffer.mode,     "rb+") # Does it really matter?
-        self.assertEqual(f.buffer.raw.mode, "rb+")
+        self.assertEqual(f.buffer.mode,     "wb+")
+        self.assertEqual(f.buffer.raw.mode, "wb+")
 
         g = self.open(f.fileno(), "wb", closefd=False)
         self.assertEqual(g.mode,     "wb")
diff --git a/Lib/test/test_tempfile.py b/Lib/test/test_tempfile.py
index 52b13b98cbcce5..7eec34f2f294ad 100644
--- a/Lib/test/test_tempfile.py
+++ b/Lib/test/test_tempfile.py
@@ -1386,7 +1386,7 @@ def test_properties(self):
 
         f.write(b'x')
         self.assertTrue(f._rolled)
-        self.assertEqual(f.mode, 'rb+')
+        self.assertEqual(f.mode, 'wb+')
         self.assertIsNotNone(f.name)
         with self.assertRaises(AttributeError):
             f.newlines
diff --git 
a/Misc/NEWS.d/next/Library/2025-08-15-20-35-30.gh-issue-69528.qc-Eh_.rst 
b/Misc/NEWS.d/next/Library/2025-08-15-20-35-30.gh-issue-69528.qc-Eh_.rst
new file mode 100644
index 00000000000000..b18781e0dceb8c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-08-15-20-35-30.gh-issue-69528.qc-Eh_.rst
@@ -0,0 +1,2 @@
+The :attr:`~io.FileIO.mode` attribute of files opened in the ``'wb+'`` mode is
+now ``'wb+'`` instead of ``'rb+'``.
diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c
index 9992d48a1d8fc7..b84c1bd3e22c18 100644
--- a/Modules/_io/fileio.c
+++ b/Modules/_io/fileio.c
@@ -70,6 +70,7 @@ typedef struct {
     unsigned int writable : 1;
     unsigned int appending : 1;
     signed int seekable : 2; /* -1 means unknown */
+    unsigned int truncate : 1;
     unsigned int closefd : 1;
     char finalizing;
     /* Stat result which was grabbed at file open, useful for optimizing common
@@ -209,6 +210,7 @@ fileio_new(PyTypeObject *type, PyObject *args, PyObject 
*kwds)
     self->writable = 0;
     self->appending = 0;
     self->seekable = -1;
+    self->truncate = 0;
     self->stat_atopen = NULL;
     self->closefd = 1;
     self->weakreflist = NULL;
@@ -341,6 +343,7 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, 
const char *mode,
                 goto bad_mode;
             rwa = 1;
             self->writable = 1;
+            self->truncate = 1;
             flags |= O_CREAT | O_TRUNC;
             break;
         case 'a':
@@ -1145,10 +1148,17 @@ mode_string(fileio *self)
             return "ab";
     }
     else if (self->readable) {
-        if (self->writable)
-            return "rb+";
-        else
+        if (self->writable) {
+            if (self->truncate) {
+                return "wb+";
+            }
+            else {
+                return "rb+";
+            }
+        }
+        else {
             return "rb";
+        }
     }
     else
         return "wb";

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to