1

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Vector
  • 10,879
  • 12
  • 61
  • 101
  • 4
    Why do you believe you need such a thing? – Duncan Jan 07 '13 at 11:33
  • 3
    Thats rather Un-Pythonic – asheeshr Jan 07 '13 at 11:34
  • 3
    I am not in interested in the criticism of Python purists. I'm looking for an answer to a question. – Vector Jan 07 '13 at 11:35
  • @Duncan - because... If you have an answer, I'd be very interested it hearing about it. If not, please do not vet my question. I've been developing for a LONG time using type safe languages, and I know what I'm looking for and why. If you don't, I suggest you read some good OOP books. – Vector Jan 07 '13 at 11:36
  • 2
    @Mikey, the reason I ask is because the answer will vary depending on what you're actually trying to do. Mostly the answer will be to do something else instead, but maybe not. – Duncan Jan 07 '13 at 11:38
  • The question is VERY simple IMO. See edit. – Vector Jan 07 '13 at 11:42
  • 2
    A better solution would be to make your type-checker only check it the object has the interface you want, which is not the same as actual type comparisons and much more Pythonic. You many years of experience are giving you the impression that it's only way to do things. – martineau Jan 07 '13 at 11:46
  • @Mikey Type checking doesn't suit Python - if you want a type-checked language, use one. Trying to turn Python into one will just cause problems. – Gareth Latty Jan 07 '13 at 13:10
  • Python is considered strongly typed in many respects - but type checking occurs at at runtime. WHEN type checking occurs is not the determining factor. Python is not a simple scripting language. – Vector Jan 07 '13 at 17:09
  • @Mikey Python is strongly, but dynamically typed. The two are unrelated. What you are talking about is type-checking, and doesn't suit Python's design, which emphasizes duck typing. (Hence the entire standard library and all built-ins using it extensively). – Gareth Latty Jan 07 '13 at 17:13
  • @martineau - "check it the object has the interface you want". I like this idea - but how do you do it? I posted another question @ http://stackoverflow.com/questions/14195075/whats-a-good-way-to-implement-something-simliar-to-an-interface-in-python. Perhaps your answer is related to what Duncan answered there - his solution is exactly what I'm looking for. – Vector Jan 07 '13 at 17:17
  • @Lattyware - Dikei below has proposed a simple and IMO fairly clean solution. The NAME of my derived class can indicate it is a dictionary of type T, and _setItem will enforce that rule. Not as clean as List but good enough. Do you have a problem with that approach? – Vector Jan 07 '13 at 17:27
  • Yes, because it's type checking. Python is built around the idea that a type doesn't matter, what it can do does. So if I want an iterator, I just implement `__next__()` and `__iter__()` and go on my way, if you start type checking, that won't work, despite the fact it would work if you let it! That is the essence of duck typing, and how Python is designed to work. Try and do what you want with the item, and if it fails, throw the exception. If you can't do that, then set up an ABC that checks for the right methods and attributes, but don't type check! – Gareth Latty Jan 07 '13 at 17:29
  • @Mikey: In Python the common way to handle interface checking is not to, see my [answer](http://stackoverflow.com/a/3931746/355230) to another question about a related topic. If you really want to check first, you can usually explicitly inspect/query the object and see if it has the methods or data members required before you try to access or use them ('LBYL'). – martineau Jan 07 '13 at 18:31

5 Answers5

7

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.

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • For well structured code and good design in complex systems it is necessary. Read any book about design.... So you're me telling Python cannot be used for implementing well written, flexible systems with readable source code that other developers can work on without scratching their heads for weeks. – Vector Jan 07 '13 at 16:37
  • @Mikey That's simply not true - look at any well coded Python program and you'll see it's not the case. Type safety is simply one way to ensure your program follows it's design. – Gareth Latty Jan 07 '13 at 16:40
  • You clearly don't understand the concept of duck typing. Programming good, flexible, readable systems doesn't require type checking and Python proves that. Python is should *not* be used to try and design a type-safe system, no. To do so undermines the whole language. – Gareth Latty Jan 07 '13 at 16:41
  • @Mikey I'm not suggesting it is. I'm saying there is a way that suits the language, and a way other developers will expect coming into your code. Going against that will make your code less effective, efficient, readable and maintainable. Why use a screwdriver to hammer a nail? – Gareth Latty Jan 07 '13 at 16:56
  • 1
    To be clear, this isn't anti-type-safety - I completely understand why some people prefer it, and some cases call for it, this is just a case of if you want type-safety, Python is the wrong tool for the job. – Gareth Latty Jan 07 '13 at 17:03
  • "You clearly don't" I understand it quite well - enough so that I know that it's a pandora's box that facilitates writing garbage, jumbled code that will become very problematic in large commercial systems maintained by development teams. I'd venture that 95% of enterprise level systems are written in strongly typed languages- Java, C#, C++, etc - there is a reason for that. There are things in Python that I love-but facilitation of garbage code is not one. As other posters have demonstrated, this can be dealt with quite simply - there are PYTHON LANGUAGE FEATURES to support it. – Vector Jan 07 '13 at 17:04
  • As other posters have demonstrated, this can be dealt with quite simply - there are PYTHON LANGUAGE FEATURES to support it. Perhaps the problem is not that "I clearly don't understand the concept of duck typing" but that you are not familiar with all the features the language supports. – Vector Jan 07 '13 at 17:07
  • 2
    There are not language features to support it - Python simply offers the flexibility to do what you want, which can be hacked into doing something like this. I could also make a dictionary that threw exceptions on every insert of a value - it doesn't mean it's a good idea. Python has `exec`, it doesn't make it a good idea to use it for 99% of stuff. If you don't like dynamic typing, then using a language that is built with it in mind is just silly. If you want Java, C# or C++, then go use them, instead of writing 'Python' that will cause headaches for others in future. – Gareth Latty Jan 07 '13 at 17:10
  • I work on large systems with long lifespans, and one of my main goals is always correct SEMANTICS and self documenting/self describing/Self validating/self enforcing code. For that, I agree that Python is NOT the correct tool. But it has other features-clean uncluttered style, powerful, concise expressiveness,etc, that I love. There are things you can do with one or two lines in Python that would take ten lines in most other popular languages. I guess what I'm really looking for is a strongly typed compiled language that offers that power. They are not mutually exclusive. – Vector Jan 07 '13 at 21:10
  • 1
    @Mikey No, although I'd argue that Python's uncluttered and concise code is helped a lot by it. The closest to what you are describing I know of is Boo which is essentially Python crossed with C# (statically typed, .net). I would still say developing in a Python style doesn't preclude correct semantics, but if you can't look beyond the one method of doing it, then good luck finding what you want. – Gareth Latty Jan 07 '13 at 21:15
  • "although I'd argue that Python's uncluttered and concise code is helped a lot by it." I agree from what I've seen there are some python capabilities that seem to require duck typing, but I write gobs of C#, C++ and Object Pascal code for Linux and Windows and I'm constantly coming across situations where I'd like to use Python list interpretation capabilities or python style range syntax - most of the time the strongly typed constraints of my language don't preclude it - it's just not there. Maybe I should look at Boo... but it sounds a little scary LOL.. – Vector Jan 07 '13 at 22:54
  • I meant Python 'list comprehension' capabilities. – Vector Jan 07 '13 at 23:54
5

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)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Your `update` method doesn't work quite right, it checks the keys rather than the values. `MyTypeDict(str).update({1:'hello'})` throws `TypeError`. – Duncan Jan 07 '13 at 11:46
  • @Duncan: You are quite right; it was half-finished (multitasking doesn't work). – Martijn Pieters Jan 07 '13 at 11:48
  • It still goes wrong if the keys are themselves tuples. That may not matter, but it depends what the OP actually wants to use this for. – Duncan Jan 07 '13 at 12:01
  • @Duncan: Yeah, I've switched that to using ducktyping on `.iteritems()` instead. – Martijn Pieters Jan 07 '13 at 12:04
4

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() 
Kien Truong
  • 11,179
  • 2
  • 30
  • 36
  • This is sufficent for what I want to do right now. The NAME of my derived class can indicate it is a dictionary of type T, and _setItem will enforce that rule. Not as clean as List but good enough. – Vector Jan 07 '13 at 17:28
2

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.

Duncan
  • 92,073
  • 11
  • 122
  • 156
  • I'm not so worried about shooting myself in the foot as other developers shooting themselves in the foot.is malicious code – Vector Jan 07 '13 at 16:43
  • SELF DEFINING/SELF DOCUMENTING CODE in large systems is my chief concern. Code that when another developer looks at it (or myself six months later...), they will get a clear idea from the classes and types being used about what's going on in a module. Martijn's answer is 'overkill' for what I need. Dikei's answer, which you also suggested, is probably sufficient - clearer and easier than my solution. – Vector Jan 07 '13 at 16:53
  • 2
    @Mikey Anything you do like this is likely to confuse any Python developer that comes across it - it's completely counter-productive and no Python programmer would make the assumption that this was the way something would work. – Gareth Latty Jan 07 '13 at 16:55
0

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)
Vector
  • 10,879
  • 12
  • 61
  • 101