0

I am defining a Python Holiday class and three subclasses: one for fixed date holidays, a second for relative holidays, and a third for floating Monday holidays. I would like to create a set of constants in the superclass Holiday so that applications can simply refer to particular holidays as

Holiday.NEW_YEARS
Holiday.CHRISTMAS
etc.

but the subclasses obviously do not exist when the parent class is instantiated. How can I do this?

class Holiday(object):
    NEW_YEARS = FixedHoliday(1, 1)
    MLK_BIRTHDAY = FloatingMonday(1, 15)
    ...
martineau
  • 119,623
  • 25
  • 170
  • 301
philhanna
  • 73
  • 1
  • 6
  • Possible duplicate of https://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods – quant Nov 10 '18 at 22:05
  • You probably need another parent class that `Holiday` can inherent the constants from. – Red Cricket Nov 10 '18 at 22:06
  • 2
    This isn't good OO. A superclass shouldn't know about or interact with its descendants. If you want to do something similar, create a registry or factory, that doesn't derive from `Holiday`, but knows how to make one. – Jim Stewart Nov 10 '18 at 22:06
  • 1
    I don't think it's a dupe of that because I'm not sure the requirement makes sense – roganjosh Nov 10 '18 at 22:07
  • 1
    @Jim Stewart: While generally I agree, in this case the OP is effectively just using the base class as a namespace (i.e. a container)—so I think what they want to do would be an exception to that rule-of-thumb, or at least falls into a grey area... – martineau Nov 10 '18 at 23:46

3 Answers3

1

One way to do it would be to decorate the base class after it and all the subclasses are defined. You can do this in Python because classes are mutable objects.

Here's what I'm suggesting:

class Holiday(object):
    def __init__(self, month, day):
        self.month, self.day = month, day

    def __repr__(self):
        return '{}(month={}, day={})'.format(type(self).__name__, self.month, self.day)

class FixedHoliday(Holiday):
    pass

class FloatingMonday(Holiday):
    pass


def inject_constants(cls):
    """ Add attributes to class. """
    HOLIDATA = {
        'NEW_YEARS': FixedHoliday(1, 1),
        'MLK_BIRTHDAY': FloatingMonday(1, 15)
    }

    for key, value in HOLIDATA.items():
        setattr(cls, key, value)

    return cls

Holiday = inject_constants(Holiday)


if __name__ == '__main__':
    print(Holiday.NEW_YEARS)    # -> FixedHoliday(month=1, day=1)
    print(Holiday.MLK_BIRTHDAY) # -> FloatingMonday(month=1, day=15)
martineau
  • 119,623
  • 25
  • 170
  • 301
0

A class can't and shouldn't refer to its derived classes. Try this instead:

class BaseHoliday(object):
    pass

class FixedHoliday(BaseHoliday):
    # class code

# more classes

class Holidays(object):
    NEW_YEARS = FixedHoliday(1, 1)
    MLK_BIRTHDAY = FloatingMonday(1, 15)
roeen30
  • 759
  • 3
  • 13
  • I don't understand this code at all. Why are you using global naming conventions in a base class? – roganjosh Nov 10 '18 at 22:11
  • What do you mean by "global naming conventions"? – roeen30 Nov 10 '18 at 22:14
  • 2
    Capitalisation implies global variables by PEP8 – roganjosh Nov 10 '18 at 22:15
  • 1
    Source for this? Class names are always camelcase and begin with a capital in PEP8, see [here](https://www.python.org/dev/peps/pep-0008/#class-names). This is not a PEP8 discussion, however. – roeen30 Nov 10 '18 at 22:19
  • So you were talking about the *class attributes*. You didn't mention that. I just copied that from OP. Anyway, again, this discussion is completely unconstructive and off topic. – roeen30 Nov 10 '18 at 22:52
  • How is anything PEP8 at all relevant to this question? – ggdx Nov 10 '18 at 23:37
  • @ggdx: It's relevant because PEP 8 spells out the preferred way to *all* Python code should be written. Its [Naming Conventions](https://www.python.org/dev/peps/pep-0008/#naming-conventions) in particular convey information about the thing being named if you know the guidelines and the code's author has followed them. – martineau Feb 19 '22 at 10:55
  • @roeen30: The attributes aren't class names, they're the names of *constant* class *instances*, which is why they've been given ALL_CAPS names. – martineau Feb 19 '22 at 11:07
0

I guess this sort of workaround will be fine

class _holiday:
    payload: str


class Holiday:
    class NEW_YEARS(_holiday):
        ...

    class CHRISTMAS(_holiday):
        ...