From 4ee7ecd87275412036c69f9b473e472354deb99f Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Thu, 23 Mar 2023 10:36:47 -0400 Subject: [PATCH 1/2] GH-84976: Move Lib/datetime.py to Lib/_pydatetime This breaks the tests, but we are keeping it as a separate commit so that the move operation and editing of the moved files are separate, for a cleaner history. --- Lib/{datetime.py => _pydatetime.py} | 0 Lib/test/datetimetester.py | 6 +++++- Lib/test/test_datetime.py | 12 ++++++++---- Python/stdlib_module_names.h | 1 + 4 files changed, 14 insertions(+), 5 deletions(-) rename Lib/{datetime.py => _pydatetime.py} (100%) diff --git a/Lib/datetime.py b/Lib/_pydatetime.py similarity index 100% rename from Lib/datetime.py rename to Lib/_pydatetime.py diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index c5eb6e7f1643ee..fb07d2a5ad9b06 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -39,6 +39,10 @@ # Needed by test_datetime import _strptime +try: + import _pydatetime +except ImportError: + pass # pickle_loads = {pickle.loads, pickle._loads} @@ -92,7 +96,7 @@ def test_divide_and_round(self): if '_Fast' in self.__class__.__name__: self.skipTest('Only run for Pure Python implementation') - dar = datetime_module._divide_and_round + dar = _pydatetime._divide_and_round self.assertEqual(dar(-10, -3), 3) self.assertEqual(dar(5, -2), -2) diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index 7f9094fa7bd4e6..3859733a4fe65b 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -8,10 +8,12 @@ def load_tests(loader, tests, pattern): try: - pure_tests = import_fresh_module(TESTS, fresh=['datetime', '_strptime'], - blocked=['_datetime']) - fast_tests = import_fresh_module(TESTS, fresh=['datetime', - '_datetime', '_strptime']) + pure_tests = import_fresh_module(TESTS, + fresh=['datetime', '_pydatetime', '_strptime'], + blocked=['_datetime']) + fast_tests = import_fresh_module(TESTS, + fresh=['datetime', '_strptime'], + blocked=['_pydatetime']) finally: # XXX: import_fresh_module() is supposed to leave sys.module cache untouched, # XXX: but it does not, so we have to cleanup ourselves. @@ -42,6 +44,8 @@ def setUpClass(cls_, module=module): cls_._save_sys_modules = sys.modules.copy() sys.modules[TESTS] = module sys.modules['datetime'] = module.datetime_module + if hasattr(module, '_pydatetime'): + sys.modules['_pydatetime'] = module._pydatetime sys.modules['_strptime'] = module._strptime @classmethod def tearDownClass(cls_): diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h index 27f42e5202e571..ed4a0ac2dd32de 100644 --- a/Python/stdlib_module_names.h +++ b/Python/stdlib_module_names.h @@ -56,6 +56,7 @@ static const char* _Py_stdlib_module_names[] = { "_posixshmem", "_posixsubprocess", "_py_abc", +"_pydatetime", "_pydecimal", "_pyio", "_pylong", From 012ffde9403b72eea6e65760afdb41e4bcd61a09 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Thu, 23 Mar 2023 11:36:22 -0400 Subject: [PATCH 2/2] GH-84976: Re-introduce `datetime.py` and fix reprs Without the change to the reprs, pure-python classes would have a repr of `datetime._pydatetime.time`, etc. --- Lib/_pydatetime.py | 42 ++++++------------- Lib/datetime.py | 9 ++++ ...3-04-19-16-08-53.gh-issue-84976.HwbzlD.rst | 5 +++ 3 files changed, 27 insertions(+), 29 deletions(-) create mode 100644 Lib/datetime.py create mode 100644 Misc/NEWS.d/next/Library/2023-04-19-16-08-53.gh-issue-84976.HwbzlD.rst diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py index b0eb1c216a689d..f4fc2c58e5e293 100644 --- a/Lib/_pydatetime.py +++ b/Lib/_pydatetime.py @@ -16,6 +16,13 @@ def _cmp(x, y): return 0 if x == y else 1 if x > y else -1 +def _get_class_module(self): + module_name = self.__class__.__module__ + if module_name == '_pydatetime': + return 'datetime' + else: + return module_name + MINYEAR = 1 MAXYEAR = 9999 _MAXORDINAL = 3652059 # date.max.toordinal() @@ -706,7 +713,7 @@ def __repr__(self): args.append("microseconds=%d" % self._microseconds) if not args: args.append('0') - return "%s.%s(%s)" % (self.__class__.__module__, + return "%s.%s(%s)" % (_get_class_module(self), self.__class__.__qualname__, ', '.join(args)) @@ -1016,7 +1023,7 @@ def __repr__(self): >>> repr(dt) 'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)' """ - return "%s.%s(%d, %d, %d)" % (self.__class__.__module__, + return "%s.%s(%d, %d, %d)" % (_get_class_module(self), self.__class__.__qualname__, self._year, self._month, @@ -1510,7 +1517,7 @@ def __repr__(self): s = ", %d" % self._second else: s = "" - s= "%s.%s(%d, %d%s)" % (self.__class__.__module__, + s= "%s.%s(%d, %d%s)" % (_get_class_module(self), self.__class__.__qualname__, self._hour, self._minute, s) if self._tzinfo is not None: @@ -2065,7 +2072,7 @@ def __repr__(self): del L[-1] if L[-1] == 0: del L[-1] - s = "%s.%s(%s)" % (self.__class__.__module__, + s = "%s.%s(%s)" % (_get_class_module(self), self.__class__.__qualname__, ", ".join(map(str, L))) if self._tzinfo is not None: @@ -2372,10 +2379,10 @@ def __repr__(self): if self is self.utc: return 'datetime.timezone.utc' if self._name is None: - return "%s.%s(%r)" % (self.__class__.__module__, + return "%s.%s(%r)" % (_get_class_module(self), self.__class__.__qualname__, self._offset) - return "%s.%s(%r, %r)" % (self.__class__.__module__, + return "%s.%s(%r, %r)" % (_get_class_module(self), self.__class__.__qualname__, self._offset, self._name) @@ -2638,26 +2645,3 @@ def _name_from_offset(delta): # small dst() may get within its bounds; and it doesn't even matter if some # perverse time zone returns a negative dst()). So a breaking case must be # pretty bizarre, and a tzinfo subclass can override fromutc() if it is. - -try: - from _datetime import * -except ImportError: - pass -else: - # Clean up unused names - del (_DAYNAMES, _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH, _DI100Y, _DI400Y, - _DI4Y, _EPOCH, _MAXORDINAL, _MONTHNAMES, _build_struct_time, - _check_date_fields, _check_time_fields, - _check_tzinfo_arg, _check_tzname, _check_utc_offset, _cmp, _cmperror, - _date_class, _days_before_month, _days_before_year, _days_in_month, - _format_time, _format_offset, _index, _is_leap, _isoweek1monday, _math, - _ord2ymd, _time, _time_class, _tzinfo_class, _wrap_strftime, _ymd2ord, - _divide_and_round, _parse_isoformat_date, _parse_isoformat_time, - _parse_hh_mm_ss_ff, _IsoCalendarDate, _isoweek_to_gregorian, - _find_isoformat_datetime_separator, _FRACTION_CORRECTION, - _is_ascii_digit) - # XXX Since import * above excludes names that start with _, - # docstring does not get overwritten. In the future, it may be - # appropriate to maintain a single module level docstring and - # remove the following line. - from _datetime import __doc__ diff --git a/Lib/datetime.py b/Lib/datetime.py new file mode 100644 index 00000000000000..bad8beb4f6b026 --- /dev/null +++ b/Lib/datetime.py @@ -0,0 +1,9 @@ +try: + from _datetime import * + from _datetime import __doc__ +except ImportError: + from _pydatetime import * + from _pydatetime import __doc__ + +__all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo", + "MINYEAR", "MAXYEAR") diff --git a/Misc/NEWS.d/next/Library/2023-04-19-16-08-53.gh-issue-84976.HwbzlD.rst b/Misc/NEWS.d/next/Library/2023-04-19-16-08-53.gh-issue-84976.HwbzlD.rst new file mode 100644 index 00000000000000..8658627aeba434 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-19-16-08-53.gh-issue-84976.HwbzlD.rst @@ -0,0 +1,5 @@ +Create a new ``Lib/_pydatetime.py`` file that defines the Python version of +the ``datetime`` module, and make ``datetime`` import the contents of the +new library only if the C implementation is missing. Currently, the full +Python implementation is defined and then deleted if the C implementation is +not available, slowing down ``import datetime`` unnecessarily.