]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-69528: Distinguish between file modes "wb+" and "rb+" (GH-137834)
authorStan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Tue, 21 Oct 2025 17:33:30 +0000 (18:33 +0100)
committerGitHub <noreply@github.com>
Tue, 21 Oct 2025 17:33:30 +0000 (20:33 +0300)
Co-authored-by: Xiang Zhang <angwerzx@126.com>
Lib/_pyio.py
Lib/test/test_gzip.py
Lib/test/test_io/test_fileio.py
Lib/test/test_io/test_general.py
Lib/test/test_tempfile.py
Misc/NEWS.d/next/Library/2025-08-15-20-35-30.gh-issue-69528.qc-Eh_.rst [new file with mode: 0644]
Modules/_io/fileio.c

index 5db8ce9244b5ba642ade408d8cefdaae2a5c1487..9ae72743919a329dbf02a332d94de032bd069b38 100644 (file)
@@ -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 @@ class FileIO(RawIOBase):
             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 @@ class FileIO(RawIOBase):
                 return 'ab'
         elif self._readable:
             if self._writable:
-                return 'rb+'
+                if self._truncate:
+                    return 'wb+'
+                else:
+                    return 'rb+'
             else:
                 return 'rb'
         else:
index 9a2e1dd248fe94929a5b5d3736c72d94c675c901..f14a882d3868666c2e0bab3a4b4174a0fe86560f 100644 (file)
@@ -639,7 +639,7 @@ class TestGzip(BaseTest):
             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:
index e3d54f6315aade68896975bcafca013f045000e6..e53c4749f58cf2224572e3c13d41d6a1cf4643c6 100644 (file)
@@ -567,8 +567,8 @@ class OtherFileTests:
         # 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
index 7cea0392d0d261e1303797ef8b2392664cb50a3c..ac9c5a425d7ea2a0b5653b0815394becbe279a2e 100644 (file)
@@ -960,8 +960,8 @@ class MiscIOTest:
 
         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")
index 52b13b98cbcce5cd14afbf8b14e8e51ac0f4d599..7eec34f2f294ada10c0bfd9968e8252595e2c379 100644 (file)
@@ -1386,7 +1386,7 @@ class TestSpooledTemporaryFile(BaseTestCase):
 
         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 (file)
index 0000000..b18781e
--- /dev/null
@@ -0,0 +1,2 @@
+The :attr:`~io.FileIO.mode` attribute of files opened in the ``'wb+'`` mode is
+now ``'wb+'`` instead of ``'rb+'``.
index 9992d48a1d8fc7a1fe1f5817bcb508d9f0a25135..b84c1bd3e22c1874a9389862bc7c1d58199d4a8c 100644 (file)
@@ -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";