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

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


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._xc = C()c.x = 'foo'  # setter calledfoo = c.x    # getter calleddel c.x      # deleter called


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

The "Pythonic" way is not to use "getters" and "setters", but to use plain attributes, like the question demonstrates, and del for deleting (but the names are changed to protect the innocent... builtins):

value = 'something'obj.attribute = value  value = obj.attributedel obj.attribute

If later, you want to modify the setting and getting, you can do so without having to alter user code, by using the property decorator:

class Obj:    """property demo"""    #    @property            # first decorate the getter method    def attribute(self): # This getter method name is *the* name        return self._attribute    #    @attribute.setter    # the property decorates with `.setter` now    def attribute(self, value):   # name, e.g. "attribute", is the same        self._attribute = value   # the "value" name isn't special    #    @attribute.deleter     # decorate with `.deleter`    def attribute(self):   # again, the method name is the same        del self._attribute

(Each decorator usage copies and updates the prior property object, so note that you should use the same name for each set, get, and delete function/method.

After defining the above, the original setting, getting, and deleting code is the same:

obj = Obj()obj.attribute = value  the_value = obj.attributedel obj.attribute

You should avoid this:

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

Firstly, the above doesn't work, because you don't provide an argument for the instance that the property would be set to (usually self), which would be:

class Obj:    def set_property(self, property, value): # don't do this        ...    def get_property(self, property):        # don't do this either        ...

Secondly, this duplicates the purpose of two special methods, __setattr__ and __getattr__.

Thirdly, we also have the setattr and getattr builtin functions.

setattr(object, 'property_name', value)getattr(object, 'property_name', default_value)  # default is optional

The @property decorator is for creating getters and setters.

For example, we could modify the setting behavior to place restrictions the value being set:

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

In general, we want to avoid using property and just use direct attributes.

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, and prevent its deletion, with appropriate messages to inform the user of its proper usage:

class Protective(object):    """protected property demo"""    #    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 value != int(value):            raise TypeError("protected_value must be an integer")        if 0 <= value <= 100:            self._protected_value = int(value)        else:            raise ValueError("protected_value must be " +                             "between 0 and 100 inclusive")    #    @protected_value.deleter    def protected_value(self):        raise AttributeError("do not delete, protected_value can be set to 0")

(Note that __init__ refers to self.protected_value but the property methods refer to self._protected_value. This is so that __init__ uses the property through the public API, ensuring it is "protected".)

And usage:

>>> p1 = Protective(3)>>> p1.protected_value3>>> p1 = Protective(5.0)>>> p1.protected_value5>>> 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_valueValueError: protectected_value must be between 0 and 100 inclusive>>> p1.protected_value = 7.3Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 17, in protected_valueTypeError: protected_value must be an integer>>> p1.protected_value = 101Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 15, in protected_valueValueError: protectected_value must be between 0 and 100 inclusive>>> del p1.protected_valueTraceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 18, in protected_valueAttributeError: do not delete, protected_value can be set to 0

Do the names matter?

Yes they do. .setter and .deleter make copies of the original property. This allows subclasses to properly modify behavior without altering the behavior in the parent.

class Obj:    """property demo"""    #    @property    def get_only(self):        return self._attribute    #    @get_only.setter    def get_or_set(self, value):        self._attribute = value    #    @get_or_set.deleter    def get_set_or_delete(self):        del self._attribute

Now for this to work, you have to use the respective names:

obj = Obj()# obj.get_only = 'value' # would errorobj.get_or_set = 'value'  obj.get_set_or_delete = 'new value'the_value = obj.get_onlydel obj.get_set_or_delete# del obj.get_or_set # would error

I'm not sure where this would be useful, but the use-case is if you want a get, set, and/or delete-only property. Probably best to stick to semantically same property having the same name.

Conclusion

Start with simple attributes.

If you later need functionality around the setting, getting, and deleting, you can add it with the property decorator.

Avoid functions named set_... and get_... - that's what properties are for.


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.pOut[3]: 'pants'In [4]: t.p = 10In [5]: t.pOut[5]: 20


matomo