2

I'm trying to extend the datetime.date class do allow adding ints instead of working with timedelta objects:

class Date(datetime.date):
    def __add__(self, other):
        if isinstance(other, int):
            other = datetime.timedelta(days=other)
        return super(Date, self).__add__(other)

The problem is that the __add__() method above will return an instance of datetime.date instead of a Date.

From my understanding, there is no way to make super().__add__() aware of the Date type. The most elegant solution is to copy the entire __add__() method from datetime.date and add my extra bit of code. Is there a way to cast a datetime.date object to a Date object in the Date.__add__() method?

Here is a snippet to highlight the issue:

D = Date(2000,1,1)
d = D + 1
type(d) # datetime.date instead of Date

EDIT: My first solution after looking at datetime.py (search for "class date") is to do this:

class Date(datetime.date):
    def __add__(self, other):
        if isinstance(other, int):
            other = datetime.timedelta(days=other)
        d = super(Date, self).__add__(other)
        self.__year = d.year
        self.__month = d.month
        self.__day = d.day
        return self # this is of type datetime.date

OK, I thought I'd point out that my first try was this:

class Date(datetime.date):
    def __add__(self, other):
        if isinstance(other, int):
            other = datetime.timedelta(days=other)
        d = super(Date, self).__add__(other)
        d.__class__ = Date
        return d

which will not work because (as I suspected) the datetime module is in C and according to this post for such types you cannot assign to __class__. I am a bit confused what the code in datetime.py is for then.

Community
  • 1
  • 1
s5s
  • 11,159
  • 21
  • 74
  • 121
  • What about something like `return Date(super(Date, self).__add__(other)`? – bimsapi Jan 11 '15 at 16:18
  • @bimsapi Doesn't work, unfortunatly. Seems like `datetime.date`'s constructor isn't nice like that. – matsjoyce Jan 11 '15 at 16:19
  • Of course `super` methods return a `datetime.date`, **that's the superclass**! You could create a `Date` from it, though; `d = super(Date, self).__add__(other); return Date(d.year, d.month, d.day)`. – jonrsharpe Jan 11 '15 at 16:23

3 Answers3

2

After adding, just create another Date object from datetime.date and return it like this

    def __add__(self, other):
        if isinstance(other, int):
            other = datetime.timedelta(days=other)
        result = super(Date, self).__add__(other)
        return Date(result.year, result.month, result.day)

And here is a test,

D = Date(2000, 1, 1) + 1
print(type(D), D)
# (<class '__main__.Date'>, Date(2000, 1, 2))
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
0

The problem is that the add() method above will return an instance of datetime.date instead of a Date.

It will, but that's why you're using super(), after all: you really want to call the super classes' method.

You could just call your own class's copy constructor!

The most elegant solution is to copy the entire add() method from datetime.date and add my extra bit of code.

That can't really be called an elegant solution, I think. Also, I think the datetime module is C.

Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
  • Yup, it is C. There is a `datetime.py`, but all the methods are blank and return None. – matsjoyce Jan 11 '15 at 16:21
  • OK, I thought it's C which will explain why I cannot just overwrite self.__class__ = Date in Date.__add__() before returning the object. – s5s Jan 11 '15 at 16:22
0

In general, super(Subclass, self).__add__(other) may return Subclass instance:

class Base(object):
    __slots__ = 'n'
    def __new__(cls, n):
        self = object.__new__(cls)
        self.n = n
        return self
    @classmethod
    def fromordinal(cls, n):
        return cls(n)
    def __add__(self, other):
        if hasattr(other, 'n'):
            return self.fromordinal(self.n + other.n)
        return NotImplemented
    def __repr__(self):
        return "{cls}({arg})".format(cls=self.__class__.__name__, arg=self.n)
    def __eq__(self, other):
        return self.n == other.n

class Subclass(Base):
    __slots__ = ['_secret']
    def __add__(self, other):
        if hasattr(other, '_secret'):
            other = self.fromordinal(other._secret)
        return super(Subclass, self).__add__(other)


a = Subclass(1)
b = Subclass(2)
c = Subclass(2)
c._secret = 3
print(a + b)
print(a + c)
assert (a + b) == Subclass(3)
assert (a + c) == Subclass(4)
assert b == c and (a + b) != (a + c) and type(a + b) == type(a + c) == Subclass

But datetime.date.__add__ method is hardcoded to return datetime.date instances even for datetime.date subclasses. In CPython, it is implemented in C that for performance reasons does not call (possibly) pure Python __add__ method from a subclass. Pure Python datetime.py that might be used by other Python implementations such as Pypy, IronPython, Jython does not invoke subclass methods for compatibility with CPython.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • So what does the datetime.py file do? Is it just python wrappers around C code? – s5s Jan 11 '15 at 19:51
  • @s5s: see Python issue7989: [Add pure Python implementation of datetime module to CPython](http://bugs.python.org/issue7989): *The stated long-term goal of the stdlib is to minimize the C extension modules to only those that have to be written in C (modules can still have performance enhancing extension back-ends). Since datetime does not meet that requirement it's not a matter of "if" but "when" datetime will get a pure Python version and use the extension code only for performance.* ... *The key thing to remember is that this is so the VMs other than CPython are not treated as second-class.* – jfs Jan 11 '15 at 20:06