Skip to content

perf: hoist import datetime as dt to module scope in time.py - #394

Open
g4lb wants to merge 1 commit into
python-humanize:mainfrom
g4lb:perf-hoist-datetime-import
Open

perf: hoist import datetime as dt to module scope in time.py#394
g4lb wants to merge 1 commit into
python-humanize:mainfrom
g4lb:perf-hoist-datetime-import

Conversation

@g4lb

@g4lb g4lb commented Sep 7, 2026

Copy link
Copy Markdown

What

The companion to #393, for time.py. #335 added __lazy_modules__ so module-scope
imports become lazy on Python 3.15+, but it added the declarations only — the
function-level import datetime as dt statements that predated it were left behind.
This hoists the seven of them to module scope, adds "datetime" to
__lazy_modules__, and drops the now-redundant TYPE_CHECKING import. Same pattern
filesize.py already uses for math.

The two doctest examples that contain >>> import datetime as dt are untouched.

Why

A function-level import costs a sys.modules lookup and a name binding on every
call. Measured on this repository's own tests/test_benchmarks.py with
pytest-benchmark, 10 interleaved A/B rounds per side against main
(Apple Silicon, CPython 3.14.6, medians):

benchmark before after change p
test_naturaltime 1979.5 ns 1708.0 ns −13.72 % 0.0002
test_naturaldate 2292.0 ns 2104.5 ns −8.18 % 0.0011
test_naturalday 796.0 ns 750.0 ns −5.78 % 0.0126
test_naturaldelta 750.0 ns 708.0 ns −5.60 % 0.0057

Geometric mean across all 15 benchmarks: 0.9764 (−2.36 %). No benchmark regressed
significantly; the remaining eleven are noise (all p > 0.35).

The aggregate is smaller than #393's because only the time functions are affected —
the other benchmarks dilute it. If you would rather see this measured on the time
benchmarks alone, say so and I will post that.

Import time

import humanize is unchanged: datetime is already resident by the time the package
finishes importing, so moving the statement costs nothing at startup.

Checks

pytest tests/ → 724 passed, 74 skipped. ruff check, black --check and mypy all
clean on the changed file. (pytest --doctest-modules src/humanize/time.py has one
pre-existing failure in naturaldelta, identical on main — it needs dateutil,
which I had not installed. Not related to this change.)

Relationship to #393

Independent — different files, no conflict, either can land first. #393 covers
number.py and math.

How this was found

By pointing autor3search-python at
humanize: a harness that proposes a change, measures it against a pinned baseline
with interleaved A/B rounds, and keeps or discards it on the numbers. Every figure
above is a measurement, not an estimate.

python-humanize#335 declared `__lazy_modules__` so module-scope imports become lazy on Python
3.15+, but only added the declarations — the function-level `import datetime as dt`
statements predating it were left in place, costing a `sys.modules` lookup on every
call.

Move the import to module scope and add "datetime" to `__lazy_modules__`, the same
pattern filesize.py already uses for `math`. The now-redundant `TYPE_CHECKING` import
goes with it.
@hugovk hugovk added the changelog: Changed For changes in existing functionality label Sep 8, 2026
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.56%. Comparing base (3201e70) to head (bb74731).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #394      +/-   ##
==========================================
- Coverage   99.56%   99.56%   -0.01%     
==========================================
  Files          12       12              
  Lines         927      921       -6     
==========================================
- Hits          923      917       -6     
  Misses          4        4              
Flag Coverage Δ
macos-latest 97.61% <100.00%> (-0.02%) ⬇️
ubuntu-latest 97.61% <100.00%> (-0.02%) ⬇️
windows-latest 95.43% <100.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@hugovk

hugovk commented Sep 8, 2026

Copy link
Copy Markdown
Member

Import time

import humanize is unchanged: datetime is already resident by the time the package finishes importing, so moving the statement costs nothing at startup.

datetime is not imported elsewhere by humanize, so this makes import times worse on 3.14 and older:

Python main PR change
3.11.9 3409 µs 4254 µs +845 µs (+25 %)
3.14.7 4081 µs 4279 µs +198 µs (+5 %)
3.15.0rc2 213 µs 211 µs none (lazy)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: Changed For changes in existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants