Search code examples
pythonsuper

Using super with __getitem__ versus subscript


I'm writing a dict which only has tuples of positive integers as keys. If the key is unknown and one of the elements of the tuple is 1, lookup should return a default value of 0. Any other unknown key should raise KeyError.

This works fine:

class zeroDict(dict):
    '''
    If key not in dict and an element of the tuple is 
    a 1, impute the value 0.
    '''    
    def __init__self():
        super().__init__()
    def __getitem__(self, key):
        try:
            return super().__getitem__(key)
        except KeyError:
            if 1 in key:
                return 0
            else:
                raise   

This does not:

class zDict(dict):
    '''
    If key not in dict and an element of the tuple is 
    a 1, impute the value 0.
    '''    
    def __init__self():
        super().__init__()
    def __getitem__(self, key):
        try:
            return super()[key]
        except KeyError:
            if 1 in key:
                return 0
            else:
                raise  

When I try to read a value from zDict I get TypeError: 'super' object is not subscriptable.

The only difference between the implementations is that zeroDict says

return super().__getitem__(key) 

and zDict says

return super()[key]

However, help(dict.__getitem__) prints

__getitem__(...)
    x.__getitem__(y) <==> x[y]   

which seems to say that the two statements are equivalent. What is going on here?


Solution

  • As others have explained, the reason that super() is not working here is because it returns a super object, which is a proxy object that handles dispatching dotted attribute access to the next class in the method resolution order.

    That being said, you shouldn't be overriding __getitem__ here, the python data-model provides something just for this case, it's the __missing__ method:

    object.__missing__(self, key)

    implement self[key] for dict subclasses when key is not in the dictionary.Called by dict.__getitem__()

    So, do something like this:

    class ZeroDict(dict):
        def __missing__(self, key):
            if 0 in key:
                return 0
            else:
                raise KeyError(key)
    

    And a demonstration:

    >>> class ZeroDict(dict):
    ...     def __missing__(self, key):
    ...         if 0 in key:
    ...             return 0
    ...         else:
    ...             raise KeyError(key)
    ...
    >>> d = ZeroDict()
    >>> d[(1, 0)] = 'foo'
    >>> d
    {(1, 0): 'foo'}
    >>> d[1, 0]
    'foo'
    >>> d[1, 1]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 6, in __missing__
    KeyError: (1, 1)
    >>> d[0, 1]
    0
    >>>