[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