diff --git a/py/objtype.c b/py/objtype.c
index db99d407acfcd5b6c176abe1184f101a5a0c8615..ca52006f25a299b0ca8c03b5b605d375e24f7ff7 100644
--- a/py/objtype.c
+++ b/py/objtype.c
@@ -472,7 +472,22 @@ STATIC void mp_obj_instance_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *des
         dest[0] = elem->value;
         return;
     }
-
+#if MICROPY_CPYTHON_COMPAT
+    if (attr == MP_QSTR___dict__) {
+        // Create a new dict with a copy of the instance's map items.
+        // This creates, unlike CPython, a 'read-only' __dict__: modifying
+        // it will not result in modifications to the actual instance members.
+        mp_map_t *map = &self->members;
+        mp_obj_t attr_dict = mp_obj_new_dict(map->used);
+        for (mp_uint_t i = 0; i < map->alloc; ++i) {
+            if (MP_MAP_SLOT_IS_FILLED(map, i)) {
+                mp_obj_dict_store(attr_dict, map->table[i].key, map->table[i].value);
+            }
+        }
+        dest[0] = attr_dict;
+        return;
+    }
+#endif
     struct class_lookup_data lookup = {
         .obj = self,
         .attr = attr,
diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index 3be6168a8f22ca1f2d9d4ef2eca1a3853b4fb1a1..496896dad45169efc1caaad047779cc22bd1a1ba 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -46,6 +46,7 @@ Q(__locals__)
 Q(__main__)
 Q(__module__)
 Q(__name__)
+Q(__dict__)
 Q(__hash__)
 Q(__next__)
 Q(__qualname__)
diff --git a/tests/basics/builtin_dict.py b/tests/basics/builtin_dict.py
new file mode 100644
index 0000000000000000000000000000000000000000..f51ec21d007ae7b3b076dec8376bd10dab2713a3
--- /dev/null
+++ b/tests/basics/builtin_dict.py
@@ -0,0 +1,11 @@
+class A:
+    def __init__(self):
+        self.a=1
+        self.b=2
+
+try:
+    d=A().__dict__
+    print(d['a'])
+    print(d['b'])
+except AttributeError:
+    print("SKIP")