diff --git a/Doc/library/time.rst b/Doc/library/time.rst index a931134331f0a5c..d7ca0628c662ea0 100644 --- a/Doc/library/time.rst +++ b/Doc/library/time.rst @@ -122,23 +122,24 @@ An explanation of some terminology and conventions is in order. Functions --------- -.. function:: asctime([t]) +.. function:: asctime([time_tuple]) Convert a tuple or :class:`struct_time` representing a time as returned by :func:`gmtime` or :func:`localtime` to a string of the following form: ``'Sun Jun 20 23:21:05 1993'``. The day field is two characters long and is space padded if the day is a single digit, - e.g.: ``'Wed Jun 9 04:26:40 1993'``. + for example: ``'Wed Jun 9 04:26:40 1993'``. - If *t* is not provided, the current time as returned by :func:`localtime` - is used. Locale information is not used by :func:`asctime`. + If *time_tuple* is not provided, + the current time as returned by :func:`localtime` is used. + Locale information is not used by :func:`asctime`. .. note:: Unlike the C function of the same name, :func:`asctime` does not add a trailing newline. -.. function:: pthread_getcpuclockid(thread_id) +.. function:: pthread_getcpuclockid(thread_id, /) Return the *clk_id* of the thread-specific CPU-time clock for the specified *thread_id*. @@ -157,7 +158,7 @@ Functions .. versionadded:: 3.7 -.. function:: clock_getres(clk_id) +.. function:: clock_getres(clk_id, /) Return the resolution (precision) of the specified clock *clk_id*. Refer to :ref:`time-clock-id-constants` for a list of accepted values for *clk_id*. @@ -167,7 +168,7 @@ Functions .. versionadded:: 3.3 -.. function:: clock_gettime(clk_id) -> float +.. function:: clock_gettime(clk_id, /) -> float Return the time of the specified clock *clk_id*. Refer to :ref:`time-clock-id-constants` for a list of accepted values for *clk_id*. @@ -180,7 +181,7 @@ Functions .. versionadded:: 3.3 -.. function:: clock_gettime_ns(clk_id) -> int +.. function:: clock_gettime_ns(clk_id, /) -> int Similar to :func:`clock_gettime` but return time as nanoseconds. @@ -189,7 +190,7 @@ Functions .. versionadded:: 3.7 -.. function:: clock_settime(clk_id, time) +.. function:: clock_settime(clk_id, time, /) Set the time of the specified clock *clk_id*. Currently, :data:`CLOCK_REALTIME` is the only accepted value for *clk_id*. @@ -205,7 +206,7 @@ Functions Accepts any real number as *time*, not only integer or float. -.. function:: clock_settime_ns(clk_id, time: int) +.. function:: clock_settime_ns(clk_id, time: int, /) Similar to :func:`clock_settime` but set time with nanoseconds. @@ -214,23 +215,23 @@ Functions .. versionadded:: 3.7 -.. function:: ctime([secs]) +.. function:: ctime(seconds=None, /) Convert a time expressed in seconds since the epoch_ to a string of a form: ``'Sun Jun 20 23:21:05 1993'`` representing local time. The day field is two characters long and is space padded if the day is a single digit, - e.g.: ``'Wed Jun 9 04:26:40 1993'``. + for example: ``'Wed Jun 9 04:26:40 1993'``. - If *secs* is not provided or :const:`None`, the current time as - returned by :func:`.time` is used. ``ctime(secs)`` is equivalent to - ``asctime(localtime(secs))``. Locale information is not used by + If *seconds* is not provided or :const:`None`, the current time as + returned by :func:`.time` is used. ``ctime(seconds)`` is equivalent to + ``asctime(localtime(seconds))``. Locale information is not used by :func:`ctime`. .. versionchanged:: 3.15 Accepts any real number, not only integer or float. -.. function:: get_clock_info(name) +.. function:: get_clock_info(name, /) Get information on the specified clock as a namespace object. Supported clock names and the corresponding functions to read their value @@ -255,10 +256,10 @@ Functions .. versionadded:: 3.3 -.. function:: gmtime([secs]) +.. function:: gmtime(seconds=None, /) Convert a time expressed in seconds since the epoch_ to a :class:`struct_time` in - UTC in which the dst flag is always zero. If *secs* is not provided or + UTC in which the dst flag is always zero. If *seconds* is not provided or :const:`None`, the current time as returned by :func:`.time` is used. Fractions of a second are ignored. See above for a description of the :class:`struct_time` object. See :func:`calendar.timegm` for the inverse of this @@ -268,11 +269,12 @@ Functions Accepts any real number, not only integer or float. -.. function:: localtime([secs]) +.. function:: localtime(seconds=None, /) - Like :func:`gmtime` but converts to local time. If *secs* is not provided or - :const:`None`, the current time as returned by :func:`.time` is used. The dst - flag is set to ``1`` when DST applies to the given time. + Like :func:`gmtime` but converts to local time. + If *seconds* is not provided or :const:`None`, + the current time as returned by :func:`.time` is used. + The dst flag is set to ``1`` when DST applies to the given time. :func:`localtime` may raise :exc:`OverflowError`, if the timestamp is outside the range of values supported by the platform C :c:func:`localtime` @@ -284,7 +286,7 @@ Functions Accepts any real number, not only integer or float. -.. function:: mktime(t) +.. function:: mktime(time_tuple, /) This is the inverse function of :func:`localtime`. Its argument is the :class:`struct_time` or full 9-tuple (since the dst flag is needed; use ``-1`` @@ -391,7 +393,7 @@ Functions .. versionadded:: 3.7 -.. function:: sleep(secs) +.. function:: sleep(seconds, /) Suspend execution of the calling thread for the given number of seconds. The argument may be a non-integer to indicate a more precise sleep time. @@ -404,13 +406,16 @@ Functions .. rubric:: Windows implementation - On Windows, if *secs* is zero, the thread relinquishes the remainder of its - time slice to any other thread that is ready to run. If there are no other - threads ready to run, the function returns immediately, and the thread - continues execution. On Windows 10 and newer the implementation uses + On Windows, if *seconds* is zero, + the thread relinquishes the remainder of its time slice + to any other thread that is ready to run. + If there are no other threads ready to run, + the function returns immediately, and the thread continues execution. + On Windows 10 and newer the implementation uses a `high-resolution timer `_ - which provides resolution of 100 nanoseconds. If *secs* is zero, ``Sleep(0)`` is used. + which provides resolution of 100 nanoseconds. + If *seconds* is zero, ``Sleep(0)`` is used. .. rubric:: Unix implementation @@ -425,12 +430,13 @@ Functions To voluntarily relinquish the CPU, specify a real-time :ref:`scheduling policy ` and use :func:`os.sched_yield` instead. - .. audit-event:: time.sleep secs + .. audit-event:: time.sleep seconds .. versionchanged:: 3.5 - The function now sleeps at least *secs* even if the sleep is interrupted - by a signal, except if the signal handler raises an exception (see - :pep:`475` for the rationale). + The function now sleeps at least *seconds* + even if the sleep is interrupted by a signal, + except if the signal handler raises an exception + (see :pep:`475` for the rationale). .. versionchanged:: 3.11 On Unix, the ``clock_nanosleep()`` and ``nanosleep()`` functions are now @@ -445,13 +451,13 @@ Functions .. index:: single: % (percent); datetime format -.. function:: strftime(format[, t]) +.. function:: strftime(format[, time_tuple]) Convert a tuple or :class:`struct_time` representing a time as returned by :func:`gmtime` or :func:`localtime` to a string as specified by the *format* - argument. If *t* is not provided, the current time as returned by + argument. If *time_tuple* is not provided, the current time as returned by :func:`localtime` is used. *format* must be a string. :exc:`ValueError` is - raised if any field in *t* is outside of the allowed range. + raised if any field in *time_tuple* is outside of the allowed range. 0 is a legal argument for any position in the time tuple; if it is normally illegal the value is forced to a correct one. diff --git a/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py index 899ecec1ba2afa4..b12e209707a9de3 100644 --- a/Lib/test/test_lazy_import/__init__.py +++ b/Lib/test/test_lazy_import/__init__.py @@ -678,17 +678,53 @@ def test_lazy_modules_tracks_lazy_imports(self): class ErrorHandlingTests(LazyImportTestCase): """Tests for error handling during lazy import reification.""" - def test_missing_lazy_submodule_raises_attribute_error(self): - """Accessing a nonexistent lazy submodule via parent attr raises AttributeError.""" + def test_missing_lazy_submodule_raises_module_not_found_error(self): + """Accessing a nonexistent lazy submodule via parent attr raises ModuleNotFoundError.""" code = textwrap.dedent(""" lazy import test.test_lazy_import.data.nonexistent_module try: _ = test.test_lazy_import.data.nonexistent_module - except AttributeError: + except ModuleNotFoundError: pass else: - raise AssertionError("AttributeError was not raised") + raise AssertionError("ModuleNotFoundError was not raised") + """) + assert_python_ok("-c", code) + + def test_non_package_lazily_imported(self): + """Accessing a nonexistent lazy name via parent attr raises ModuleNotFoundError.""" + code = textwrap.dedent(""" + lazy import math.pi + + try: + _ = math.pi + except ModuleNotFoundError: + pass + else: + raise AssertionError("ModuleNotFoundError was not raised") + """) + assert_python_ok("-c", code) + + def test_non_package_lazily_imported_as(self): + """Doing a dotted lazy import as still works""" + code = textwrap.dedent(""" + lazy import math.pi as pi + pi + """) + assert_python_ok("-c", code) + + def test_missing_attribute_raises_import_error(self): + """Accessing a nonexistent lazy name via from import raises ImportError.""" + code = textwrap.dedent(""" + lazy from sys import doesnotexist + + try: + _ = doesnotexist + except ImportError: + pass + else: + raise AssertionError("ImportError was not raised") """) assert_python_ok("-c", code) diff --git a/Lib/test/test_lazy_import/data/lazypkg/__init__.py b/Lib/test/test_lazy_import/data/lazypkg/__init__.py new file mode 100644 index 000000000000000..276b51823fee32a --- /dev/null +++ b/Lib/test/test_lazy_import/data/lazypkg/__init__.py @@ -0,0 +1 @@ +lazy from . import bar diff --git a/Lib/test/test_lazy_import/data/lazypkg/bar.py b/Lib/test/test_lazy_import/data/lazypkg/bar.py new file mode 100644 index 000000000000000..b8d8b60b886b88a --- /dev/null +++ b/Lib/test/test_lazy_import/data/lazypkg/bar.py @@ -0,0 +1,2 @@ +print("BAR_MODULE_LOADED") +def f(): pass diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index e38d0942e463e9c..8e4c28562a6cbe5 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -5611,11 +5611,11 @@ class TestLazyImportSuggestions(unittest.TestCase): def test_attribute_error_does_not_reify_lazy_imports(self): """Printing an AttributeError should not trigger lazy import reification.""" - # pkg.bar prints "BAR_MODULE_LOADED" when imported. + # lazypkg.bar prints "BAR_MODULE_LOADED" when imported. # If lazy import is reified during suggestion computation, we'll see it. code = textwrap.dedent(""" - lazy import test.test_lazy_import.data.pkg.bar - test.test_lazy_import.data.pkg.nonexistent + lazy import test.test_lazy_import.data.lazypkg + test.test_lazy_import.data.lazypkg.nonexistent """) rc, stdout, stderr = assert_python_failure('-c', code) self.assertNotIn(b"BAR_MODULE_LOADED", stdout) @@ -5624,9 +5624,9 @@ def test_traceback_formatting_does_not_reify_lazy_imports(self): """Formatting a traceback should not trigger lazy import reification.""" code = textwrap.dedent(""" import traceback - lazy import test.test_lazy_import.data.pkg.bar + lazy import test.test_lazy_import.data.lazypkg try: - test.test_lazy_import.data.pkg.nonexistent + test.test_lazy_import.data.lazypkg.nonexistent except AttributeError: traceback.format_exc() print("OK") @@ -5638,9 +5638,9 @@ def test_traceback_formatting_does_not_reify_lazy_imports(self): def test_suggestion_still_works_for_non_lazy_attributes(self): """Suggestions should still work for non-lazy module attributes.""" code = textwrap.dedent(""" - lazy import test.test_lazy_import.data.pkg.bar + lazy import test.test_lazy_import.data.lazypkg # Typo for __name__ - test.test_lazy_import.data.pkg.__nme__ + test.test_lazy_import.data.lazypkg.__nme__ """) rc, stdout, stderr = assert_python_failure('-c', code) self.assertIn(b"__name__", stderr) diff --git a/Makefile.pre.in b/Makefile.pre.in index 46fa26e01572cc2..d3d24a13898d992 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2690,6 +2690,7 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_lazy_import/data/pkg \ test/test_lazy_import/data/badsyntax \ test/test_lazy_import/data/circular_import_pkg \ + test/test_lazy_import/data/lazypkg \ test/test_lazy_import/data/metasyntactic \ test/test_lazy_import/data/metasyntactic/foo \ test/test_lazy_import/data/metasyntactic/foo/ack \ diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 70d7e1b3713687a..0005974b52499ce 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -978,7 +978,7 @@ time_strftime(PyObject *module, PyObject *args) #undef time_char #undef format_time PyDoc_STRVAR(strftime_doc, -"strftime(format[, tuple]) -> string\n\ +"strftime(format[, time_tuple]) -> string\n\ \n\ Convert a time tuple to a string according to a format specification.\n\ See the library reference manual for formatting codes. When the time tuple\n\ @@ -1003,7 +1003,7 @@ time_strptime(PyObject *self, PyObject *args) PyDoc_STRVAR(strptime_doc, -"strptime(string, format) -> struct_time\n\ +"strptime(string[, format]) -> struct_time\n\ \n\ Parse a string to a time tuple according to a format specification.\n\ See the library reference manual for formatting codes (same as\n\ @@ -1056,7 +1056,7 @@ time_asctime(PyObject *module, PyObject *args) } PyDoc_STRVAR(asctime_doc, -"asctime([tuple]) -> string\n\ +"asctime([time_tuple]) -> string\n\ \n\ Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.\n\ When the time tuple is not present, current time as returned by localtime()\n\ @@ -1075,11 +1075,11 @@ time_ctime(PyObject *self, PyObject *args) } PyDoc_STRVAR(ctime_doc, -"ctime(seconds) -> string\n\ +"ctime([seconds]) -> string\n\ \n\ Convert a time in seconds since the Epoch to a string in local time.\n\ -This is equivalent to asctime(localtime(seconds)). When the time tuple is\n\ -not present, current time as returned by localtime() is used."); +This is equivalent to asctime(localtime(seconds)). When 'seconds' is not\n\ +passed in, convert the current time instead."); #ifdef HAVE_MKTIME static PyObject * @@ -1153,7 +1153,7 @@ time_mktime(PyObject *module, PyObject *tm_tuple) } PyDoc_STRVAR(mktime_doc, -"mktime(tuple) -> floating-point number\n\ +"mktime(time_tuple) -> floating-point number\n\ \n\ Convert a time tuple in local time to seconds since the Epoch.\n\ Note that mktime(gmtime(0)) will not generally return zero for most\n\ diff --git a/Python/import.c b/Python/import.c index 5ca78a971fa54c6..47d5296a2fec9b1 100644 --- a/Python/import.c +++ b/Python/import.c @@ -3940,19 +3940,6 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) goto error; } - Py_ssize_t dot = -1; - int full = 0; - if (lz->lz_attr != NULL) { - full = 1; - } - if (!full) { - dot = PyUnicode_FindChar(lz->lz_from, '.', 0, - PyUnicode_GET_LENGTH(lz->lz_from), 1); - } - if (dot < 0) { - full = 1; - } - if (lz->lz_attr != NULL) { if (PyUnicode_Check(lz->lz_attr)) { fromlist = PyTuple_New(1); @@ -3978,23 +3965,10 @@ _PyImport_LoadLazyImportTstate(PyThreadState *tstate, PyObject *lazy_import) PyErr_SetString(PyExc_ImportError, "__import__ not found"); goto error; } - if (full) { - obj = _PyEval_ImportNameWithImport( - tstate, import_func, globals, globals, - lz->lz_from, fromlist, _PyLong_GetZero() - ); - } - else { - PyObject *name = PyUnicode_Substring(lz->lz_from, 0, dot); - if (name == NULL) { - goto error; - } - obj = _PyEval_ImportNameWithImport( - tstate, import_func, globals, globals, - name, fromlist, _PyLong_GetZero() - ); - Py_DECREF(name); - } + obj = _PyEval_ImportNameWithImport( + tstate, import_func, globals, globals, + lz->lz_from, fromlist, _PyLong_GetZero() + ); if (obj == NULL) { goto error; }