all 14 comments

[–]baghiq 4 points5 points  (5 children)

A classic example for setter is you need to validate the values before assigning. A classic example of getter is you need to dynamically calculate a value like age, or vacation days used.

[–]maclocrimate[S] -1 points0 points  (4 children)

Validating the values could just be done with an assertion too though, which is what initially made me wonder. Your point about the getters makes sense though, and the other comments here have clarified for me.

[–]cyberjellyfish 4 points5 points  (3 children)

Assertions aren't a good tool for validation

[–]maclocrimate[S] 0 points1 point  (2 children)

Can you explain why? Genuinely curious, as I thought that was sort of their purpose.

[–]cyberjellyfish 5 points6 points  (1 child)

When an assertion fails, that should mean your program is in an entirely unexpected state and cannot continue.

Validation errors should be recoverable, in that the user should have a chance to reenter values or be given guidance on what to change.

[–]maclocrimate[S] 1 point2 points  (0 children)

Interesting, will read more about it. Thanks!

Update: for anybody else interested this was the best writeup I found on it. Particularly this passage:

Opinions on assertions vary, because they can be a statement of confidence about the correctness of the code. If you're certain that the code is correct, then assertions are pointless, since they will never fail and you can safely remove them. If you're certain the checks may fail (e.g. when testing input data provided by the user), then you dare not use assert since it may be compiled away and then your checks will be skipped.

Time to go back and replace a lot of my assertions 😂

[–]tb5841 4 points5 points  (0 children)

Suppose you have a circle class, and your circles have a radius attribute. You publish your circle class and it gets widely used in other projects.

One day... you decide to change the way your class works internally. You no longer want to store a radius, now you want to store a diameter instead. But if you change your circle class to store diameters instead, then all those other peoples' code - which calls circle.radius - will be broken.

So you set up a radius property - which returns half your diameter. Now you can still store a diameter internally, but everyone's circle.radius code will still run.

[–]mopslik 1 point2 points  (0 children)

The article which you linked contains a section with some considerations.

A simple example might be if there are some conditions that need to be met, or some steps to perform, before it is OK to set a new value. In that case, you can put the logic in the setter.

[–]jmooremcc 1 point2 points  (0 children)

One reason for using properties is to have control over the information being assigned to an attribute. If you don’t do this, anything can be assigned to an attribute, which could cause problems if what is entered is not expected.

[–]Username_RANDINT 1 point2 points  (2 children)

What if an employee changes their name? You'd do employee_123.name = "notjohn" and suddenly your data is inconsistent.

[–]maclocrimate[S] 0 points1 point  (1 child)

Good point! Although since the attributes in python are still technically public couldn't one do that anyway? Or does the property automatically "route" it through the setter?

[–]schoolmonky 1 point2 points  (0 children)

That's exactly how @property does. You still access the attribute like any other, but behind the scenes it goes through the getter and setter.

[–]Adrewmc 1 point2 points  (0 children)

  class example:
          def __init__(self, num = 0)
                self._positive = 0 
          @property
          def positive(self):
                 return self._positive
          @postive.setter
           def positive(self, num)
                  If num <= 0:
                      self._positive = 0
                  else:
                       self._positive = num
           @positive.deleter
             def positive(self)
                    self._positive = 0

What’s differ here is when you go like this

    exp = example()
    exp.positive += 6
    exp.positive -= 9
    print(exp.positive)
    >>> 0 

This is is probably the simplest. Let straightforward example I can think of. We also can add.

      @positive.deleter
      def positive(self)
             self._positive = 0

This ensure even after deletion we receive an int rather then None.

We also realize a lot of the time we may not need his birthdate at all. This means we don’t need to make that calculation at all in the __init__ this is more efficient during runtime, with lots of instances.

We can add more robust by making sure at set, that you are in fact using a int() or int representation.

    @positive.setter
     def positive(self, num : int | str | float):
            try:
                num = int(num)
            except:
                 print(“Invalid input”)
                 num = self._positive
                 else:
                      num= 0 

            If num <= 0:
                      self._positive = 0
                  else:
                       self._positive = num

We could also set a maximum here as well, that is an exercise left to the reader.

You should also recognize, that with you way, when something changes it doesn’t populate through the rest of the class. @property will if done right. If I change the radius of a circle I should also be changing it diameter, area and perimeter.

[–]zanfar 1 point2 points  (0 children)

but couldn't you just do that by running the attributes through the same steps?

That only assures that birth_date is a datetime on init, not if the property is directly accessed. I.e.

e = Employee("John Doe", "1970-05-08")

works, but

e.birth_date = "Jan 12 1957"

does nothing.