bpo-34953: Implement `mmap.mmap.__repr__` (GH-9891)


diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py
index 88c501d..5400f25 100644
--- a/Lib/test/test_mmap.py
+++ b/Lib/test/test_mmap.py
@@ -740,6 +740,42 @@
             # See bpo-34754 for details.
             self.assertRaises(OSError, mm.flush, 1, len(b'python'))
 
+    def test_repr(self):
+        open_mmap_repr_pat = re.compile(
+            r"<mmap.mmap closed=False, "
+            r"access=(?P<access>\S+), "
+            r"length=(?P<length>\d+), "
+            r"pos=(?P<pos>\d+), "
+            r"offset=(?P<offset>\d+)>")
+        closed_mmap_repr_pat = re.compile(r"<mmap.mmap closed=True>")
+        mapsizes = (50, 100, 1_000, 1_000_000, 10_000_000)
+        offsets = tuple((mapsize // 2 // mmap.ALLOCATIONGRANULARITY)
+                        * mmap.ALLOCATIONGRANULARITY for mapsize in mapsizes)
+        for offset, mapsize in zip(offsets, mapsizes):
+            data = b'a' * mapsize
+            length = mapsize - offset
+            accesses = ('ACCESS_DEFAULT', 'ACCESS_READ',
+                        'ACCESS_COPY', 'ACCESS_WRITE')
+            positions = (0, length//10, length//5, length//4)
+            with open(TESTFN, "wb+") as fp:
+                fp.write(data)
+                fp.flush()
+                for access, pos in itertools.product(accesses, positions):
+                    accint = getattr(mmap, access)
+                    with mmap.mmap(fp.fileno(),
+                                   length,
+                                   access=accint,
+                                   offset=offset) as mm:
+                        mm.seek(pos)
+                        match = open_mmap_repr_pat.match(repr(mm))
+                        self.assertIsNotNone(match)
+                        self.assertEqual(match.group('access'), access)
+                        self.assertEqual(match.group('length'), str(length))
+                        self.assertEqual(match.group('pos'), str(pos))
+                        self.assertEqual(match.group('offset'), str(offset))
+                    match = closed_mmap_repr_pat.match(repr(mm))
+                    self.assertIsNotNone(match)
+
     @unittest.skipUnless(hasattr(mmap.mmap, 'madvise'), 'needs madvise')
     def test_madvise(self):
         size = 2 * PAGESIZE
diff --git a/Modules/mmapmodule.c b/Modules/mmapmodule.c
index 0c64163..a3b9e4d 100644
--- a/Modules/mmapmodule.c
+++ b/Modules/mmapmodule.c
@@ -695,6 +695,51 @@
     return _PyObject_CallMethodIdNoArgs(self, &PyId_close);
 }
 
+static PyObject *
+mmap__repr__method(PyObject *self)
+{
+    mmap_object *mobj = (mmap_object *)self;
+
+#ifdef MS_WINDOWS
+#define _Py_FORMAT_OFFSET "lld"
+    if (mobj->map_handle == NULL)
+#elif defined(UNIX)
+# ifdef HAVE_LARGEFILE_SUPPORT
+# define _Py_FORMAT_OFFSET "lld"
+# else
+# define _Py_FORMAT_OFFSET "ld"
+# endif
+    if (mobj->data == NULL)
+#endif
+    {
+        return PyUnicode_FromFormat("<%s closed=True>", Py_TYPE(self)->tp_name);
+    } else {
+        const char *access_str;
+
+        switch (mobj->access) {
+            case ACCESS_DEFAULT:
+                access_str = "ACCESS_DEFAULT";
+                break;
+            case ACCESS_READ:
+                access_str = "ACCESS_READ";
+                break;
+            case ACCESS_WRITE:
+                access_str = "ACCESS_WRITE";
+                break;
+            case ACCESS_COPY:
+                access_str = "ACCESS_COPY";
+                break;
+            default:
+                Py_UNREACHABLE();
+        }
+
+        return PyUnicode_FromFormat("<%s closed=False, access=%s, length=%zd, "
+                                    "pos=%zd, offset=%" _Py_FORMAT_OFFSET ">",
+                                    Py_TYPE(self)->tp_name, access_str,
+                                    mobj->size, mobj->pos, mobj->offset);
+    }
+}
+
 #ifdef MS_WINDOWS
 static PyObject *
 mmap__sizeof__method(mmap_object *self, void *unused)
@@ -1044,23 +1089,23 @@
     sizeof(mmap_object),                        /* tp_basicsize */
     0,                                          /* tp_itemsize */
     /* methods */
-    (destructor) mmap_object_dealloc,           /* tp_dealloc */
+    (destructor)mmap_object_dealloc,            /* tp_dealloc */
     0,                                          /* tp_vectorcall_offset */
     0,                                          /* tp_getattr */
     0,                                          /* tp_setattr */
     0,                                          /* tp_as_async */
-    0,                                          /* tp_repr */
+    (reprfunc)mmap__repr__method,               /* tp_repr */
     0,                                          /* tp_as_number */
-    &mmap_as_sequence,                          /*tp_as_sequence*/
-    &mmap_as_mapping,                           /*tp_as_mapping*/
-    0,                                          /*tp_hash*/
-    0,                                          /*tp_call*/
-    0,                                          /*tp_str*/
-    PyObject_GenericGetAttr,                    /*tp_getattro*/
-    0,                                          /*tp_setattro*/
-    &mmap_as_buffer,                            /*tp_as_buffer*/
-    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /*tp_flags*/
-    mmap_doc,                                   /*tp_doc*/
+    &mmap_as_sequence,                          /* tp_as_sequence */
+    &mmap_as_mapping,                           /* tp_as_mapping */
+    0,                                          /* tp_hash */
+    0,                                          /* tp_call */
+    0,                                          /* tp_str */
+    PyObject_GenericGetAttr,                    /* tp_getattro */
+    0,                                          /* tp_setattro */
+    &mmap_as_buffer,                            /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   /* tp_flags */
+    mmap_doc,                                   /* tp_doc */
     0,                                          /* tp_traverse */
     0,                                          /* tp_clear */
     0,                                          /* tp_richcompare */