Repository: numba/numba

Issue:
Support for len(dict.key()), len(dict.values()), len(dict.items())
---

<!--

Thanks for opening an issue! To help the Numba team handle your information
efficiently, please first ensure that there is no other issue present that
already describes the issue you have
(search at https://github.com/numba/numba/issues?&q=is%3Aissue).

-->

## Feature request

Python supports `len` on `dict.keys`, `dict.values`, and `dict.items`, but none of these seem to be supported in Numba. Should be a relatively simple change to support these.

Example:
```python
@njit
def foo(keys, vals):
    d = dictobject.new_dict(int32, float64)
    for k, v in zip(keys, vals):
        d[k] = v
    return len(d.items())

keys = [1, 2, 3]
vals = [0.1, 0.2, 0.3]
foo(keys, vals)
```
<!--

Please include details of the feature you would like to see, why you would
like to see it/the use case.

-->



Discussion:

Resolving patch:
diff --git a/numba/typed/dictobject.py b/numba/typed/dictobject.py
--- a/numba/typed/dictobject.py
+++ b/numba/typed/dictobject.py
@@ -17,6 +17,7 @@
     models,
     lower_builtin,
     lower_cast,
+    make_attribute_wrapper,
 )
 from numba.core.imputils import iternext_impl, impl_ret_untracked
 from numba.core import types, cgutils
@@ -103,6 +104,12 @@ def __init__(self, dmm, fe_type):
         super(DictIterModel, self).__init__(dmm, fe_type, members)
 
 
+# Make _parent available to make len simple
+make_attribute_wrapper(DictItemsIterableType, "parent", "_parent")
+make_attribute_wrapper(DictKeysIterableType, "parent", "_parent")
+make_attribute_wrapper(DictValuesIterableType, "parent", "_parent")
+
+
 def _raise_if_error(context, builder, status, msg):
     """Raise an internal error depending on the value of *status*
     """
@@ -671,6 +678,20 @@ def impl(d):
     return impl
 
 
+@overload(len)
+def impl_len_iters(d):
+    """len(dict.keys()), len(dict.values()), len(dict.items())
+    """
+    if not isinstance(d, (DictKeysIterableType,
+                      DictValuesIterableType, DictItemsIterableType)):
+        return
+
+    def impl(d):
+        return _dict_length(d._parent)
+
+    return impl
+
+
 @overload_method(types.DictType, '__setitem__')
 @overload(operator.setitem)
 def impl_setitem(d, key, value):


Test patch:
diff --git a/numba/tests/test_dictobject.py b/numba/tests/test_dictobject.py
--- a/numba/tests/test_dictobject.py
+++ b/numba/tests/test_dictobject.py
@@ -357,6 +357,26 @@ def foo(keys, vals):
             keys,
         )
 
+    def test_dict_keys_len(self):
+        """
+        Exercise len(dict.keys())
+        """
+        @njit
+        def foo(keys, vals):
+            d = dictobject.new_dict(int32, float64)
+            # insertion
+            for k, v in zip(keys, vals):
+                d[k] = v
+            return len(d.keys())
+
+        keys = [1, 2, 3]
+        vals = [0.1, 0.2, 0.3]
+
+        self.assertEqual(
+            foo(keys, vals),
+            len(keys),
+        )
+
     def test_dict_values(self):
         """
         Exercise dict.values
@@ -380,6 +400,45 @@ def foo(keys, vals):
             vals,
         )
 
+    def test_dict_values_len(self):
+        """
+        Exercise len(dict.values())
+        """
+        @njit
+        def foo(keys, vals):
+            d = dictobject.new_dict(int32, float64)
+            # insertion
+            for k, v in zip(keys, vals):
+                d[k] = v
+            return len(d.values())
+
+        keys = [1, 2, 3]
+        vals = [0.1, 0.2, 0.3]
+
+        self.assertEqual(
+            foo(keys, vals),
+            len(vals),
+        )
+
+    def test_dict_items_len(self):
+        """
+        Exercise len(dict.items())
+        """
+        @njit
+        def foo(keys, vals):
+            d = dictobject.new_dict(int32, float64)
+            # insertion
+            for k, v in zip(keys, vals):
+                d[k] = v
+            return len(d.items())
+
+        keys = [1, 2, 3]
+        vals = [0.1, 0.2, 0.3]
+        self.assertPreciseEqual(
+            foo(keys, vals),
+            len(vals),
+        )
+
     def test_dict_iter(self):
         """
         Exercise iter(dict)