This came to mind as a result of this discussion but since it would require changes to 2 different modules to have any impact[1], I feel like this deserves its own discussion.
Two other areas where I have missed being able to unpack datetime/timedelta objects are:
creating modified versions of datetime objects. For example, datetime(**my_datetime, tzinfo=None) would have felt like a natural way to strip the timezone info, in a way which my_datetime.replace(tzinfo=None) doesnât.
Putting datetimes into dataframes or databases, rarely I do want to have hour/minute/second columns. Being able to include data=f(..., cols={..., **dt, ...}) in one go would be both intuitive and convenient. The current technique is to use data=f(..., cols={..., "dt": dt, ...}) and then include some post-processing steps to convert the âdtâ column into its component columns, which is slightly bothersome boilerplate. Or to spell out explicitly data=f(..., cols={..., "year":dt.year, "month":dt.month, "day":dt.day, "hour":dt.hour, "minutes":dt.minutes, "seconds": dt.seconds, ...}) which is more work (both to type and to read) and leaves more room for errors.
I could also conceive of people wanting to use such unpacking for json.
datetime and timedelta already have machinery to calculate this dict, because thatâs what they display as their repr. eg:
From experimentation (in Python 3.11-3.15), I conclude ** requires methods from the Mapping protocol.
Below prints {'a': 1, 'b': 2, 'c': 2} and type checks okay:
pluskeys (which would be a mix in, ifExperiment would have had Mapping as base class).
Equipping timedelta (and datetime and presumably date and time) with these methods as needed to enable dict(**my_timedelta) seems excessive and unwanted to me. To illustrate unwanted side effects: It would also allow e.g. len(my_timedelta) (always == 3), which someone might (understandably but incorrectly) misinterpret for the length (duration) of my_timedelta.
If timedelta, datetime, date, time would have been dataclass-like, that would facilitate dict(**asdict(my_timedelta)).
Retrofitting as dataclass isnât practical. An asdict method (like namedtuple has) may be more appropriate, and serve your objectives.
Alternatively, timedelta, datetime, date, time could gain support for match, i.e. have a __match_args__ type member, enumerating the fields. That would (indirectly) serve your purpose, e.g. though a hypothetical user-defined match_asdict function.
I agree that making these types mappings requires too much. I also donât think they conceptually are mappings, so giving them those methods would be odd.
This strikes me as both the most obvious and the best solution. timedelta.asdict() is clear in its meaning and doesnât cost much to support.
I donât personally use these types in this way, so Iâll ask: should all four of these get this treatment, or only timedeltas and datetimes?
Iâm inclined to think that while this seems reasonable, the argument is very generic, and could be made for any type that is defined by its fields and isnât already a dataclass. For example, os.stat_result or os.DirEntry. Where do we draw the line?
The arguments given in the OP arenât that compelling, either:
Honestly, .replace() seems far more natural to me.
Most of the time, Iâd use a date column in the database. Why are you splitting the value up into fields in the first place?
Generally, Iâd use an ISO format string when serialising dates to JSON.
Iâm not saying there isnât some value in a function to convert a datetime or a timedelta to a dict, just that itâs a rare enough need that writing your own helper seems like it should be sufficient (and the need to write a helper might just prompt you to rethink your design and come up with something cleanerâŚ)
Yeah, a reasonable concern. Iâd simply say that itâs case by case, based on demonstrated utility. If thereâs a compelling use case for it, someone can make the argument.
But if thereâs going to be a way to turn a timedelta or stat_result into a dict, I think the natural definition is an asdict method, like named tuples.
Also, my experience aligns with yours. I generally pass around datetime data using the native types for whatever system or as ISO formatted strings. So I second the question of âwhy are you splitting them up?â
I donât think itâs a good idea to broaden APIs in this way. If anything, we want to go the other way. We prefer dataclass to namedtuple in order to prevent accidental iteration.
Rather than making timedelta a mapping, I suggest adding an actual method like timedelta.asdict, or maybe generalize this the same way as copy.replace so that you have copy.asdict(âŚ) that works on a variety of types including dataclasses and timedeltas.
Right. I could see some unwanted side-effects of adding __iter__ too. Hadnât really thought about that, but it seems fair we donât want that.
Add a copy.asdict() method?
Then youâve maybe got them all covered.
As I wrote in the OP, itâs rare.
Once there was a data source that supplied csv files with date-hour-minute columns. I needed to look up information from those csvâs, and converting the date-hour-minutes into timestamps required yet more machinery to deal with the daylight savings ambiguity. Was easier to convert the datetimes into date-hour-minutes instead.
There was another situation where the ânameâ of a row was defined to be âXXAA_YYBBâ where âXXâ is the hour of one time associated with the row, with âAAâ being the minutes and âYYBBâ the hour and minutes of another time associated with the row.
Thereâs quite frequently need to âgroup byâ the hour of the day for statistics purposes. But that doesnât usually justify splitting any further than âdateâ and âhourâ.
Weâre still on Python 3.12.
But nice point, migration to 3.13 is soon, and then I can use copy.replace.[1]
And all changes weâre discussing would happen on python versions where copy.replace is available regardless. âŠď¸
How would such a function work? Would it need an __asdict__ dunder? If not, how would it know which attributes need to go in the dict representation? Sounds like way too much overhead for a use case this infrequent.
Your examples sound like precisely the sort of data wrangling that Iâd expect to write custom functions for. Itâs messy, but data wranglingâs like that. And every case is slightly different, so the gains from putting something in the stdlib are probably minimal.
So a new feature thatâs only going to be in 3.15+ wouldnât help you at all. Do you have any examples of use cases that would be helped by a 3.15+ feature? Generally that means library code that supports new Python versions as they come out, so you could look on places like github for examples of code that might benefit.
Presumably, it could (ab?)use __match_args__.
That would need adding to timedelta c.s., but doing so seems reasonable to me.
Having copy.asdict employ __match_args__could be defended because it âreversesâ creation by ** (except positional-only parameters), i.e. usually type(x)(**copy.asdict(x)) == x. However, doubt Iâd use it (often).
timedelta does not have __match_args__ for a reason: the keyword arguments for the constructor are not available as attributes with the same meaning.
That is, timedelta(hours=3, seconds=0) results in an object that does not have an hours attribute and whose seconds attribute is not 0. Changing the meaning of the seconds attribute would be a backward incompatible change that likely breaks code.