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

Paul Ganssle paul at ganssle.io
Mon Feb 4 08:48:05 EST 2019


Hey all,

This thread about the return type of datetime operations seems to have
stopped without any explicit decision - I think I responded to everyone
who had objections, but I think only Guido has given a +1 to whether or
not we should go ahead.

Have we got agreement to go ahead with this change? Are we still
targeting Python 3.8 here?

For those who don't want to dig through your old e-mails, here's the
archive link for this thread:
https://mail.python.org/pipermail/python-dev/2019-January/155984.html

If you want to start commenting on the actual implementation, it's
available here (though it's pretty simple):
https://github.com/python/cpython/pull/10902

Best,

Paul


On 1/6/19 7:17 PM, Guido van Rossum wrote:
> OK, I concede your point (and indeed I only tested this on 3.6). If we
> could break the backward compatibility for now() we presumably can
> break it for this purpose.
>
> On Sun, Jan 6, 2019 at 11:02 AM Paul Ganssle <paul at ganssle.io
> <mailto: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). 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 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
>>     <mailto: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 <mailto: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 <mailto: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 <http://python.org/~guido>)
>     _______________________________________________
>     Python-Dev mailing list
>     Python-Dev at python.org <mailto: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 <http://python.org/~guido>)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20190204/221d897c/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: OpenPGP digital signature
URL: <http://mail.python.org/pipermail/python-dev/attachments/20190204/221d897c/attachment-0001.sig>


More information about the Python-Dev mailing list