when to use getter and setter with property?

Question:

When should you use a property with getters/setters? It is not pythonic or wrong to not use a property with getters and setters? Should or shouldn’t I write it with a property?

Examples:

class Person:
    def __init__(self, firstname, lastname, age):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age

    def say_hi(self):
        print(f"""Hi i'm {self.firstname} {self.lastname} and i'm {self.age}""")

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, newage):
        if not isinstance(newage, int):
            raise TypeError("Expect an Integer")
        self._age = newage

versus

class Person2:
    def __init__(self, firstname, lastname, age):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age

    def say_hi(self):
        print(f"""Hi i'm {self.firstname} {self.lastname} and i'm {self.age}""")

    def get_age(self):
        return self.age

    def set_age(self, newage):
        if not isinstance(newage, int):
            raise TypeError("Expect an Integer")
        self.age = newage

Asked By: kjay

||

Answers:

The pythonic way would be not to use setters and getters at all; just have an attribute:

class Person:
    def __init__(self, firstname, lastname, age):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age

    def say_hi(self):
        print(f"Hi i'm {self.firstname} {self.lastname} and i'm {self.age}")

If you want to check types, use type annotations and a checker like mypy:

class Person:
    def __init__(self, firstname, lastname, age):
        self.firstname: str = firstname
        self.lastname: str = lastname
        self.age: int = age

    def say_hi(self):
        print(f"Hi i'm {self.firstname} {self.lastname} and i'm {self.age}")

If it later turns out that you do need to do something more complex, you can always turn it into a property later with no change of interface.

Answered By: Jiří Baum

You should generally prefer to use "protected" variables (such as those starting with _) with properties (not separate functions that users need to call, that’s just clunky), as it confers some advantages. This encapsulation is very handy as it:

  • lets you control the internal data completely, such as preventing people entering ages like -42 (which they will do if they can); and
  • lets you change the underlying implementation in any manner you want, without affecting clients.

For example on that last point, you may want to maintain a separate structure of all names and simply store references to those names in your Person class. This can allow you to store many more names, as the surname "Von Grimmelshausen" would be stored once (in the separate structure) and as much smaller indexes in all the Person objects that use it.

You can then totally change the naive getter from:

@property
def surname(self):
    return self._surname

to:

@property
def surname(self):
    return self._surname_db[self._surname_index]

without any changes to clients.

Answered By: paxdiablo

"Pythonic" is a holy struggle.

I personally prefer the Class under full control.

In your case:

class Person:

    def __init__(self, firstname, lastname, age):
        self.firstname = firstname
        self.lastname = lastname
        self.age = age

    def say_hi(self):
        print(f"Hi i'm {self.firstname} {self.lastname} and i'm {self.age}")

    def test_str(self, cosi):
        return self.test(cosi, str)

    @staticmethod
    def test(cosi, neco):
        assert isinstance(cosi, neco), f"Bad value! {cosi} is not instance" 
                                       f" from {neco.__name__}"
        return cosi

    @staticmethod
    def test_positiv_int(num):
        assert 0 < int(num), f"Expect an positiv integer"  # negative value protect
        return int(num)  # if int is like string this returned int

    def __setattr__(self, key, value):
        # important!!!:
        whitedict = dict(firstname=self.test_str,
                         lastname=self.test_str,
                         age=self.test_positiv_int
                         )
        # Call fn from whitedict with parameter
        self.__dict__[key] = whitedict[key](value)
Answered By: Radek Rojík

The second version of your code (referring to class2) utilizes two instance methods i.e get_age and
set_age which are not serving much of a purpose because you can retrieve the age attribute of an instance without calling the get_age method, also you can set the age attribute to literally anything without invoking your set_age method. Also if you want user to retrieve or set the age attribute using your given instance methods, the user who was using your class previously will have to make changes in their code which is something we do not want.


Now, the first version of your code (referring to class1) is very helpful because you can pose restrictions on the age attribute by using property decorators. You can make the age attribute read only or both read and write and you can just retrieve or set the age attribute of an instance normally without having to call any methods.

Also, as you explicitly need to call the set_age method on an instance in second version of your code, for this
piece of logic :

if not isinstance(newage, int):
    raise TypeError("Expect an Integer")
self._age = newage

to execute so the user cannot put any arbitrary value into the age attribute, on the other hand it happens implicitly whenever you try to set the age attribute when you use properties.

Answered By: madhur
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.