Reputation: 11703
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
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
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
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
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
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