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

Paul Ganssle paul at ganssle.io
Mon Feb 4 11:39:24 EST 2019


There's already a PR, actually, #10902:
https://github.com/python/cpython/pull/10902

Victor reviewed and approved it, I think before I started this thread,
so now it's just waiting on merge.

On 2/4/19 11:38 AM, Guido van Rossum wrote:
> I recommend that you submit a PR so we can get it into 3.8 alpha 2.
>
> On Mon, Feb 4, 2019 at 5:50 AM Paul Ganssle <paul at ganssle.io
> <mailto:paul at ganssle.io>> wrote:
>
>     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>)
>     _______________________________________________
>     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/2ad41e88/attachment.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/2ad41e88/attachment.sig>


More information about the Python-Dev mailing list