Dictionary with dynamic type signature

TrustyTony 0 Tallied Votes 274 Views Share

This is bit funny code as you can first read from this excellent response: http://www.daniweb.com/software-development/python/threads/427341/custom-types-in-a-simple-way#post1828718 why should you not use it, even it maybe feels useful.

Anyway I got curious to see how type signature could be implemented, even using it would generally just be stupid way of slowing down your code. But like mountain climbers tell about why they climb the mountain: "Because it is there". So here this un-Pythonic do not trust user as responsible adult code is. Maybe those hasattrs could have been try...except, but as it is unpythonic any case, who cares...

Also using recursion for type check would probably make lot of sense and clean code. It would make sense to make type check a function and use it both for key and the value, now key can be only be checked against single type.

from itertools import chain

class CustomDict(dict):
    """dictionary accepting values only with given type signature types"""
    def __init__(self, types, *args, **kwargs):
        """ type signature as first parameter types
            required:
            first key's required type or None for everything goes
            and after each type of value sequence for key or None for no check
        """
        if types is None:
            # same as normal dictionary: anything goes
            self.keytype, self.types = None, None
        else:
            self.keytype, self.types = types[0], types[1]
        # can not use method from parent as it would use
        # dict's __setitem__
        for key,val in chain(kwargs.items(), *args):
            self[key] = val
        
    def __setitem__(self, key, value):
        """ set items using value type signature """
        if self.keytype is not None and type(key) != self.keytype:
            raise TypeError("Key %s is not of type %s" % (key, self.keytype.__name__))
        # check not specified (None) and scalar type
        if self.types is not None and type(value) != self.types:
            if hasattr(value, '__len__') and hasattr(self.types, '__len__'):
                if len(value) != len(self.types):
                    raise TypeError("Value not of length %i: %r" % (len(self.types), value))
                for argtype, val in zip(self.types, value):
                    if argtype and type(val) != argtype:
                        raise TypeError("The part of value for key %r is not of %s type: %r" % (key, argtype.__name__, val))
            else:
                raise TypeError("Scalar types mismatch, %s not of %s" % (value, self.types))
        # use method from parent
        dict.__setitem__(self, key, value)

    def __setattr__(self, key, value):
        self.__dict__[key] = value

    __getattr__ = dict.__getitem__

if __name__ == '__main__':
    mydict = CustomDict((str, (int, int, str)),
                        ((str(a), (a,b, str(a+b)))
                         for a in range(10)
                         for b in range(10)),
                        tony=(1,1,'passed'))

    mydict['kieth'] = (100, 50, 'puppy')
    mydict['bill'] = (300, 32, 'cat')

    print(mydict)
    try:
        mydict['iris'] = (50, 4, 23)
    except TypeError as e:
        print(e)

    mine = CustomDict((str, None))
    mine['a'] = 'a'
    try:
        mine[2] = 'a'
    except TypeError as e:
        print(e)

    mine['b'] = (1,2,34,9)

    print('Dynamic change of types')
    mine.types = (str, str)

    try:
        mine['c'] = 'a'
    except TypeError as e:
        print(e)

    mine['c'] = ('a','b')
    mine['d'] = 'Hi'
    
    print(mine)

    d = CustomDict((str, (None, str)))
    d['a'] = 'ab'
    d['d'] =  ['Anything goes'], 'This must be string'
    
    try:
        d['c'] = 'abc'
    except TypeError as e:
        print(e)

    print('value of key d by field access: {d.d}'.format(d=d))
    print('d:\n%s' % d)

    md = CustomDict((int, int))
    try:
        md[1]='a'
    except TypeError as e:
        print(e)
    md[1] = 4
    
"""Output:
{'9': (9, 9, '18'), 'kieth': (100, 50, 'puppy'), 'bill': (300, 32, 'cat'), '1': (1, 9, '10'), '0': (0, 9, '9'), '3': (3, 9, '12'), '2': (2, 9, '11'), '5': (5, 9, '14'), '4': (4, 9, '13'), '7': (7, 9, '16'), '6': (6, 9, '15'), 'tony': (1, 1, 'passed'), '8': (8, 9, '17')}
The part of value for key 'iris' is not of str type: 23
Key 2 is not of type str
Dynamic change of types
Value not of length 2: 'a'
{'a': 'a', 'c': ('a', 'b'), 'b': (1, 2, 34, 9), 'd': 'Hi'}
Value not of length 2: 'abc'
value of key d by field access: (['Anything goes'], 'This must be string')
d:
{'a': 'ab', 'd': (['Anything goes'], 'This must be string')}
Scalar types mismatch, a not of <type 'int'>
W"""