implement a detach() method for BufferedIOBase and TextIOBase #5883
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index e580366..e3e7c3d 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -642,6 +642,15 @@
         """
         self._unsupported("write")
 
+    def detach(self) -> None:
+        """
+        Separate the underlying raw stream from the buffer and return it.
+
+        After the raw stream has been detached, the buffer is in an unusable
+        state.
+        """
+        self._unsupported("detach")
+
 io.BufferedIOBase.register(BufferedIOBase)
 
 
@@ -689,13 +698,21 @@
         self.raw.flush()
 
     def close(self):
-        if not self.closed:
+        if not self.closed and self.raw is not None:
             try:
                 self.flush()
             except IOError:
                 pass  # If flush() fails, just give up
             self.raw.close()
 
+    def detach(self):
+        if self.raw is None:
+            raise ValueError("raw stream already detached")
+        self.flush()
+        raw = self.raw
+        self.raw = None
+        return raw
+
     ### Inquiries ###
 
     def seekable(self):
@@ -1236,6 +1253,15 @@
         """
         self._unsupported("readline")
 
+    def detach(self) -> None:
+        """
+        Separate the underlying buffer from the TextIOBase and return it.
+
+        After the underlying buffer has been detached, the TextIO is in an
+        unusable state.
+        """
+        self._unsupported("detach")
+
     @property
     def encoding(self):
         """Subclasses should override."""
@@ -1448,11 +1474,12 @@
         self._telling = self._seekable
 
     def close(self):
-        try:
-            self.flush()
-        except IOError:
-            pass  # If flush() fails, just give up
-        self.buffer.close()
+        if self.buffer is not None:
+            try:
+                self.flush()
+            except IOError:
+                pass  # If flush() fails, just give up
+            self.buffer.close()
 
     @property
     def closed(self):
@@ -1647,6 +1674,14 @@
         self.seek(pos)
         return self.buffer.truncate()
 
+    def detach(self):
+        if self.buffer is None:
+            raise ValueError("buffer is already detached")
+        self.flush()
+        buffer = self.buffer
+        self.buffer = None
+        return buffer
+
     def seek(self, cookie, whence=0):
         if self.closed:
             raise ValueError("tell on closed file")
@@ -1865,3 +1900,7 @@
     @property
     def encoding(self):
         return None
+
+    def detach(self):
+        # This doesn't make sense on StringIO.
+        self._unsupported("detach")