4
\$\begingroup\$

I am making a subclass of a well-established python class: pendulum.Period, which overrides both __new__ and __init__ (it does this because it is subclassing the python builtin class timedelta, which uses __new__).

In my case, I want my class to handle a wider range of arguments than the superclass. The only way I have managed to get the code to work is to duplicate my handling code in both __new__ and __init__ -- this is because, per the documentation, the arguments passed to __new__ are then passed to the __init__ of the new instance, unaltered.

The result is repeated code execution. Is this avoidable?

from pendulum import Period, Date

def _toDate(val):
    """
    Convert various inputs into a Date
    """
    if isinstance(val, int) or isinstance(val, float):
        return Date.fromtimestamp(val)
    elif isinstance(val, tuple):
        return Date(*val)
    return Date(val.year, val.month, val.day)


class MyInterval(Period):    
    def __new__(cls, start, end, inclusive=True, **kwargs):
        start = _toDate(start)
        end = _toDate(end)

        if inclusive:
            end = end.add(days=1)

        return super(MyInterval, cls).__new__(cls, start, end, **kwargs)

    def __init__(self, start, end, inclusive=True, **kwargs):
        """
        Creates a pendulum Period where the interval is limited to whole 
        days. Both are cast to dates, then the end date is incremented by 
        one day if inclusive=True
        :param start: cast to date
        :param end: cast to date
        :param inclusive: [True] whether the last day of the interval should
          be included (increments by 1 day)
        """
        start = _toDate(start)
        end = _toDate(end)

        if inclusive:
            end = end.add(days=1)

        super(MyInterval, self).__init__(start, end, **kwargs)
\$\endgroup\$
  • 2
    \$\begingroup\$ Why do you need __init__ if you have __new__? \$\endgroup\$ – Stephen Rauch Jun 22 at 1:55
  • \$\begingroup\$ @StephenRauch it's just because the superclass specifies both __init__ and __new__. I depend on the superclass's code working in both steps, so I need to give it valid inputs to both those functions. \$\endgroup\$ – Brandon Kuczenski Jun 24 at 8:29
3
\$\begingroup\$

If you don’t expect to be further subclassing MyInterval, you could just use a helper method for construction.

class MyInterval(Period):

    @classmethod
    def create(cls, start, end, inclusive=True, **kwargs):
        start = _toDate(start)
        end = _toDate(end)

        if inclusive:
            end = end.add(days=1)

        return cls(start, end, **kwargs)

Alternatively, you could use a helper method to avoid the duplicated code (but not duplicated execution).

class MyInterval(Period):

    @staticmethod
    def _fixup(start, end, inclusive):
        start = _toDate(start)
        end = _toDate(end)

        if inclusive:
            end = end.add(days=1)

        return start, end

    def __new__(cls, start, end, inclusive=True, **kwargs):
        start, end = cls._fixup(start, end, inclusive)
        return super().__new__(start, end, **kwargs)

    def __init__(self, start, end, inclusive=True, **kwargs):
        start, end = self._fixup(start, end, inclusive)
        return super().__init__(start, end, **kwargs)
\$\endgroup\$
  • \$\begingroup\$ I like the classmethod approach. The downside is obviously that it doesn't stop anyone from using the ordinary constructor- which may lead to unexpected results if the body code depends on the proper input processing at construction (though it doesn't really in this case) \$\endgroup\$ – Brandon Kuczenski Jun 24 at 8:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.