0

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 :)

K2000
  • 7
  • 1
  • 1
    In Python we rarely use "properties". It's very unusual to define getter and setter methods, that's generally the case in Java, but not in here. Why do you feel that you need them? – Óscar López Apr 09 '20 at 11:36
  • I think it was an example for people who come from languages like c++ , java, c# where you can define a variable as private, hence no one outside from the class can't change or read those variables. So you would have to define a getter or setter function to reach those private variables. –  Apr 09 '20 at 11:38
  • I do not have a use case in where I need them. Just curious for a scenario in which we need those properties because I could not understand the need as you said. – K2000 Apr 09 '20 at 11:38
  • 2
    It is neither incorrect nor dirty to access instance variables. That is perfectly fine. – Wombatz Apr 09 '20 at 11:41
  • @ÓscarLópez Properties are used *quite* often. It just isn't *required* that you use them in place of direct attribute access, and in fact their very purpose is to let you easily change your mind if you later decide that direct access is no longer desirable. – chepner Apr 09 '20 at 11:50

3 Answers3

7

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 ValueErrors 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
chepner
  • 389,128
  • 51
  • 403
  • 529
1

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.

Booboo
  • 18,421
  • 2
  • 23
  • 40
0

Check this Python @property explanation.

It's a great post about how does property in python works and why it's important to avoid:

  1. Properties changes by non-developer users (clients maybe).
  2. Thousands of code refactoring by inserting getters and setters into your main code
Henrique Branco
  • 1,055
  • 5
  • 24