It can be, this is how the reference implementation does it, and it’s how dateutil does it. Here’s the implementation of __new__. The database is never consulted except in the case of a cache miss. I clarify that a bit in this PR to the PEP.
In the end, always getting “the latest data” is fraught with edge cases anyway, and the fact that datetime semantics rely on object identity rather than object equality just adds to the edge cases that are possible.
I will note that there is some precedent in this very area: local time information is only updated in response to a call to time.tzset(), and even that doesn’t work on Windows. The equivalent to calling time.tzset() to get updated time zone information would be calling ZoneInfo.clear_cache() to force ZoneInfo to use the updated data (or to always bypass the main constructor and use the .nocache() constructor).
This is partially how dateutil does it, though the main reason dateutil does it is because tz.gettz() takes any kind of string and returns a time zone from it, so tz.gettz("GMT0BST") will return a tz.tzstr, tz.gettz("Europe/London") will return a tzfile, and tz.gettz() will return local time.
I’d be more open to it if we felt that there was some possibility that we wanted the primary interface to be something that might return any number of types, but I am not convinced of the utility of this function. People mostly know what kind of time zone they want to construct and are happy to select the right type, and in fact it leads to problems when they directly use the tz.tzfile constructor (which uses gettz() for caching).
What I like about ZoneInfo using the cache directly and having the functions bypassing the cache be the more obscure alternate constructors is that most of the time users would want this operation cached - it is much faster, it will make comparison operations cheaper and more consistent and you won’t run into obscure bugs like the one detailed in this blog post.