Search code examples
pythongetter-setter

Correct usage of a getter/setter for dictionary values


I'm pretty new to Python, so if there's anything here that's flat-out bad, please point it out.

I have an object with this dictionary:

traits = {'happy': 0, 'worker': 0, 'honest': 0}

The value for each trait should be an int in the range 1-10, and new traits should not be allowed to be added. I want getter/setters so I can make sure these constraints are being kept. Here's how I made the getter and setter now:

def getTrait(self, key):
    if key not in self.traits.keys():
        raise KeyError

    return traits[key]

def setTrait(self, key, value):
    if key not in self.traits.keys():
        raise KeyError

    value = int(value)

    if value < 1 or value > 10:
        raise ValueError

    traits[key] = value

I read on this website about the property() method. But I don't see an easy way to make use of it for getting/setting the values inside the dictionary. Is there a better way to do this? Ideally I would like the usage of this object to be obj.traits['happy'] = 14, which would invoke my setter method and throw a ValueError since 14 is over 10.


Solution

  • If you are willing to use syntax like obj['happy'] = 14 then you could use __getitem__ and __setitem__:

    def __getitem__(self, key):
        if key not in self.traits.keys():
            raise KeyError
        ... 
        return traits[key]
    
    def __setitem__(self, key, value):
        if key not in self.traits.keys():
            raise KeyError
        ...
        traits[key] = value
    

    If you really do want obj.traits['happy'] = 14 then you could define a subclass of dict and make obj.traits an instance of this subclass. The subclass would then override __getitem__ and __setitem__ (see below).

    PS. To subclass dict, inherit from both collections.MutableMapping, and dict. Otherwise, dict.update would not call the new __setitem__.

    import collections
    class TraitsDict(collections.MutableMapping,dict):
        def __getitem__(self,key):
            return dict.__getitem__(self,key)
        def __setitem__(self, key, value):
            value = int(value)
            if not 1 <= value <= 10:
                raise ValueError('{v} not in range [1,10]'.format(v=value))
            dict.__setitem__(self,key,value)
        def __delitem__(self, key):
            dict.__delitem__(self,key)
        def __iter__(self):
            return dict.__iter__(self)
        def __len__(self):
            return dict.__len__(self)
        def __contains__(self, x):
            return dict.__contains__(self,x)
    
    class Person(object):
        def __init__(self):
            self.traits=TraitsDict({'happy': 0, 'worker': 0, 'honest': 0})
    
    p=Person()
    print(p.traits['happy'])
    # 0
    
    p.traits['happy']=1
    print(p.traits['happy'])
    # 1
    
    p.traits['happy']=14
    # ValueError: 14 not in range [1,10]