I've read quite a bit about Python properties and I understand their benefits, as well as them being generally more Pythonic than getters/setters. However, I haven't found any mention of their usage being more prone to spelling mistakes leading to unexpected behavior for users, and I'd like to understand how this potential issue can be mitigated.
Consider the following example:
import datetime
import dateutil
class Person:
def __init__(self, date_of_birth):
self.date_of_birth = date_of_birth
@property
def date_of_birth(self):
return self._date_of_birth
@date_of_birth.setter
def date_of_birth(self, value):
# <Some checks about value>
self._date_of_birth = datetime.datetime.strptime(value, '%d-%m-%Y').date()
def compute_age(self):
# Some computations involving today's date and self._date_of_birth
return dateutil.relativedelta.relativedelta(datetime.date.today(), self.date_of_birth).years
# Compute age of someone
person = Person("15-06-1985")
person.compute_age() # Returns 38
# Update date of birth
person.date_of_birth = "17-08-2001"
person.compute_age() # Returns 22
# Update wrong attribute
person.date_of_brith = "25-11-1999" # No error or warning raised
person.compute_age() # Still returns 22
The last block of code is still perfectly valid, but person now has a useless date_of_brith attribute which is not used by person.compute_age(), while the intent of the developer was to update the actual date of birth of person. Since it generates no warning or error, this type of mistake could go undetected and yield unexpected results that are hard to debug in more complex applications. Using a setter method person.set_date_of_birth() instead of person.date_of_birth property would prevent such typos, since a typo in the method name would yield an exception upon execution. My question is therefore, doesn't that advocate for the usage of setter methods rather than properties? Otherwise, is there a clean way to mitigate this kind of spelling mistake when using properties?
It's a fundamental property of Python that arbitrary attributes can be added to an instance. (There are exceptions, but we need not concern ourselves with them here.)
To prevent such accidental creations, we can override
__setattr__to define whatperson.XXX = ...means. In this method, you can check that the attribute being assigned to is valid.Note that we do not need to add
date_of_birthto the white list, because no instance attribute nameddate_of_birthexists. When attempting to assign toperson.date_of_birth, the default behavior is to callPerson.date_of_birth.__set__if it exists instead of creating the instance attribute.That said, I would consider whether it is worth saddling your code with such a runtime check instead of making the user responsible for testing their code appropriately.