Home » Python » What's the pythonic way to use getters and setters?

What's the pythonic way to use getters and setters?

Posted by: admin November 1, 2017 Leave a comment

Questions:

I’m doing it like:

def set_property(property,value):  
def get_property(property):  

or

object.property = value  
value = object.property

I’m new to Python, so i’m still exploring the syntax, and i’d like some advice on doing this.

Answers:

The Pythonic way is to not use them. If you must have them then hide them behind a property.

Questions:
Answers:

Try this: Python Property

The sample code is:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        print("getter of x called")
        return self._x

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x

Questions:
Answers:

What’s the pythonic way to use getters and setters?

def set_property(property,value):  
def get_property(property):  

or

object.property = value  
value = object.property

The second example is Pythonic (see point 3 below for qualification). The first example is non-Pythonic – for several reasons:

  1. we have builtin functions, setattr and getattr:

    setattr(object, 'property_name', value)
    getattr(object, 'property_name', default_value)  # default is optional
    
  2. we have the @property decorator for creating getters and setters, which are created like this:

    class Protective(object):
    
        @property
        def protected_value(self):
            return self._protected_value
    
        @protected_value.setter
        def protected_value(self, value):
            if acceptable(value): # e.g. type or range check
                self._protected_value = value
    
  3. While the above is the Pythonic way to protect attributes, in general, we want to avoid using property and just use direct attributes. Assuming that’s what you meant in your second example, let’s just change the meta-variable name from property to attribute:

    object.attribute = value
    value = object.attribute
    

    In general, this is what is expected by users of Python. Following the rule of least-surprise, you should try to give your users what they expect unless you have a very compelling reason to the contrary.

Demonstration

For example, say we needed our object’s protected attribute to be an integer between 0 and 100 inclusive:

class Protective(object):
    def __init__(self, start_protected_value=0):
        self.protected_value = start_protected_value
    @property
    def protected_value(self):
        return self._protected_value
    @protected_value.setter
    def protected_value(self, value):
        if isinstance(value, int) or (isinstance(value, float) 
                                      and value.is_integer()):
            if 0 <= value <= 100:
                self._protected_value = int(value)
            else:
                raise ValueError("protected_value must be " +
                                 "between 0 and 100 inclusive")
        else:
            raise TypeError("protected_value must be an integer")

And usage:

>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive

Questions:
Answers:

Check out the @property decorator.

Questions:
Answers:
In [1]: class test(object):
    def __init__(self):
        self.pants = 'pants'
    @property
    def p(self):
        return self.pants
    @p.setter
    def p(self, value):
        self.pants = value * 2
   ....: 
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20

Questions:
Answers:

Define __setattr__(self, name, value) and/or __getattr__(self, name).