Vector
Vector

Reputation: 11703

Type safe Python 3.2 dictionary

What's a good way to implement a type safe dictionary in Python (3.2) - a dictionary that will only allow adding objects of a particular type to itself?

I myself have a simple solution: build a wrapper class around the dictionary with an 'addItem' method that does a type check assertion before adding the object. Looking to see if someone has something better.

Upvotes: 1

Views: 1873

Answers (5)

Vector
Vector

Reputation: 11703

Based on the answers here from Dikei and Martijn Pieters, I came up with this simple implemetation, which is sufficient for my needs:

class ETypedDictonaryException (Exception):
    pass

class TypedDict(dict):

    def __init__(self, keyType, valueType):
        dict.__init__(self)
        self.keyType = keyType
        self.valueType = valueType

    def __setitem__(self, key, value):
        if ( not isinstance(key, self.keyType) or not isinstance(value, self.valueType) ):
            raise  ETypedDictonaryException("wrong key type:" +str(self.keyType) + " and " +str(self.valueType)+ "  required!")
        dict.__setitem__(self, key, value)

Upvotes: 1

Gareth Latty
Gareth Latty

Reputation: 88977

The Pythonic way here is to just use a normal dictionary and only add objects of a particular type to it - don't try to enforce the restriction, it shouldn't be necessary.


Edit: To expand my argument, let me explain - you seem to be under the impression that writing good code requires type safety. The first question is why? Sure, type safety catches some errors at compile time, but in my experience, those errors are rare, easy to catch with even the most trivial testing, and generally easy to fix.

By contrast, the most annoying, hard to fix, and hard to test for bugs are logical ones, that the computer can't spot at all. These are best prevented by making readable code that is easy to understand, so errors stand out more. Dynamic typing massively helps with that by reducing the verbosity of code. You can argue typing makes it easier to read the code (as one can see the types of variables as you use them), but in dynamic languages, this kind of thing is given by naming carefully - if I name a variable seq, people will presume it's a sequence and can be used as such. A mixture of descriptive naming and good documentation makes dynamic code far better, in my experience.

When it comes down to it, type safety in a language is a matter of preference, however, Python is a dynamic language designed around the idea of duck typing. Everything in the language is designed around that and trying to use it in another way would be incredibly counter-productive. If you want to write Java, write Java.

Upvotes: 7

Duncan
Duncan

Reputation: 95652

Martijn has given you an answer, but as you can see getting the corner cases right is tricky.

If all you want is to avoid shooting yourself in the foot then his answer may be more than you actually need; perhaps you only need to wrap __setitem__, or perhaps you would be better off letting any type go into the dictionary but doing some sort of assertion when you've finished adding things or when you access them. The last of these is, of course, the usual answer from Python people: if the objects in the dictionary don't implement the correct interface let the code break when they're used instead of checking up front.

On the other hand, if you need to protect against malicious code injecting rogue values Martijn's code is insufficient; you can work around it trivially by calling:

dict.__setitem__(mydict, key, rogue_value)

Also, if you really meant to limit objects to a single type his answer isn't what you wanted; you could have an object that passed the isinstance test but didn't provide the correct duck-typing behaviour.

That's why more context to the question would be useful.

Upvotes: 2

Martijn Pieters
Martijn Pieters

Reputation: 1121644

By sub-classing dict and adding guards to __setitem__, .update() and .setdefault(); adding a .fromkeys() class method that takes the type from the default value is a nice extra:

from itertools import tee

class MyTypeDict(dict):
    def __init__(self, type_=SomeType, *args, **kw):
        self.type = type_
        super(MyTypeDict, self).__init__(*args, **kw)
        for val in self.itervalues():
            self._checktype(val)

    @classmethod
    def fromkeys(cls, seq, value=SomeType()):
        res = cls(type_=type(value))
        res.update((k, value) for k in seq)
        return res

    def _checktype(self, value):
        if not isinstance(value, self.type):
            raise TypeError('Value type {!r} not allowed'.format(type(value)))

    def __setitem__(self, key, value):
        self._checktype(value)
        super(MyTypeDict, self).__setitem__(key, value)

    def update(self, other):
        # Loop over other, either a dict or an iterable (use a copy with `tee`)
        # for python 3, use `items()` instead.
        items = other.iteritems() if hasattr(other, 'iteritems') else tee(other)
        for key, value in items:
            self._checktype(value)
        super(MyTypeDict, self).update(other)

    def setdefault(self, key, default=None):
        if default is None:
            default = self.type()  # assumes no-args initializer
        else:
            self._checktype(default)
        return super(MyTypeDict, self).setdefault(key, default)

Use this as:

mydict = MyTypeDict(type_=SomeType)

Upvotes: 5

Kien Truong
Kien Truong

Reputation: 11381

I think you can extend the dictionary and overwrite the __setitem__ method

class MyDict(dict):
    def __setitem__(self, key, val):
        #Test for proper interface
        if val.pass_the_test:
            dict.__setitem__(self, key, val)
        else:
            raise SomeKindOfException() 

Upvotes: 4

Related Questions