1

EDIT: I added the class and some info, see the new post.

I have created a class that extends dict and set (more exactly, I extended collections.abc.MutableMapping and MutableSet). Now I want to properly override the method keys().

This class represents a set of other objects, called Measurand:

class Measurands(MutableSet, MutableMapping):
    @property
    def measurands(self):
        return self._measurands
    
    
    @measurands.setter
    def measurands(self, val):
        self._measurands = []
        
        for el in val:
            self.add(el)
    
    
    def __init__(self, arg=None):
        if arg is None:
            self.measurands = []
        else:
            try:
                measurands = arg.measurands
            except AttributeError:
                if isinstance(arg, Iterable):
                    measurands = list(arg)
                else:
                    measurands = [arg]
            
            self.measurands = measurands
            
    
    def __iter__(self):
        return iter(self.measurands)
    
    
    def __getitem__(self, key):
        for m in self:
            if m.id == key:
                return m
    
        raise KeyError(key)
    
    
    def __contains__(self, el_x):
        is_in = False
        
        for el in self:
            if el_x.id == el.id:
                is_in = True
                break
        
        return is_in
    
    
    def add(self, el):
        try:
            self[el.id].session_ids |= el.session_ids
        except KeyError:
            self[el.id] = Measurand(el)
    
    
    def _discard(self, key):
        res = False
        
        for i, m in enumerate(self):
            if m.id == key:
                del self.measurands[i]
                res = True
                break
        
        return res
    
    
    def __delitem__(self, key):
        res = self._discard(self, key)
        
        if not res:
            raise KeyError(key)
    
    def discard(self, key):
        self._discard(self, key)
    
    
    def remove(self, key):
        self.__delitem__(self, key)
    
    
    def __len__(self):
        return len(self.measurands)
    
    
    def __setitem__(self, key, value):
        res = False
        value_true = Measurand(value)
        
        for i, m in enumerate(self):
            if m.id == key:
                res = True
                
                if value.id == key:
                    self.measurands[i] = value_true
                else:
                    raise KeyError(key)
                
                break
        
        if not res:
            self.measurands.append(value_true)
    
    
    def __str__(self):
        string = "Measurands({"
        
        for m in self:
            string += str(m)
            string += ", "
        
        if string:
            string = string[:-2]
        
        string += "})"
        
        return string

The problem is the default method keys returns a "list" of Measurand objects. This is not what I want. The keys should be the property id of the Measurand objects.

For now I'm returning a simple list, but I would return a dict_keys or a collection.abc.KeysView object. Unluckily, dict_keys is not a global name.

  1. Where is dict_keys?
  2. Is this the correct/pythonic way to do it?
Community
  • 1
  • 1
Marco Sulla
  • 15,299
  • 14
  • 65
  • 100
  • 2
    *"I want to properly override the method `dict()`"* - what? `dict` isn't a method. Do you mean you want to be able to do `dict(instance_of_your_thing)`? Could you give a [mcve] of your implementation and explain the problem with an example? – jonrsharpe Nov 09 '15 at 15:34
  • Judging from the title, I think he meant he wants to properly override `keys()` and made a typo in the actual question – wpercy Nov 09 '15 at 15:50
  • @wilbur ah! That would make more sense. OP? – jonrsharpe Nov 09 '15 at 16:02

3 Answers3

0

I haven't been able to find any way to cast something to the dict_keys type, but after reading the PEP that introduced the new way to access keys and values (PEP 3106), it looks like dict_keys was introduced to

return a set-like or unordered container object whose contents are derived from the underlying dictionary rather than a list which is a copy of the keys, etc.

Taking this into account, it looks like you could get away with returning a generator object with your keys() method instead of a list.

Here is the pseudo-code from the PEP:

class dict:

    # Omitting all other dict methods for brevity.

    def keys(self):
        return d_keys(self)

class d_keys:

    def __init__(self, d):
        self.__d = d

    def __len__(self):
        return len(self.__d)

    def __contains__(self, key):
        return key in self.__d

    def __iter__(self):
        for key in self.__d:
            yield key
wpercy
  • 9,636
  • 4
  • 33
  • 45
0

The Python3 documentation says that dict.keys() return value type (i.e. a view) is "set-like", so your return value should be "set-like" as well.

The objects returned by dict.keys(), dict.values() and dict.items() are view objects. They provide a dynamic view on the dictionary’s entries, which means that when the dictionary changes, the view reflects these changes.

Dictionary views can be iterated over to yield their respective data, and support membership tests:

....

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 ^).

Full text here: https://docs.python.org/3/library/stdtypes.html#dict-views

VPfB
  • 14,927
  • 6
  • 41
  • 75
0

This is already handled for you! collections.abc.MutableMapping includes a reasonable default implementation of keys that provides the same interface as dict.keys.

Simply delete your keys method, and you'll be using the inherited implementation.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • Well, I missed to report some useful info. See the updated question. – Marco Sulla Nov 09 '15 at 20:09
  • 1
    @MarcoSulla: It's not returning a list of Measurands. You've screwed up your `__iter__`; it's iterating over values instead of keys. Fix that, and the inherited `keys` implementation will work. – user2357112 Nov 09 '15 at 21:05
  • `__iter__` is correct. If I do `for m in mm` where `mm` is a `Measurands` object, I want that `m` is the i-th `Measurand` object, not its id. If you notice, the `Measurand` class is a mix of `set` and `dict` that I created because you can't do a `set` of unhashable objects. – Marco Sulla Nov 09 '15 at 22:29
  • @MarcoSulla: That's a violation of the mapping API, and if you're not going to follow the API, you shouldn't bother with `keys`, `values`, or `items`. They'll just cause confusion. – user2357112 Nov 09 '15 at 22:35
  • Not sure that it's a violation. Since I'm extending also `set`, I'm following the behaviour of `__iter__` of the `set` interface. – Marco Sulla Nov 09 '15 at 22:41
  • @MarcoSulla: You really shouldn't be extending MutableMapping at all. The mapping API specifies that iteration should iterate over keys and `in` should report whether the argument is a key. If you want iteration and `in` to work with values, you cannot comply with the mapping API. (Also, extending `MutableSet` or `MutableMapping` does not mean you extend `set` or `dict`.) – user2357112 Nov 09 '15 at 22:45
  • You're right. I'm not following the contract of both the `abc`s. I modified the class to iterate over keys and now `keys()`, `values()` and `items()` automagically work. I have to say anyway that I do not like the default behaviour of Python's (and many other languages) iteration over dictionaries. Iterating over lists does not return the index of the list. I can't understand why this must be different for dictionaries. – Marco Sulla Nov 10 '15 at 09:58