What is the python alternative for C#'s custom property setters?

73 Views Asked by At

I'm coming from C# world into python. In C#, I can use get and set to make updates to other object properties upon setting a value to a property, something like this:

private int _age;
private string _description;

public int Age
{
    set
    {
        _age = value;
        _description = (_age < 1500) ? "new": "old";
    }
}

In python, I have the following class definition:

class Record(object):
    age = 0
    description = ""
    <...a bunch of other properties...>

How can I set the description property of a Record object when I set the age property, in the same way as I can do it in C#, without having to write custom functions like get_age(self): and set_age(self):?

3

There are 3 best solutions below

1
The_spider On BEST ANSWER

You should use the property decorator for this:

class Record:
    def __init__(self):
        self._age = 0
        self._description = ""
        
    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        self._age = value
        self._description = "new" if value < 1500 else "old"

    @property
    def description(self):
        return self._description

rec = Record()
print(rec.age, rec.description)
rec.age = 1000
print(rec.age, rec.description)
rec.age = 2000
print(rec.age, rec.description)

output:

0 
1000 new
2000 old

When you use @property, every time you use rec.description, you are actually calling rec.description() and the return value of that call is used as the attributes value. This value gets recalculated every time you use the attribute.

If there was only the @property decorator, it would be impossible to set the attribute, as there is only a function that calculates the value. Luckily, we can use @age.setter. Every time the attribute is set (rec.age = value), this function is called.

We can use of this to make the description attribute also being set when the age attribute is set.

If you want to learn more about decorators, this page is a good start.

Another, less recommended, way to do this is using __setattr__:

class Record:
    def __init__(self):
        self.age = 0
        self.description = ""
        
    def __setattr__(self, key, value):
        super().__setattr__(key, value)
        if key == "age":
            self.description = "new" if value < 1500 else "old"

rec = Record()
print(rec.age, rec.description)
rec.age = 1000
print(rec.age, rec.description)
rec.age = 2000
print(rec.age, rec.description)

The output is exactly the same.

Every time we set an attribute on an instance by using instance.attrname = attrvalue, python calls instance.__setattr__("attrname", attrvalue). Usually, this is the __setattr__ function which is inherited from object, the class from which every class inherits.

But when we override this function, we can choose our own behavior. In this case, it means that we also set the description attribute when the attribute 's name is "age".

But, of course, we should still call the object's __setattr__ function to make that the attribute is actually set. We could do this directly using object.__setattr__(self, key, value), but a better way to do this is using super. super() allows us to call functions of the class from which there is inherited. In this case it's object, but if you later on decide to let Record inherit from another class that also defines __setattr__, that class's __setattr__ will be called.

0
matszwecja On

One Pythonic way to do it is using property decorator, which creates a method that works like attribute:

class Record:
    def __init__(self, age):
        self.age = age
    
    @property
    def description(self):
        return "new" if self.age < 1500 else "old"
    
print(Record(age = 1600).description) # old

With this approach, description is not stored in memory but instead it's recalculated every time you access it.

Another way would be with full suite of getter and setter decorators, but it requires creating additional methods you wanted to avoid:

class Record:
    def __init__(self, age):
        self.description = None
        self.age = age
    
    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        self.description = "new" if value < 1500 else "old"
        self._age = value
    
print(Record(age = 1600).description)

This way description is stored in the object, and will get updated every time you set value to self.age.

3
Achille G On

Decorators let you use a setter and a getter for your class

class Record:
    def __init__(self, age):
        self.age = age
        self.description = age

    # using property decorator
    # a getter function
    @property
    def description(self):
        print("getter method called")
        return self._description
    
    # a setter function
    @description.setter
    def description(self, age):
        if(age < 1500):
            self._description = "new"
        else:
            self._description = "old"