[Python-Dev] Return type of datetime subclasses added to timedelta

Brett Cannon brett at python.org
Sun Jan 6 15:24:54 EST 2019

On Sun, 6 Jan 2019 at 11:00, Paul Ganssle <paul at ganssle.io> wrote:

> I did address this in the original post - the assumption that the subclass
> constructor will have the same arguments as the base constructor is baked
> into many alternate constructors of datetime. I acknowledge that this is a
> breaking change, but it is a small one - anyone creating such a subclass
> that *cannot* handled the class being created this way would be broken in
> myriad ways.
> We have also in recent years changed several alternate constructors
> (including `replace`) to retain the original subclass, which by your same
> standard would be a breaking change. I believe there have been no
> complaints. In fact, between Python 3.6 and 3.7, the very example you
> showed broke:
> Python 3.6.6:
> >>> class D(datetime.datetime):
> ...     def __new__(cls):
> ...         return cls.now()
> ...
> >>> D()
> D(2019, 1, 6, 13, 49, 38, 842033)
> Python 3.7.2:
> >>> class D(datetime.datetime):
> ...     def __new__(cls):
> ...         return cls.now()
> ...
> >>> D()
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "<stdin>", line 3, in __new__
> TypeError: __new__() takes 1 positional argument but 9 were given
> We haven't seen any bug reports about this sort of thing; what we *have*
> been getting is bug reports that subclassing datetime doesn't retain the
> subclass in various ways (because people *are* using datetime subclasses).

To help set expectations, the current semantics are not a bug and so the
proposal isn't fixing a bug but proposing a change in semantics.

> This is likely to cause very little in the way of problems, but it will
> improve convenience for people making datetime subclasses and almost
> certainly performance for people using them (e.g. pendulum and arrow, which
> now need to take a slow pure python route in many situations to work around
> this problem).
> If we're *really* concerned with this backward compatibility breaking,

We very much do care. Because this isn't a bug but a voluntary semantic
change you're proposing to change we can't blindly break people who are
relying on the current semantics. We need to have a justification for those
people as to why we have decided to change the semantics now after all of
these years as well as provide an upgrade path.


> we could do the equivalent of:
> try:
>     return new_behavior(...)
> except TypeError:
>     warnings.warn("The semantics of timedelta addition have "
>                   "changed in a way that raises an error in "
>                   "this subclass. Please implement __add__ "
>                   "if you need the old behavior.", DeprecationWarning)

> Then after a suitable notice period drop the warning and turn it to a hard
> error.
> Best,
> Paul
> On 1/6/19 1:43 PM, Guido van Rossum wrote:
> I don't think datetime and builtins like int necessarily need to be
> aligned. But I do see a problem -- the __new__ and __init__ methods defined
> in the subclass (if any) should allow for being called with the same
> signature as the base datetime class. Currently you can have a subclass of
> datetime whose __new__ has no arguments (or, more realistically, interprets
> its arguments differently). Instances of such a class can still be added to
> a timedelta. The proposal would cause this to break (since such an addition
> has to create a new instance, which calls __new__ and __init__). Since this
> is a backwards incompatibility, I don't see how it can be done -- and I
> also don't see many use cases, so I think it's not worth pursuing further.
> Note that the same problem already happens with the .fromordinal() class
> method, though it doesn't happen with .fromdatetime() or .now():
> >>> class D(datetime.datetime):
> ...   def __new__(cls): return cls.now()
> ...
> >>> D()
> D(2019, 1, 6, 10, 33, 37, 161606)
> >>> D.fromordinal(100)
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
> TypeError: __new__() takes 1 positional argument but 4 were given
> >>> D.fromtimestamp(123456789)
> D(1973, 11, 29, 13, 33, 9)
> >>>
> On Sun, Jan 6, 2019 at 9:05 AM Paul Ganssle <paul at ganssle.io> wrote:
>> I can think of many reasons why datetime is different from builtins,
>> though to be honest I'm not sure that consistency for its own sake is
>> really a strong argument for keeping a counter-intuitive behavior - and to
>> be honest I'm open to the idea that *all* arithmetic types *should* have
>> some form of this change.
>> That said, I would say that the biggest difference between datetime and
>> builtins (other than the fact that datetime is *not* a builtin, and as
>> such doesn't necessarily need to be categorized in this group), is that
>> unlike almost all other arithmetic types, *datetime* has a special,
>> dedicated type for describing differences in datetimes. Using your example
>> of a float subclass, consider that without the behavior of "addition of
>> floats returns floats", it would be hard to predict what would happen in
>> this situation:
>> >>> F(1.2) + 3.4
>> Would that always return a float, even though F(1.2) + F(3.4) returns an
>> F? Would that return an F because F is the left-hand operand? Would it
>> return a float because float is the right-hand operand? Would you walk the
>> MROs and find the lowest type in common between the operands and return
>> that? It's not entirely clear which subtype predominates. With datetime,
>> you have:
>> datetime - datetime -> timedelta
>> datetime ± timedelta -> datetime
>> timedelta ± timedelta -> timedelta
>> There's no operation between two datetime objects that would return a
>> datetime object, so it's always clear: operations between datetime
>> subclasses return timedelta, operations between a datetime object and a
>> timedelta return the subclass of the datetime that it was added to or
>> subtracted from.
>> Of course, the real way to resolve whether datetime should be different
>> from int/float/string/etc is to look at why this choice was actually made
>> for those types in the first place, and decide whether datetime is like
>> them *in this respect*. The heterogeneous operations problem may be a
>> reasonable justification for leaving the other builtins alone but changing
>> datetime, but if someone knows of other fundamental reasons why the
>> decision to have arithmetic operations always create the base class was
>> chosen, please let me know.
>> Best,
>> Paul
>> On 1/5/19 3:55 AM, Alexander Belopolsky wrote:
>> On Wed, Jan 2, 2019 at 10:18 PM Paul Ganssle <paul at ganssle.io> wrote:
>>> .. the original objection was that this implementation assumes that the
>>> datetime subclass has a constructor with the same (or a sufficiently
>>> similar) signature as datetime.
>> While this was used as a possible rationale for the way standard types
>> behave, the main objection to changing datetime classes is that it will
>> make them behave differently from builtins.  For example:
>> >>> class F(float):
>> ...     pass
>> ...
>> >>> type(F.fromhex('AA'))
>> <class '__main__.F'>
>> >>> type(F(1) + F(2))
>> <class 'float'>
>> This may be a legitimate gripe, but unfortunately that ship has sailed
>>> long ago. All of datetime's alternate constructors make this assumption.
>>> Any subclass that does not meet this requirement must have worked around it
>>> long ago (or they don't care about alternate constructors).
>> This is right, but the same argument is equally applicable to int, float,
>> etc. subclasses.  If you want to limit your change to datetime types you
>> should explain what makes these types special.
>> _______________________________________________
>> Python-Dev mailing list
>> Python-Dev at python.org
>> https://mail.python.org/mailman/listinfo/python-dev
>> Unsubscribe:
>> https://mail.python.org/mailman/options/python-dev/guido%40python.org
> --
> --Guido van Rossum (python.org/~guido)
> _______________________________________________
> Python-Dev mailing list
> Python-Dev at python.org
> https://mail.python.org/mailman/listinfo/python-dev
> Unsubscribe:
> https://mail.python.org/mailman/options/python-dev/brett%40python.org
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20190106/d2e2d630/attachment.html>

More information about the Python-Dev mailing list