Reputation: 441
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
Upvotes: 1
Views: 2456
Reputation: 27
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.
Upvotes: 0
Reputation: 134
"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)
Upvotes: 0
Reputation: 882586
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:
-42
(which they will do if they can); andFor 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.
Upvotes: 4
Reputation: 6940
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.
Upvotes: 3