# person_step05.py
#
# ICS 33 Spring 2026
# Code Example
#
# This is our first step toward making name and birthdate into attributes,
# while making them immutable.  They're not immutable yet; we're starting
# by taking control of how the attributes work.


class Person:

    # We'll say that there are two "fields", analogous to the fields of a
    # namedtuple.  This is a class attribute (i.e., something that will be
    # the same for all Persons, rather than different for each one).
    _fields = ('name', 'birthdate')


    def __init__(self, name, birthdate):
        # We'll store the values of the fields in one tuple, rather than in
        # separate attributes.
        self._values = (name, birthdate)


    def age(self, as_of_date):
        if self.birthdate > as_of_date:
            raise ValueError(f'Person was not born yet on {as_of_date}')

        years_old = as_of_date.year - self.birthdate.year

        if (self.birthdate.month, self.birthdate.day) >= (as_of_date.month, as_of_date.day):
            years_old -= 1

        return years_old


    # Now that we have a tuple with both name and birthdate in it, __eq__ and
    # __hash__ each got a little simpler.


    def __eq__(self, other):
        return isinstance(other, Person) and self._values == other._values


    def __hash__(self):
        return hash(self._values)


    def __getattr__(self, name):
        # This is where the magic happens.  When we go looking for an attribute,
        # we use self._fields to figure out where we'll find it in the
        # self._values tuple, then return its value from there.  If the name of
        # the attribute didn't match one of our field names, we raise an
        # exception instead.
        try:
            return self._values[self._fields.index(name)]
        except ValueError:
            raise AttributeError(f"Person object has no attribute '{name}'")
