Reputation: 7
I recently started learning programming and I do not quite understand the need for properties. Why is it incorrect or dirty to just access the instance variable. For example if we make an employee class with the attributes first_name and last_name. Why do we need a getter and setter to access these variable instead of just saying e1.first_name and e1.last_name?
Thanks in advance :)
Upvotes: 0
Views: 79
Reputation: 1930
Check this Python @property explanation.
It's a great post about how does property in python works and why it's important to avoid:
Upvotes: 0
Reputation: 44013
See Abstraction VS Information Hiding VS Encapsulation.
A class in Object-Oriented Programming is an abstraction and it encapsulates data and the methods that operate on the data to implement that abstraction. Generally, it is considered good practice to hide the implementation of the class so that we are free to change that implementation without breaking programs that use the class.
Let's take the example of a class that has attributes fahrenheit_temperature
and celsius_temperature
that returns the current temperature of the object in either fahrenheit or celsius. I could implement this as two separate attributes/properties but if I did not have getter and setter methods for these properties, there would be no guarantee that these two temperatures would stay in sync because a client might set one without the other. But I can now define the setter method for each property to actually set new values for each temperature. Moreover, by having a getter and setter methods, I really only need to keep one actual attribute because I can, for example, calculate the celsius temperature from the fahrenheit temperature whenever that is required. So tomorrow I could change the implementation of the class and no client code will break.
Upvotes: 1
Reputation: 530862
Properties let you add constraints to existing code without changing the interface. Let's say you started with a simple class:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
People use this class in many places, and in each place the attributes name
and age
are accessed directly.
At some later time, you realize that you shouldn't be able to make the age negative, and maybe you'd like to make sure the age is actually an int
. If you tried to add a new method like set_age
to restrict the values one could assign to age
, you would have to update all the existing code (which may not be something you can do, if Person
is part of a library that anyone can use).
Instead, you change age
from an ordinary instance attribute to a property.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int) or int < 0:
raise ValueError("Age must be a non-negative integer")
self._age = age
Now, all the code like
p = Person("Alice", 13)
p.age = 14
continues to work exactly the way it did before. Code like
p = Person("Bob", 10)
p.age = -10
or
p = Person("Cassie", "old")
will now raise ValueError
s as desired.
Properties also let you define "computed attributes", which are not necessarily stored, but re-computed from other attribute values as necessary. A simple example:
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return math.pi * self.radius ** 2
@property
def perimeter(self):
return 2 * self.radius * math.pi
Neither the area nor the perimeter is stored, but will always return the correct value, no matter how often the radius is changed. Note, too, that you cannot assign directly to the area or the perimeter, because no setter was defined. You can only change either by changing the radius.
If the opertation is particularly expensive, you can cache the result to be re-used.
class Circle:
def __init__(self, radius):
self.radius = radius
self._area = None # Cached radius
# We could still access radius directly, but
# we want to "catch" changes to the radius
# to invalidate the stored area.
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, radius):
self._radius = radius
self._area = None # Invalidate the cache
# Too expensive, let's re-use a previous calculation
# if the radius hasn't changed.
@property
def area(self):
if self._area is None:
self._area = math.pi * self.radius ** 2
return self._area
# Cheap enough to not bother caching.
@property
def perimeter(self):
return 2 * self.radius * math.pi
Upvotes: 7