I like your example. But don’t you think that this is even cleaner:
def parse_time(...) -> timedelta: ...
x = parse_time(minutes_past_midnight)
time.sleep(x)
Now you don’t need to know that sleep needs seconds. The guarantees are not only visible to you from the static types, but they’re also checked by the type checker.
Maybe you’re making a more subtle point than I understand, but I don’t see a difference between what you have written and my example? Other than the fact that you show a type signature for a function which I omitted. That omission was intentional, since it may be very distant from the call site.
It seems to me that if time.sleep accepts more types, then time.sleep(x), for unknown x, becomes more ambiguous.
On its own, time.sleep(foo.total_seconds()) suggests strongly that foo is a timedelta. That hint is especially useful when context clues could produce a misleading impression that foo is a numeric type with some other units (minutes, milliseconds, decades, jiffies, etc).
My opinion, at least for the moment, is unchanged.
I agree with what you’re saying. What I’m saying is that since you are the author of parse_time, and you know that parse_time returns a timedelta, then you do know that you are passing a timedelta to sleep, and you do know that sleep is doing the right thing (or else it would fail statically).
On the other hand, while I agree that in your preferred example, you also know that foo is a timedelta, and you know that you’re getting seconds from it, you still have to check that sleep accepts seconds.
So, essentially passing around timedelta objects allows you to forget about an arbitrary choice in sleep’s interface. It’s about being able to “turn your brain off” and focus on the details that actually matter.
Let’s not assume that the reader is also the author. Maybe parse_time was written 15 years ago and I inherited it last week.
We’re frequently working with only partial knowledge of the code which we are reading. We might not know the type of a variable – at least, not with certainty – and that seeing how it is used can help us understand what it is or is supposed to be.
sleep implicitly converts to int (by __index__ method)
sleep needs actual float (or subclass) for sub-second precision
I couldn’t find that in the documentation.
Perhaps typeshed should type sleep as def sleep(seconds: float | SupportsIndex, /) -> None: ... instead of def sleep(seconds: float, /) -> None: ...?
[edit 2: typeshed issue 15313]
Edit: for above, Python 3.14 resembles 3.13. But in Python 3.15 (current a5), sleep does support float:
Python 3.15.0a5 (tags/v3.15.0a5:d51cc01, Jan 14 2026, 15:09:24) [MSC v.1944 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> class Z:
... def __float__(self): return 1.234
...
>>> import time
>>> time.sleep(Z())
>>> exit
If we would add timedelta to the allowed parameter types, the above Z and Y would still be confusing. That ambiguity can only be removed by explicit conversions:
>>> start=time.time(); time.sleep(float(Z())); end=time.time(); end-start # __float__
1.2346432209014893
>>> start=time.time(); time.sleep(int(float(Y()))); end=time.time(); end-start # int of __float__
2.000532627105713
So while agreeing use of timedelta (either by totalseconds or by direct support in sleep) removes uncertainty, other cases remain uncertain.
There’s been a lot of discussion about timedelta being a numeric type in another thread recently.
It is a numeric type, it just also has a dimension (seconds).
Converting to float via __float__ would be fine in cases that deal in values of that dimension.(such as sleep). The only real problem here is the lack of protocol to enforce only converting when dealing in implicit seconds.
I still think implementing __float__ on timedelta is the best option here. Anyone wanting dimensional analysis in general from the type system would need to propose that separately. There are many existing cases in the type system where that’s already not done, and shouldn’t prevent using the obvious solution here.
I don’t want to encourage you, but there is a use case for __duration__() which cannot be answered by __float__() and __seconds__(). There are many functions which take the timeout argument. But some of them require time in seconds, others in milliseconds or microseconds. Maybe even in nanoseconds or 1/100th seconds or 1/100 millions seconds. Because an underlying OS function uses such time units. Representing them as float can cause errors, because 0.001 > 1/1000, but 0.009 < 9/1000.
But what should hypothetical __duration__()return? It cannot be float. Integer nanoseconds? But what if we need to specify subnanosecond precision? If the custom timedelta-like class supports picosend precision, how should it round it to nanoseconds? We can get double rounding error. The safe bet is to return Decimal or Fraction with arbitrary precision, but this is heavy machinery.
And if we talk about relative duration, we should not forget about absolute timestamps. The problem is worse, because large absolute value close to 2**31 is a norm, and nanosecond precision adds other 30 bits.
Presumably, any API that required such precision wouldn’t use __float__ to begin with, and therefore implementing __float__ for timedelta to solve the current case shouldn’t be an issue, especially if this is documented for timedelta.
Or in the alternative, presumably users wouldn’t pass timedeltas to such functions.
I’m still unclear what’s so bad about sleep(interval.total_seconds()).
Implementing __float__ on timedelta means that there’s an obvious interpretation of float(interval). And I simply don’t think that’s true. We’re all very used to the idea that intervals are in seconds, but that’s just a convention. In the database world, Oracle’s intervals are expresed as fractional days, for instance. And for high-precision use, expressing intervals in nanoseconds (or milliseconds, on older systems) is common.
Having sleep accept datetime values is basically just a minor convenience, and IMO not worth the cost of mixing abstraction levels (the datetime module is explicitly a higher level abstraction than the time module, which expresses the operating system’s concept of time).
I personally don’t have an issue with using total_seconds explicitly. In my own code, even if this were implemented, I’d still prefer doing this than relying on __float__ as a matter of code convention with converting between value representations that have and don’t have a dimension.
That said, I don’t see an issue with a value that has a dimension being able to be converted to a scalar value absent the dimension implicitly in APIs that handle scalar values. It’s up to users to only pass this where it would make sense to do so. For timedelta, that would be places that expect seconds.
If people want rigorous static dimensional analysis, there are languages that do it better and ideas that could be borrowed from them, but I see that as a seperate concern.
The same happens for any number of other wrong argument types already. Not everything that implements __float__ is valid as seconds. If this is a serious argument, the change to sleep to accept SupportsFloat should be reverted.