Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 335
jb. Avatar asked Oct 13 '11 21:10

jb.


People also ask

Should you use getters and setters in python?

Getters and Setters in python are often used when: We use getters & setters to add validation logic around getting and setting a value. To avoid direct access of a class field i.e. private variables cannot be accessed directly or modified by external user.

What is the purpose of getter and setter?

Getters and setters are used to protect your data, particularly when creating classes. For each instance variable, a getter method returns its value while a setter method sets or updates its value. Given this, getters and setters are also known as accessors and mutators, respectively.

How are values in a dictionary accessed?

The well-known, or I should say the traditional way to access a value in a dictionary is by referring to its key name, inside a square bracket. Notice that when you want to access the value of the key that doesn't exist in the dictionary will result in a KeyError.

Should I always use getters and setters?

Using getters and setters, is always, in my opinion good practice. One thing you should avoid is to have external entities mess with the internal structure of your class at will. Typical example, consider having a dateOfBirth parameter.


2 Answers

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]
like image 122
unutbu Avatar answered Oct 05 '22 23:10

unutbu


Some obvious tips come to my mind first:

  1. Do not use .keys() method when checking for existence of some key (instead of if key not in self.traits.keys() use if key not in self.traits).
  2. Do not explicitly throw KeyError exception - it is thrown if you try to access inexistent key.

Your code could look like this after above changes:

def getTrait(self, key):
    return traits[key]

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

    value = int(value)

    if value < 1 or value > 10:
        raise ValueError

    traits[key] = value

Ps. I did no check the correctness of your code thoroughly - there may be some other issues.

like image 37
Tadeck Avatar answered Oct 05 '22 22:10

Tadeck