Home > Net >  `__ge__` fails when used on dict.values()
`__ge__` fails when used on dict.values()

Time:04-08

I create a dictionary,

d = {'a': 1, 'b': 2, 'c': 3, 'f': 6}

when I use dir on its values,

print(dir(d.values()))

it gives,

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
 '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
 '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__',
 '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
 '__sizeof__', '__str__', '__subclasshook__']

but how exactly is the __ge__ supposed to be used? (and similarly, __gt__, __le__, ...) as when I do,

e = {'w': 5, 'x': 6, 'y': 7, 'z': 8}
print(d.values() >= e.values())

it gives,

TypeError: '>=' not supported between instances of 'dict_values' and 'dict_values'

similar error appears if I compare dict_values with dict or set or list or ...

carrying a similar experiment with d.keys() gives a result,

print(d.keys() >= e.keys())

gives,

False

and if I change,

e = {1: 2, 3: 4, 5: 6}
print(d.keys() >= e.keys())

gives,

False

but should it not raise an error, comparing string to int, and furthermore, len(d.keys()) is not equal to len(e.keys())?

CodePudding user response:

Well looking at the python source code, it seems that comparison between dict_keys is called dictview_richcompare in the class definition and the code is here, so you see that comparison between keys only takes the length in consideration. Also, if you take a look at dict_vals definition you will see that tp_richcompare is not implemented! Which solves the mistery.

CodePudding user response:

If you look at d.values().__gt__.__qualname__, it says 'object.__gt__'. So it is probably the same as object().__gt__, which I guess the only thing it does is that it returns NotImplemented (which in turn raises an error about uncomparable types). See this example:

In [31]: d.values().__gt__(object())
Out[31]: NotImplemented

In [32]: d.values() > object()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-32-59ecc977c231> in <module>
----> 1 d.values() > object()

TypeError: '>' not supported between instances of 'dict_values' and 'object'

However dict_keys overrides this behavior, as you can see on d.keys().__gt__.__qualname__ which returns 'dict_keys.__gt__'.

As you can find in the docs, dict_keys are dictionary view objects that act like a set (and sets are defined with comparison operations). For example, set A is greater than B if B is a strict subset of A.

For example:

In [1]: A = set()
In [2]: B = set()

In [3]: A > B
Out[3]: False

In [4]: A >= B
Out[4]: True

In [5]: A.add(3)

In [6]: A > B
Out[6]: True

In [7]: A.add(4); B.add(3)

In [8]: A > B
Out[8]: True

CodePudding user response:

I think the main reasoning is what is mentioned in the docs under Dictionary View Objects:

Keys views are set-like since their entries are unique and hashable. If all values are hashable, so that (key, value) pairs are unique and hashable, then the items view is also set-like. (Values views are not treated as set-like since the entries are generally not unique.) For set-like views, all of the operations defined for the abstract base class collections.abc.Set are available (for example, ==, <, or ^).

Now the documentation for collections.abc.Set didn't really explain anything beyond mentioning which operations are supported, so I looked to the dictobject.c source code . dictview_richcompare is the place where the operations are defined for Dictionary Views, and after some type and edge case the main logic relies on length of both views and whether one is contained in (i.e. subset of) the other:

ok = 0;
    switch(op) {

    case Py_NE:
    case Py_EQ:
        if (len_self == len_other)
            ok = all_contained_in(self, other);
        if (op == Py_NE && ok >= 0)
            ok = !ok;
        break;

    case Py_LT:
        if (len_self < len_other)
            ok = all_contained_in(self, other);
        break;

      case Py_LE:
          if (len_self <= len_other)
              ok = all_contained_in(self, other);
          break;

    case Py_GT:
        if (len_self > len_other)
            ok = all_contained_in(other, self);
        break;

    case Py_GE:
        if (len_self >= len_other)
            ok = all_contained_in(other, self);
        break;

    }
/* Return 1 if self is a subset of other, iterating over self;
   0 if not; -1 if an error occurred. */
static int
all_contained_in(PyObject *self, PyObject *other)

Where the PyTypeObject for keys, values and items are defined dictview_richcompare is passed for PyDictKeys_Type and PyDictItems_Type, but 0 is passed for PyDictValues_Type. So it's not even plugged into the same comparison method as the other two views.

I couldn't really find any comments in the file about why values was excluded, so I'm assuming it is again related to the "unique and hashable" properties mentioned in the docs. Even intuitively, it wouldn't really make much sense to compare values on the basis of subsets without their keys.

  • Related