# fields.py
#
# ICS 33 Spring 2026
# Code Example
#
# This is a decorator that can be applied to an empty class to automatically
# turn it into something a lot like a namedtuple.  We need to pass an iterable
# of field names to the decorator and then apply it to an empty.  For example,
# if we write this:
#
#     @fields(('name', 'age'))
#     class Person:
#         pass
#
# then we expect Person to have three features automatically:
#
# * An __init__ method that, if passed two positional arguments, will assign
#   the value of the first argument into a _name attribute and the value of
#   the second argument into an _age attribute.
# * A name() method that returns the value of the _name attribute.
# * An age() method that returns the value of the _age attribute.


def fields(field_names):

    # Since field_names might be any kind of iterable, but we'll need to
    # iterate it more than once, we'll store them in a list temporarily.
    field_names = list(field_names)


    # We'll need to build "getter" methods for each of the fields.  Since
    # we can't know ahead of time how many there will be or what their
    # names are, we'll need to write a function that can build one; we'll
    # give it the name of a field and it'll build us the appropriate
    # getter.
    def make_field_getter(field_name):
        def get_field(self):
            # We can't say something like "self._age" here, because we
            # don't know what the attribute's name will be.  So, instead,
            # we use the built-in getattr() function, which, if you give
            # it an object and the name of an attribute, it'll perform
            # the same attribute lookup that "self._age" would have done.
            return getattr(self, f'_{field_name}')

        return get_field


    # Our decorator is parameterized, so we need to build a decorator
    # function each time it's called.  The decorator function is the one
    # that takes a class and modifies it.  This is that function.
    def decorate(cls):

        # Just as getattr() allows us to get an attribute using a dynamically
        # determined name, setattr() allows ut to set an attribute dynamically.
        # Here, we're building getter methods for each field and storing them
        # into the appropriately-named class attributes.
        for field_name in field_names:
            setattr(cls, field_name, make_field_getter(field_name))


        # This is the __init__ method that we'll add to the class.
        def __init__(self, *args):
            for field_name, arg in zip(field_names, args):
                setattr(self, f'_{field_name}', arg)

        # Finally, we'll add the __init__ method to the class and we can then
        # return the modified class.
        cls.__init__ = __init__
        return cls

    return decorate
