0

I want to create a class that would extend dict's functionalities. This is my code so far:

class Masks(dict):

    def __init__(self, positive=[], negative=[]):
        self['positive'] = positive
        self['negative'] = negative

I want to have two predefined arguments in the constructor: a list of positive and negative masks. When I execute the following code, I can run

m = Masks()

and a new masks-dictionary object is created - that's fine. But I'd like to be able to create this masks objects just like I can with dicts:

d = dict(one=1, two=2)

But this fails with Masks:

>>> n = Masks(one=1, two=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() got an unexpected keyword argument 'two'

I should call the parent constructor init somewhere in Masks.init probably. I tried it with **kwargs and passing them into the parent constructor, but still - something went wrong. Could someone point me on what should I add here?

dreftymac
  • 31,404
  • 26
  • 119
  • 182
ducin
  • 25,621
  • 41
  • 157
  • 256

2 Answers2

2

You must call the superclass __init__ method. And if you want to be able to use the Masks(one=1, ..) syntax then you have to use **kwargs:

In [1]: class Masks(dict):
   ...:     def __init__(self, positive=(), negative=(), **kwargs):
   ...:         super(Masks, self).__init__(**kwargs)
   ...:         self['positive'] = list(positive)
   ...:         self['negative'] = list(negative)
   ...:         

In [2]: m = Masks(one=1, two=2)

In [3]: m['one']
Out[3]: 1

A general note: do not subclass built-ins!!! It seems an easy way to extend them but it has a lot of pitfalls that will bite you at some point.

A safer way to extend a built-in is to use delegation, which gives better control on the subclass behaviour and can avoid many pitfalls of inheriting the built-ins. (Note that implementing __getattr__ it's possible to avoid reimplementing explicitly many methods)

Inheritance should be used as a last resort when you want to pass the object into some code that does explicit isinstance checks.

Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • Be careful when using `or` this way; there may be valid false values that could be passed to it. Consider using a private sentinel object and using `is`. – Ignacio Vazquez-Abrams Aug 23 '13 at 15:00
  • @IgnacioVazquez-Abrams I removed them. I believe a default of `()` and an explicit conversion to `list` is easier to understand(and allows the arguments to be any iterable), even though it has slightly different semantics. – Bakuriu Aug 23 '13 at 15:03
  • 2
    Subclassing builtins is no more fraught with peril than subclassing anything else, and is frequently quite useful. The python builtins are well designed to support subclassing, especially `dict`. Although I'm not convinced that a subclass of dict is necessarily the best option for tdelaney, this sweeping advice is contradictory to my own experiences. -1. – SingleNegationElimination Aug 23 '13 at 15:25
  • @TokenMacGuy I don't agree with your statement. In my experience, *every* time I tried to subclass a built-in I discovered that it was much *simpler* and less bug-prone to use delegation(or another, completely different solution to the problem). It's true that the problem isn't the built-ins per se, but inheritance, but this is particularly true with built-ins since they tend to make heavy use of special methods, which can give a lot of surprises if you aren't aware of their exact semantics and how exactly they are called by the interpreter. – Bakuriu Aug 23 '13 at 15:39
  • FWIW I _strongly_ agree with @TokenMacGuy, and have found subclassing built-in to be a very useful. It was/is considered by many to be a significant improvement when it was first introduced into Python -- see [Unifying types and classes in Python 2.2](http://www.python.org/download/releases/2.2/descrintro/#subclassing) by Guido van Rossum. Bakuriu, if you've encountered issues subclassing them yourself, perhaps it was due to a limited understanding of how/when to and not to use them on your own part...and not necessarily something that would necessarily apply universally to everyone else. – martineau Aug 23 '13 at 17:54
-1

Since all you want is a regular dict with predefined entries, you can use a factory function.

def mask(*args, **kw):
    """Create mask dict using the same signature as dict(),
    defaulting 'positive' and 'negative' to empty lists.
    """
    d = dict(*args, **kw)
    d.setdefault('positive', [])
    d.setdefault('negative', [])
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • The OP never mentioned that all he wants to do is to provide some default values. He simply stumbled across a problem when writing its subclass. He probably *does* want to add more methods or data in its subclass, and using a function wont work well in that case. – Bakuriu Aug 23 '13 at 15:41
  • @Bakuriu hmmm... I must not understand what _I want to have two predefined arguments in the constructor:_ means. – tdelaney Aug 23 '13 at 15:44
  • Can you point out where the OP said that that's the **only** thing that he wants? AFAICT that's only a (pretty useless, since it doesn't add information over the code) comment on the code above, not a formalization of what he meant when saying that he wants to extend `dict`. – Bakuriu Aug 23 '13 at 16:22
  • @Bakuriu I want a dict with default entries. And a pony. – tdelaney Aug 23 '13 at 16:32