Repository: MagicStack/immutables

Issue:
`immutables.Map.__str__` doesn't match `dict.__str__`
```python
In [7]: d = {'a': 1}

In [8]: str(d)
Out[8]: "{'a': 1}"

In [9]: str(immutables.Map(d))
Out[9]: "<immutables.Map({'a': 1}) at 0x108ed56c0>"
```

Comments:

Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c992bda..7e2c51b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -45,13 +45,13 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
       with:
         fetch-depth: 50
         submodules: true
 
     - name: Set up Python 3.7
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       with:
         python-version: 3.7
 
@@ -70,7 +70,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: [3.5, 3.6, 3.7, 3.8]
+        python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
         os: [ubuntu-16.04, macos-latest, windows-latest]
         exclude:
           # Python 3.5 is unable to properly
@@ -80,13 +80,13 @@ jobs:
             python-version: 3.5
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
       with:
         fetch-depth: 50
         submodules: true
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -130,7 +130,7 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
       with:
         fetch-depth: 5
         submodules: false
diff --git a/README.rst b/README.rst
index e62545e..4132bc0 100644
--- a/README.rst
+++ b/README.rst
@@ -12,7 +12,8 @@ An immutable mapping type for Python.
 The underlying datastructure is a Hash Array Mapped Trie (HAMT)
 used in Clojure, Scala, Haskell, and other functional languages.
 This implementation is used in CPython 3.7 in the ``contextvars``
-module (see PEP 550 and PEP 567 for more details).
+module (see `PEP 550 <https://www.python.org/dev/peps/pep-0550/>`_ and 
+`PEP 567 <https://www.python.org/dev/peps/pep-0567/>`_ for more details).
 
 Immutable mappings based on HAMT have O(log N) performance for both
 ``set()`` and ``get()`` operations, which is essentially O(1) for
diff --git a/immutables/_map.c b/immutables/_map.c
index 9f0a586..7e510fd 100644
--- a/immutables/_map.c
+++ b/immutables/_map.c
@@ -3194,14 +3194,14 @@ map_py_repr(BaseMapObject *m)
 
     if (MapMutation_Check(m)) {
         if (_PyUnicodeWriter_WriteASCIIString(
-                &writer, "<immutables.MapMutation({", 25) < 0)
+                &writer, "immutables.MapMutation({", 24) < 0)
         {
             goto error;
         }
     }
     else {
         if (_PyUnicodeWriter_WriteASCIIString(
-                &writer, "<immutables.Map({", 17) < 0)
+                &writer, "immutables.Map({", 16) < 0)
         {
             goto error;
         }
@@ -3255,16 +3255,6 @@ map_py_repr(BaseMapObject *m)
         goto error;
     }
 
-    PyObject *addr = PyUnicode_FromFormat(" at %p>", m);
-    if (addr == NULL) {
-        goto error;
-    }
-    if (_PyUnicodeWriter_WriteStr(&writer, addr) < 0) {
-        Py_DECREF(addr);
-        goto error;
-    }
-    Py_DECREF(addr);
-
     Py_ReprLeave((PyObject *)m);
     return _PyUnicodeWriter_Finish(&writer);
 
diff --git a/immutables/map.py b/immutables/map.py
index 7c16139..fe9dbaf 100644
--- a/immutables/map.py
+++ b/immutables/map.py
@@ -649,8 +649,7 @@ class Map:
         items = []
         for key, val in self.items():
             items.append("{!r}: {!r}".format(key, val))
-        return '<immutables.Map({{{}}}) at 0x{:0x}>'.format(
-            ', '.join(items), id(self))
+        return 'immutables.Map({{{}}})'.format(', '.join(items))
 
     def __dump__(self):  # pragma: no cover
         buf = []
@@ -818,8 +817,7 @@ class MapMutation:
         items = []
         for key, val in self.__root.items():
             items.append("{!r}: {!r}".format(key, val))
-        return '<immutables.MapMutation({{{}}}) at 0x{:0x}>'.format(
-            ', '.join(items), id(self))
+        return 'immutables.MapMutation({{{}}})'.format(', '.join(items))
 
     def __len__(self):
         return self.__count
diff --git a/setup.py b/setup.py
index b54270d..cb31d2c 100644
--- a/setup.py
+++ b/setup.py
@@ -59,6 +59,7 @@ setuptools.setup(
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3.9',
         'Operating System :: POSIX',
         'Operating System :: MacOS :: MacOS X',
         'Operating System :: Microsoft :: Windows',


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 45367be..019007f 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -15,7 +15,7 @@ jobs:
     strategy:
       max-parallel: 4
       matrix:
-        python-version: [3.5, 3.6, 3.7, 3.8]
+        python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
         os: [windows-latest, ubuntu-18.04, macos-latest]
         exclude:
           # Python 3.5 is unable to properly
@@ -25,7 +25,7 @@ jobs:
             python-version: 3.5
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v2
       with:
         fetch-depth: 50
         submodules: true
@@ -41,7 +41,7 @@ jobs:
           __version__\s*=\s*(?:['"])([[:PEP440:]])(?:['"])
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v2
       if: steps.release.outputs.version == 0
       with:
         python-version: ${{ matrix.python-version }}
diff --git a/tests/test_map.py b/tests/test_map.py
index b6ee7f1..4a473b3 100644
--- a/tests/test_map.py
+++ b/tests/test_map.py
@@ -845,11 +845,10 @@ class BaseMapTest:
 
     def test_repr_1(self):
         h = self.Map()
-        self.assertTrue(repr(h).startswith('<immutables.Map({}) at 0x'))
+        self.assertEqual(repr(h), 'immutables.Map({})')
 
         h = h.set(1, 2).set(2, 3).set(3, 4)
-        self.assertTrue(repr(h).startswith(
-            '<immutables.Map({1: 2, 2: 3, 3: 4}) at 0x'))
+        self.assertEqual(repr(h), 'immutables.Map({1: 2, 2: 3, 3: 4})')
 
     def test_repr_2(self):
         h = self.Map()
@@ -879,8 +878,7 @@ class BaseMapTest:
         h = h.set(k, 1)
         k.val = h
 
-        self.assertTrue(repr(h).startswith(
-            '<immutables.Map({{...}: 1}) at 0x'))
+        self.assertEqual(repr(h), 'immutables.Map({{...}: 1})')
 
     def test_hash_1(self):
         h = self.Map()
@@ -964,8 +962,7 @@ class BaseMapTest:
         h = h.set('a', 1)
         hm1 = h.mutate()
 
-        self.assertTrue(repr(hm1).startswith(
-            "<immutables.MapMutation({'a': 1})"))
+        self.assertEqual(repr(hm1), "immutables.MapMutation({'a': 1})")
 
         with self.assertRaisesRegex(TypeError, 'unhashable type'):
             hash(hm1)
diff --git a/tests/test_none_keys.py b/tests/test_none_keys.py
index f7969f3..92e7813 100644
--- a/tests/test_none_keys.py
+++ b/tests/test_none_keys.py
@@ -61,7 +61,7 @@ class BaseNoneTest:
         self.assertEqual(len(m), 1)
         self.assertTrue(None in m)
         self.assertEqual(m[None], 1)
-        self.assertTrue(repr(m).startswith('<immutables.Map({None: 1}) at 0x'))
+        self.assertEqual(repr(m), 'immutables.Map({None: 1})')
 
         for level in range(7):
             key = NoneCollision('a', level)
@@ -72,7 +72,7 @@ class BaseNoneTest:
         m = m.delete(None)
         self.assertEqual(len(m), 0)
         self.assertFalse(None in m)
-        self.assertTrue(repr(m).startswith('<immutables.Map({}) at 0x'))
+        self.assertEqual(repr(m), 'immutables.Map({})')
 
         self.assertEqual(m, self.Map())
 
@@ -125,7 +125,7 @@ class BaseNoneTest:
             self.assertFalse(None in m3)
             self.assertFalse(key in m3)
             self.assertEqual(m3, self.Map())
-            self.assertTrue(repr(m3).startswith('<immutables.Map({}) at 0x'))
+            self.assertEqual(repr(m3), 'immutables.Map({})')
             with self.assertRaises(KeyError):
                 m3.delete(None)
             with self.assertRaises(KeyError):
@@ -144,7 +144,7 @@ class BaseNoneTest:
             self.assertFalse(None in m4)
             self.assertFalse(key in m4)
             self.assertEqual(m4, self.Map())
-            self.assertTrue(repr(m4).startswith('<immutables.Map({}) at 0x'))
+            self.assertEqual(repr(m4), 'immutables.Map({})')
             with self.assertRaises(KeyError):
                 m4.delete(None)
             with self.assertRaises(KeyError):