Why use Pythonic getters and setters?

What is the python getter setter approach for implementing getters and setters in Python?

Currently, I’m using methods like: def set_property(property, value): def get_property(property): or object.property = value value = object.property

What is the most pythonic way to use getters and setters in Python?

Hello @klyni_gg, Hope you are doing great,

Here is the detailed explanation of the above question:-

Using Property Decorators The most Pythonic way to implement getters and setters in Python is by using the property decorator. This allows you to define methods for getting, setting, and deleting attributes, while accessing them as if they were regular attributes. This is cleaner and follows Python’s philosophy of ‘beautiful is better than ugly.’

class MyClass:
    def __init__(self, value):
        self._value = value
    
    @property
    def value(self):
        """Getter"""
        return self._value
    
    @value.setter
    def value(self, value):
        """Setter"""
        if value < 0:
            raise ValueError("Value must be non-negative")
        self._value = value

obj = MyClass(10)
print(obj.value)  # Calls the getter
obj.value = 20    # Calls the setter
print(obj.value)

This approach allows for encapsulating logic inside the getter and setter while keeping the syntax simple and intuitive.

Great explanation, @devan-skeem! To add to that, when using the @property decorator, combining it with @<property_name>.setter provides modularity and readability. Here’s another variation using the same decorators:

class MyClass:
    def __init__(self, value):
        self._value = value
    
    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        if value < 0:
            raise ValueError("Value must be non-negative")
        self._value = value

obj = MyClass(10)
print(obj.value)  # Getter
obj.value = 15    # Setter
print(obj.value)

This ensures controlled access to the attribute while keeping the code structure clear. It’s especially helpful when you want to enforce additional logic within setters or getters.

Excellent points, @devan-skeem and @Rashmihasija! Another interesting approach to control attribute access, especially in advanced scenarios, is using __getattr__ and __setattr__. This method is more dynamic and offers greater flexibility, although it should be used carefully to avoid complicating the code.

class MyClass:
    def __init__(self, value):
        self._value = value
    
    def __getattr__(self, name):
        if name == "value":
            return self._value
        raise AttributeError(f"{name} not found")
    
    def __setattr__(self, name, value):
        if name == "value":
            if value < 0:
                raise ValueError("Value must be non-negative")
            self._value = value
        else:
            super().__setattr__(name, value)

obj = MyClass(10)
print(obj.value)  # Calls __getattr__
obj.value = 20    # Calls __setattr__
print(obj.value)

This technique is powerful for scenarios requiring dynamic attribute handling. However, for simpler use cases, the property decorator is generally preferable for its clarity and maintainability.