diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 9561c40776f01c..0ccc7a2b4482ec 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -304,6 +304,12 @@ clocks to track time. custom :class:`contextvars.Context` for the *callback* to run in. The current context is used when no *context* is provided. + .. note:: + + For performance, callbacks scheduled with :meth:`loop.call_later` + may run up to one clock-resolution early (see + ``time.get_clock_info('monotonic').resolution``). + .. versionchanged:: 3.7 The *context* keyword-only parameter was added. See :pep:`567` for more details. @@ -324,6 +330,12 @@ clocks to track time. An instance of :class:`asyncio.TimerHandle` is returned which can be used to cancel the callback. + .. note:: + + For performance, callbacks scheduled with :meth:`loop.call_at` + may run up to one clock-resolution early (see + ``time.get_clock_info('monotonic').resolution``). + .. versionchanged:: 3.7 The *context* keyword-only parameter was added. See :pep:`567` for more details. diff --git a/Doc/library/asyncio-stream.rst b/Doc/library/asyncio-stream.rst index 90c90862ca1ed3..e1568ae330b70f 100644 --- a/Doc/library/asyncio-stream.rst +++ b/Doc/library/asyncio-stream.rst @@ -321,6 +321,9 @@ StreamWriter stream.write(data) await stream.drain() + .. note:: + The *data* buffer should be a C contiguous one-dimensional :term:`bytes-like object `. + .. method:: writelines(data) The method writes a list (or any iterable) of bytes to the underlying socket diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index 10ddfa02b43156..0b99a832405549 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -573,7 +573,7 @@ Decimal objects >>> Decimal(321).exp() Decimal('2.561702493119680037517373933E+139') - .. classmethod:: from_float(f) + .. classmethod:: from_float(f, /) Alternative constructor that only accepts instances of :class:`float` or :class:`int`. @@ -600,7 +600,7 @@ Decimal objects .. versionadded:: 3.1 - .. classmethod:: from_number(number) + .. classmethod:: from_number(number, /) Alternative constructor that only accepts instances of :class:`float`, :class:`int` or :class:`Decimal`, but not strings @@ -991,7 +991,7 @@ Each thread has its own current context which is accessed or changed using the Return the current context for the active thread. -.. function:: setcontext(c) +.. function:: setcontext(c, /) Set the current context for the active thread to *c*. @@ -1168,11 +1168,11 @@ In addition to the three supplied contexts, new contexts can be created with the Return a duplicate of the context. - .. method:: copy_decimal(num) + .. method:: copy_decimal(num, /) Return a copy of the Decimal instance num. - .. method:: create_decimal(num) + .. method:: create_decimal(num='0', /) Creates a new Decimal instance from *num* but using *self* as context. Unlike the :class:`Decimal` constructor, the context precision, @@ -1196,7 +1196,7 @@ In addition to the three supplied contexts, new contexts can be created with the If the argument is a string, no leading or trailing whitespace or underscores are permitted. - .. method:: create_decimal_from_float(f) + .. method:: create_decimal_from_float(f, /) Creates a new Decimal instance from a float *f* but rounding using *self* as the context. Unlike the :meth:`Decimal.from_float` class method, @@ -1234,222 +1234,222 @@ In addition to the three supplied contexts, new contexts can be created with the recounted here. - .. method:: abs(x) + .. method:: abs(x, /) Returns the absolute value of *x*. - .. method:: add(x, y) + .. method:: add(x, y, /) Return the sum of *x* and *y*. - .. method:: canonical(x) + .. method:: canonical(x, /) Returns the same Decimal object *x*. - .. method:: compare(x, y) + .. method:: compare(x, y, /) Compares *x* and *y* numerically. - .. method:: compare_signal(x, y) + .. method:: compare_signal(x, y, /) Compares the values of the two operands numerically. - .. method:: compare_total(x, y) + .. method:: compare_total(x, y, /) Compares two operands using their abstract representation. - .. method:: compare_total_mag(x, y) + .. method:: compare_total_mag(x, y, /) Compares two operands using their abstract representation, ignoring sign. - .. method:: copy_abs(x) + .. method:: copy_abs(x, /) Returns a copy of *x* with the sign set to 0. - .. method:: copy_negate(x) + .. method:: copy_negate(x, /) Returns a copy of *x* with the sign inverted. - .. method:: copy_sign(x, y) + .. method:: copy_sign(x, y, /) Copies the sign from *y* to *x*. - .. method:: divide(x, y) + .. method:: divide(x, y, /) Return *x* divided by *y*. - .. method:: divide_int(x, y) + .. method:: divide_int(x, y, /) Return *x* divided by *y*, truncated to an integer. - .. method:: divmod(x, y) + .. method:: divmod(x, y, /) Divides two numbers and returns the integer part of the result. - .. method:: exp(x) + .. method:: exp(x, /) Returns ``e ** x``. - .. method:: fma(x, y, z) + .. method:: fma(x, y, z, /) Returns *x* multiplied by *y*, plus *z*. - .. method:: is_canonical(x) + .. method:: is_canonical(x, /) Returns ``True`` if *x* is canonical; otherwise returns ``False``. - .. method:: is_finite(x) + .. method:: is_finite(x, /) Returns ``True`` if *x* is finite; otherwise returns ``False``. - .. method:: is_infinite(x) + .. method:: is_infinite(x, /) Returns ``True`` if *x* is infinite; otherwise returns ``False``. - .. method:: is_nan(x) + .. method:: is_nan(x, /) Returns ``True`` if *x* is a qNaN or sNaN; otherwise returns ``False``. - .. method:: is_normal(x) + .. method:: is_normal(x, /) Returns ``True`` if *x* is a normal number; otherwise returns ``False``. - .. method:: is_qnan(x) + .. method:: is_qnan(x, /) Returns ``True`` if *x* is a quiet NaN; otherwise returns ``False``. - .. method:: is_signed(x) + .. method:: is_signed(x, /) Returns ``True`` if *x* is negative; otherwise returns ``False``. - .. method:: is_snan(x) + .. method:: is_snan(x, /) Returns ``True`` if *x* is a signaling NaN; otherwise returns ``False``. - .. method:: is_subnormal(x) + .. method:: is_subnormal(x, /) Returns ``True`` if *x* is subnormal; otherwise returns ``False``. - .. method:: is_zero(x) + .. method:: is_zero(x, /) Returns ``True`` if *x* is a zero; otherwise returns ``False``. - .. method:: ln(x) + .. method:: ln(x, /) Returns the natural (base e) logarithm of *x*. - .. method:: log10(x) + .. method:: log10(x, /) Returns the base 10 logarithm of *x*. - .. method:: logb(x) + .. method:: logb(x, /) Returns the exponent of the magnitude of the operand's MSD. - .. method:: logical_and(x, y) + .. method:: logical_and(x, y, /) Applies the logical operation *and* between each operand's digits. - .. method:: logical_invert(x) + .. method:: logical_invert(x, /) Invert all the digits in *x*. - .. method:: logical_or(x, y) + .. method:: logical_or(x, y, /) Applies the logical operation *or* between each operand's digits. - .. method:: logical_xor(x, y) + .. method:: logical_xor(x, y, /) Applies the logical operation *xor* between each operand's digits. - .. method:: max(x, y) + .. method:: max(x, y, /) Compares two values numerically and returns the maximum. - .. method:: max_mag(x, y) + .. method:: max_mag(x, y, /) Compares the values numerically with their sign ignored. - .. method:: min(x, y) + .. method:: min(x, y, /) Compares two values numerically and returns the minimum. - .. method:: min_mag(x, y) + .. method:: min_mag(x, y, /) Compares the values numerically with their sign ignored. - .. method:: minus(x) + .. method:: minus(x, /) Minus corresponds to the unary prefix minus operator in Python. - .. method:: multiply(x, y) + .. method:: multiply(x, y, /) Return the product of *x* and *y*. - .. method:: next_minus(x) + .. method:: next_minus(x, /) Returns the largest representable number smaller than *x*. - .. method:: next_plus(x) + .. method:: next_plus(x, /) Returns the smallest representable number larger than *x*. - .. method:: next_toward(x, y) + .. method:: next_toward(x, y, /) Returns the number closest to *x*, in direction towards *y*. - .. method:: normalize(x) + .. method:: normalize(x, /) Reduces *x* to its simplest form. - .. method:: number_class(x) + .. method:: number_class(x, /) Returns an indication of the class of *x*. - .. method:: plus(x) + .. method:: plus(x, /) Plus corresponds to the unary prefix plus operator in Python. This operation applies the context precision and rounding, so it is *not* an @@ -1490,7 +1490,7 @@ In addition to the three supplied contexts, new contexts can be created with the always exact. - .. method:: quantize(x, y) + .. method:: quantize(x, y, /) Returns a value equal to *x* (rounded), having the exponent of *y*. @@ -1500,7 +1500,7 @@ In addition to the three supplied contexts, new contexts can be created with the Just returns 10, as this is Decimal, :) - .. method:: remainder(x, y) + .. method:: remainder(x, y, /) Returns the remainder from integer division. @@ -1508,43 +1508,43 @@ In addition to the three supplied contexts, new contexts can be created with the dividend. - .. method:: remainder_near(x, y) + .. method:: remainder_near(x, y, /) Returns ``x - y * n``, where *n* is the integer nearest the exact value of ``x / y`` (if the result is 0 then its sign will be the sign of *x*). - .. method:: rotate(x, y) + .. method:: rotate(x, y, /) Returns a rotated copy of *x*, *y* times. - .. method:: same_quantum(x, y) + .. method:: same_quantum(x, y, /) Returns ``True`` if the two operands have the same exponent. - .. method:: scaleb (x, y) + .. method:: scaleb (x, y, /) Returns the first operand after adding the second value its exp. - .. method:: shift(x, y) + .. method:: shift(x, y, /) Returns a shifted copy of *x*, *y* times. - .. method:: sqrt(x) + .. method:: sqrt(x, /) Square root of a non-negative number to context precision. - .. method:: subtract(x, y) + .. method:: subtract(x, y, /) Return the difference between *x* and *y*. - .. method:: to_eng_string(x) + .. method:: to_eng_string(x, /) Convert to a string, using engineering notation if an exponent is needed. @@ -1553,12 +1553,12 @@ In addition to the three supplied contexts, new contexts can be created with the require the addition of either one or two trailing zeros. - .. method:: to_integral_exact(x) + .. method:: to_integral_exact(x, /) Rounds to an integer. - .. method:: to_sci_string(x) + .. method:: to_sci_string(x, /) Converts a number to a string using scientific notation. diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index 5a2c6bdd27c386..02b73ccd3f3d19 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -350,6 +350,13 @@ searches them recursively for docstrings, which are then scanned for tests. Any classes found are recursively searched similarly, to test docstrings in their contained methods and nested classes. +.. note:: + + ``doctest`` can only automatically discover classes and functions that are + defined at the module level or inside other classes. + + Since nested classes and functions only exist when an outer function + is called, they cannot be discovered. Define them outside to make them visible. .. _doctest-finding-examples: diff --git a/Doc/library/locale.rst b/Doc/library/locale.rst index d48ea04077f366..0800b3e5677c93 100644 --- a/Doc/library/locale.rst +++ b/Doc/library/locale.rst @@ -42,7 +42,7 @@ The :mod:`locale` module defines the following exception and functions: If *locale* is a pair, it is converted to a locale name using the locale aliasing engine. The language code has the same format as a :ref:`locale name `, - but without encoding and ``@``-modifier. + but without encoding. The language code and encoding can be ``None``. If *locale* is omitted or ``None``, the current setting for *category* is @@ -58,6 +58,9 @@ The :mod:`locale` module defines the following exception and functions: specified in the :envvar:`LANG` environment variable). If the locale is not changed thereafter, using multithreading should not cause problems. + .. versionchanged:: next + Support language codes with ``@``-modifiers. + .. function:: localeconv() @@ -366,11 +369,15 @@ The :mod:`locale` module defines the following exception and functions: values except :const:`LC_ALL`. It defaults to :const:`LC_CTYPE`. The language code has the same format as a :ref:`locale name `, - but without encoding and ``@``-modifier. + but without encoding. The language code and encoding may be ``None`` if their values cannot be determined. The "C" locale is represented as ``(None, None)``. + .. versionchanged:: next + ``@``-modifier are no longer silently removed, but included in + the language code. + .. function:: getpreferredencoding(do_setlocale=True) diff --git a/Doc/library/resource.rst b/Doc/library/resource.rst index 0515d205bbca0b..0421b355056419 100644 --- a/Doc/library/resource.rst +++ b/Doc/library/resource.rst @@ -50,6 +50,11 @@ this module for those platforms. .. data:: RLIM_INFINITY Constant used to represent the limit for an unlimited resource. + Its value is larger than any limited resource value. + + .. versionchanged:: next + It is now always positive. + Previously, it could be negative, such as -1 or -3. .. function:: getrlimit(resource) diff --git a/Doc/library/uuid.rst b/Doc/library/uuid.rst index 6698e6d3f43c43..aa4f1bf940bc5c 100644 --- a/Doc/library/uuid.rst +++ b/Doc/library/uuid.rst @@ -411,7 +411,7 @@ Here are some examples of typical usage of the :mod:`uuid` module:: >>> import uuid >>> # make a UUID based on the host ID and current time - >>> uuid.uuid1() + >>> uuid.uuid1() # doctest: +SKIP UUID('a8098c1a-f86e-11da-bd1a-00112444be1e') >>> # make a UUID using an MD5 hash of a namespace UUID and a name @@ -449,15 +449,24 @@ Here are some examples of typical usage of the :mod:`uuid` module:: >>> uuid.MAX UUID('ffffffff-ffff-ffff-ffff-ffffffffffff') + >>> # same as UUIDv1 but with fields reordered to improve DB locality + >>> uuid.uuid6() # doctest: +SKIP + UUID('1f0799c0-98b9-62db-92c6-a0d365b91053') + >>> # get UUIDv7 creation (local) time as a timestamp in milliseconds >>> u = uuid.uuid7() >>> u.time # doctest: +SKIP 1743936859822 + >>> # get UUIDv7 creation (local) time as a datetime object >>> import datetime as dt >>> dt.datetime.fromtimestamp(u.time / 1000) # doctest: +SKIP datetime.datetime(...) + >>> # make a UUID with custom blocks + >>> uuid.uuid8(0x12345678, 0x9abcdef0, 0x11223344) + UUID('00001234-5678-8ef0-8000-000011223344') + .. _uuid-cli-example: diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 252d8966b7450f..eb073cae9be491 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -209,6 +209,13 @@ Other language changes as keyword arguments at construction time. (Contributed by Serhiy Storchaka, Oleg Iarygin, and Yoav Nir in :gh:`74185`.) +* The :attr:`~object.__dict__` and :attr:`!__weakref__` descriptors now use a + single descriptor instance per interpreter, shared across all types that + need them. + This speeds up class creation, and helps avoid reference cycles. + (Contributed by Petr Viktorin in :gh:`135228`.) + + New modules =========== @@ -274,6 +281,15 @@ http.cookies (Contributed by Nick Burns and Senthil Kumaran in :gh:`92936`.) +locale +------ + +* :func:`~locale.setlocale` now supports language codes with ``@``-modifiers. + ``@``-modifier are no longer silently removed in :func:`~locale.getlocale`, + but included in the language code. + (Contributed by Serhiy Storchaka in :gh:`137729`.) + + math ---- @@ -579,6 +595,12 @@ Porting to Python 3.15 The |pythoncapi_compat_project| can be used to get most of these new functions on Python 3.14 and older. +* :data:`resource.RLIM_INFINITY` is now always positive. + Passing a negative integer value that corresponded to its old value + (such as ``-1`` or ``-3``, depending on platform) to + :func:`resource.setrlimit` and :func:`resource.prlimit` is now deprecated. + (Contributed by Serhiy Storchaka in :gh:`137044`.) + Deprecated C APIs ----------------- diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 27102588076cc2..c5a08e05e70287 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -566,6 +566,8 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(type_params)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_STR(utf_8)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(CANCELLED)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(Emax)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(Emin)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(FINISHED)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(False)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(JSONDecodeError)); @@ -848,11 +850,13 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(callback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cancel)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(capath)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(capitals)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(category)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cb_type)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(certfile)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(chain)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(check_same_thread)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(clamp)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(clear)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(close)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(closed)); @@ -892,6 +896,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(coro)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(count)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(covariant)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(ctx)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(cwd)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(d_parameter_type)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(data)); @@ -1113,6 +1118,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(module)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(module_globals)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(modules)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(modulo)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(month)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(mro)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(msg)); @@ -1181,6 +1187,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pos1)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pos2)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(posix)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(prec)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(preserve_exc)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(print_file_and_line)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(priority)); @@ -1303,6 +1310,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(traceback)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(trailers)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(translate)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(traps)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(true)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(truncate)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(twice)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 2d6130c216993f..9e49e4497750a0 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -57,6 +57,8 @@ struct _Py_global_strings { struct { STRUCT_FOR_ID(CANCELLED) + STRUCT_FOR_ID(Emax) + STRUCT_FOR_ID(Emin) STRUCT_FOR_ID(FINISHED) STRUCT_FOR_ID(False) STRUCT_FOR_ID(JSONDecodeError) @@ -339,11 +341,13 @@ struct _Py_global_strings { STRUCT_FOR_ID(callback) STRUCT_FOR_ID(cancel) STRUCT_FOR_ID(capath) + STRUCT_FOR_ID(capitals) STRUCT_FOR_ID(category) STRUCT_FOR_ID(cb_type) STRUCT_FOR_ID(certfile) STRUCT_FOR_ID(chain) STRUCT_FOR_ID(check_same_thread) + STRUCT_FOR_ID(clamp) STRUCT_FOR_ID(clear) STRUCT_FOR_ID(close) STRUCT_FOR_ID(closed) @@ -383,6 +387,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(coro) STRUCT_FOR_ID(count) STRUCT_FOR_ID(covariant) + STRUCT_FOR_ID(ctx) STRUCT_FOR_ID(cwd) STRUCT_FOR_ID(d_parameter_type) STRUCT_FOR_ID(data) @@ -604,6 +609,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(module) STRUCT_FOR_ID(module_globals) STRUCT_FOR_ID(modules) + STRUCT_FOR_ID(modulo) STRUCT_FOR_ID(month) STRUCT_FOR_ID(mro) STRUCT_FOR_ID(msg) @@ -672,6 +678,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(pos1) STRUCT_FOR_ID(pos2) STRUCT_FOR_ID(posix) + STRUCT_FOR_ID(prec) STRUCT_FOR_ID(preserve_exc) STRUCT_FOR_ID(print_file_and_line) STRUCT_FOR_ID(priority) @@ -794,6 +801,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(traceback) STRUCT_FOR_ID(trailers) STRUCT_FOR_ID(translate) + STRUCT_FOR_ID(traps) STRUCT_FOR_ID(true) STRUCT_FOR_ID(truncate) STRUCT_FOR_ID(twice) diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index e300732e9e58c3..2cb1b104681300 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -691,6 +691,13 @@ struct _Py_interp_cached_objects { PyTypeObject *paramspecargs_type; PyTypeObject *paramspeckwargs_type; PyTypeObject *constevaluator_type; + + /* Descriptors for __dict__ and __weakref__ */ +#ifdef Py_GIL_DISABLED + PyMutex descriptor_mutex; +#endif + PyObject *dict_descriptor; + PyObject *weakref_descriptor; }; struct _Py_interp_static_objects { diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 9e72c7df77b945..5c65cb218dedbb 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -564,6 +564,8 @@ extern "C" { #define _Py_str_identifiers_INIT { \ INIT_ID(CANCELLED), \ + INIT_ID(Emax), \ + INIT_ID(Emin), \ INIT_ID(FINISHED), \ INIT_ID(False), \ INIT_ID(JSONDecodeError), \ @@ -846,11 +848,13 @@ extern "C" { INIT_ID(callback), \ INIT_ID(cancel), \ INIT_ID(capath), \ + INIT_ID(capitals), \ INIT_ID(category), \ INIT_ID(cb_type), \ INIT_ID(certfile), \ INIT_ID(chain), \ INIT_ID(check_same_thread), \ + INIT_ID(clamp), \ INIT_ID(clear), \ INIT_ID(close), \ INIT_ID(closed), \ @@ -890,6 +894,7 @@ extern "C" { INIT_ID(coro), \ INIT_ID(count), \ INIT_ID(covariant), \ + INIT_ID(ctx), \ INIT_ID(cwd), \ INIT_ID(d_parameter_type), \ INIT_ID(data), \ @@ -1111,6 +1116,7 @@ extern "C" { INIT_ID(module), \ INIT_ID(module_globals), \ INIT_ID(modules), \ + INIT_ID(modulo), \ INIT_ID(month), \ INIT_ID(mro), \ INIT_ID(msg), \ @@ -1179,6 +1185,7 @@ extern "C" { INIT_ID(pos1), \ INIT_ID(pos2), \ INIT_ID(posix), \ + INIT_ID(prec), \ INIT_ID(preserve_exc), \ INIT_ID(print_file_and_line), \ INIT_ID(priority), \ @@ -1301,6 +1308,7 @@ extern "C" { INIT_ID(traceback), \ INIT_ID(trailers), \ INIT_ID(translate), \ + INIT_ID(traps), \ INIT_ID(true), \ INIT_ID(truncate), \ INIT_ID(twice), \ diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 0ee7d555c56cdd..24df69aa93fda2 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -40,6 +40,7 @@ extern void _PyTypes_FiniTypes(PyInterpreterState *); extern void _PyTypes_FiniExtTypes(PyInterpreterState *interp); extern void _PyTypes_Fini(PyInterpreterState *); extern void _PyTypes_AfterFork(void); +extern void _PyTypes_FiniCachedDescriptors(PyInterpreterState *); static inline PyObject ** _PyStaticType_GET_WEAKREFS_LISTPTR(managed_static_type_state *state) diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index 02c62f68ff0801..4905007b95f8fa 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -16,6 +16,14 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(Emax); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(Emin); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(FINISHED); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -1144,6 +1152,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(capitals); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(category); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -1164,6 +1176,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(clamp); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(clear); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -1320,6 +1336,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(ctx); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(cwd); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2204,6 +2224,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(modulo); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(month); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2476,6 +2500,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(prec); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(preserve_exc); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); @@ -2964,6 +2992,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(traps); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(true); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index d29f1615f276d2..b98f21dcbe9220 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1283,10 +1283,6 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields): if '__slots__' in cls.__dict__: raise TypeError(f'{cls.__name__} already specifies __slots__') - # gh-102069: Remove existing __weakref__ descriptor. - # gh-135228: Make sure the original class can be garbage collected. - sys._clear_type_descriptors(cls) - # Create a new dict for our new class. cls_dict = dict(cls.__dict__) field_names = tuple(f.name for f in fields(cls)) @@ -1304,6 +1300,11 @@ def _add_slots(cls, is_frozen, weakref_slot, defined_fields): # available in _MARKER. cls_dict.pop(field_name, None) + # Remove __dict__ and `__weakref__` descriptors. + # They'll be added back if applicable. + cls_dict.pop('__dict__', None) + cls_dict.pop('__weakref__', None) # gh-102069 + # And finally create the class. qualname = getattr(cls, '__qualname__', None) newcls = type(cls)(cls.__name__, cls.__bases__, cls_dict) diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 8bcd741c446bd2..8a1437a2cc5d1e 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -297,7 +297,8 @@ def cache_from_source(path, debug_override=None, *, optimization=None): # Strip initial drive from a Windows path. We know we have an absolute # path here, so the second part of the check rules out a POSIX path that # happens to contain a colon at the second character. - if head[1] == ':' and head[0] not in path_separators: + # Slicing avoids issues with an empty (or short) `head`. + if head[1:2] == ':' and head[0:1] not in path_separators: head = head[2:] # Strip initial path separator from `head` to complete the conversion diff --git a/Lib/inspect.py b/Lib/inspect.py index 183e67fabf966e..d7814bfeb2b885 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1698,7 +1698,8 @@ def _shadowed_dict_from_weakref_mro_tuple(*weakref_mro): class_dict = dunder_dict['__dict__'] if not (type(class_dict) is types.GetSetDescriptorType and class_dict.__name__ == "__dict__" and - class_dict.__objclass__ is entry): + (class_dict.__objclass__ is object or + class_dict.__objclass__ is entry)): return class_dict return _sentinel diff --git a/Lib/locale.py b/Lib/locale.py index 0bde7ed51c66c1..37cafb4a601b3c 100644 --- a/Lib/locale.py +++ b/Lib/locale.py @@ -375,12 +375,14 @@ def _replace_encoding(code, encoding): def _append_modifier(code, modifier): if modifier == 'euro': if '.' not in code: - return code + '.ISO8859-15' + # Linux appears to require keeping the "@euro" modifier in place, + # even when using the ".ISO8859-15" encoding. + return code + '.ISO8859-15@euro' _, _, encoding = code.partition('.') - if encoding in ('ISO8859-15', 'UTF-8'): + if encoding == 'UTF-8': return code if encoding == 'ISO8859-1': - return _replace_encoding(code, 'ISO8859-15') + code = _replace_encoding(code, 'ISO8859-15') return code + '@' + modifier def normalize(localename): @@ -485,13 +487,18 @@ def _parse_localename(localename): # Deal with locale modifiers code, modifier = code.split('@', 1) if modifier == 'euro' and '.' not in code: - # Assume Latin-9 for @euro locales. This is bogus, - # since some systems may use other encodings for these - # locales. Also, we ignore other modifiers. - return code, 'iso-8859-15' + # Assume ISO8859-15 for @euro locales. Do note that some systems + # may use other encodings for these locales, so this may not always + # be correct. + return code + '@euro', 'ISO8859-15' + else: + modifier = '' if '.' in code: - return tuple(code.split('.')[:2]) + code, encoding = code.split('.')[:2] + if modifier: + code += '@' + modifier + return code, encoding elif code == 'C': return None, None elif code == 'UTF-8': @@ -516,7 +523,14 @@ def _build_localename(localetuple): if encoding is None: return language else: - return language + '.' + encoding + if '@' in language: + language, modifier = language.split('@', 1) + else: + modifier = '' + localename = language + '.' + encoding + if modifier: + localename += '@' + modifier + return localename except (TypeError, ValueError): raise TypeError('Locale must be None, a string, or an iterable of ' 'two strings -- language code, encoding.') from None @@ -888,6 +902,12 @@ def getpreferredencoding(do_setlocale=True): # SS 2025-06-10: # Remove 'c.utf8' -> 'en_US.UTF-8' because 'en_US.UTF-8' does not exist # on all platforms. +# +# SS 2025-07-30: +# Remove conflicts with GNU libc. +# +# removed 'el_gr@euro' +# removed 'uz_uz@cyrillic' locale_alias = { 'a3': 'az_AZ.KOI8-C', @@ -1021,7 +1041,6 @@ def getpreferredencoding(do_setlocale=True): 'el': 'el_GR.ISO8859-7', 'el_cy': 'el_CY.ISO8859-7', 'el_gr': 'el_GR.ISO8859-7', - 'el_gr@euro': 'el_GR.ISO8859-15', 'en': 'en_US.ISO8859-1', 'en_ag': 'en_AG.UTF-8', 'en_au': 'en_AU.ISO8859-1', @@ -1456,7 +1475,6 @@ def getpreferredencoding(do_setlocale=True): 'ur_pk': 'ur_PK.CP1256', 'uz': 'uz_UZ.UTF-8', 'uz_uz': 'uz_UZ.UTF-8', - 'uz_uz@cyrillic': 'uz_UZ.UTF-8', 've': 've_ZA.UTF-8', 've_za': 've_ZA.UTF-8', 'vi': 'vi_VN.TCVN', diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 8da6647c3f71fc..9dfeeccb81b34d 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -6013,5 +6013,69 @@ class A(metaclass=M): pass +class TestGenericDescriptors(unittest.TestCase): + def test___dict__(self): + class CustomClass: + pass + class SlotClass: + __slots__ = ['foo'] + class SlotSubClass(SlotClass): + pass + class IntSubclass(int): + pass + + dict_descriptor = CustomClass.__dict__['__dict__'] + self.assertEqual(dict_descriptor.__objclass__, object) + + for cls in CustomClass, SlotSubClass, IntSubclass: + with self.subTest(cls=cls): + self.assertIs(cls.__dict__['__dict__'], dict_descriptor) + instance = cls() + instance.attr = 123 + self.assertEqual( + dict_descriptor.__get__(instance, cls), + {'attr': 123}, + ) + with self.assertRaises(AttributeError): + print(dict_descriptor.__get__(True, bool)) + with self.assertRaises(AttributeError): + print(dict_descriptor.__get__(SlotClass(), SlotClass)) + + # delegation to type.__dict__ + self.assertIsInstance( + dict_descriptor.__get__(type, type), + types.MappingProxyType, + ) + + def test___weakref__(self): + class CustomClass: + pass + class SlotClass: + __slots__ = ['foo'] + class SlotSubClass(SlotClass): + pass + class IntSubclass(int): + pass + + weakref_descriptor = CustomClass.__dict__['__weakref__'] + self.assertEqual(weakref_descriptor.__objclass__, object) + + for cls in CustomClass, SlotSubClass: + with self.subTest(cls=cls): + self.assertIs(cls.__dict__['__weakref__'], weakref_descriptor) + instance = cls() + instance.attr = 123 + self.assertEqual( + weakref_descriptor.__get__(instance, cls), + None, + ) + with self.assertRaises(AttributeError): + weakref_descriptor.__get__(True, bool) + with self.assertRaises(AttributeError): + weakref_descriptor.__get__(SlotClass(), SlotClass) + with self.assertRaises(AttributeError): + weakref_descriptor.__get__(IntSubclass(), IntSubclass) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 6d6d5f96aab4a8..a77ce234deec58 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -580,6 +580,18 @@ def test_cache_from_source_respects_pycache_prefix_relative(self): self.util.cache_from_source(path, optimization=''), os.path.normpath(expect)) + @unittest.skipIf(sys.implementation.cache_tag is None, + 'requires sys.implementation.cache_tag to not be None') + def test_cache_from_source_in_root_with_pycache_prefix(self): + # Regression test for gh-82916 + pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode') + path = 'qux.py' + expect = os.path.join(os.path.sep, 'tmp', 'bytecode', + f'qux.{self.tag}.pyc') + with util.temporary_pycache_prefix(pycache_prefix): + with os_helper.change_cwd('/'): + self.assertEqual(self.util.cache_from_source(path), expect) + @unittest.skipIf(sys.implementation.cache_tag is None, 'requires sys.implementation.cache_tag to not be None') def test_source_from_cache_inside_pycache_prefix(self): diff --git a/Lib/test/test_locale.py b/Lib/test/test_locale.py index 698e137e3e8abd..01b1e754d04219 100644 --- a/Lib/test/test_locale.py +++ b/Lib/test/test_locale.py @@ -1,4 +1,5 @@ from decimal import Decimal +from test import support from test.support import cpython_only, verbose, is_android, linked_to_musl, os_helper from test.support.warnings_helper import check_warnings from test.support.import_helper import ensure_lazy_imports, import_fresh_module @@ -425,8 +426,8 @@ def test_hyphenated_encoding(self): self.check('cs_CZ.ISO8859-2', 'cs_CZ.ISO8859-2') def test_euro_modifier(self): - self.check('de_DE@euro', 'de_DE.ISO8859-15') - self.check('en_US.ISO8859-15@euro', 'en_US.ISO8859-15') + self.check('de_DE@euro', 'de_DE.ISO8859-15@euro') + self.check('en_US.ISO8859-15@euro', 'en_US.ISO8859-15@euro') self.check('de_DE.utf8@euro', 'de_DE.UTF-8') def test_latin_modifier(self): @@ -534,6 +535,105 @@ def test_setlocale_long_encoding(self): with self.assertRaises(locale.Error): locale.setlocale(locale.LC_ALL, loc2) + @support.subTests('localename,localetuple', [ + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'iso885915')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'iso88591')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'ISO8859-15')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'ISO8859-1')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', None)), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'iso885915')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'iso88591')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'ISO8859-15')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'ISO8859-1')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', None)), + ('el_GR.ISO8859-7@euro', ('el_GR@euro', 'iso88597')), + ('el_GR.ISO8859-7@euro', ('el_GR@euro', 'ISO8859-7')), + ('el_GR.ISO8859-7@euro', ('el_GR@euro', None)), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'iso885915')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'iso88591')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'ISO8859-15')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'ISO8859-1')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', None)), + ('ca_ES.UTF-8@valencia', ('ca_ES@valencia', 'utf8')), + ('ca_ES.UTF-8@valencia', ('ca_ES@valencia', 'UTF-8')), + ('ca_ES.UTF-8@valencia', ('ca_ES@valencia', None)), + ('ks_IN.UTF-8@devanagari', ('ks_IN@devanagari', 'utf8')), + ('ks_IN.UTF-8@devanagari', ('ks_IN@devanagari', 'UTF-8')), + ('ks_IN.UTF-8@devanagari', ('ks_IN@devanagari', None)), + ('sd_IN.UTF-8@devanagari', ('sd_IN@devanagari', 'utf8')), + ('sd_IN.UTF-8@devanagari', ('sd_IN@devanagari', 'UTF-8')), + ('sd_IN.UTF-8@devanagari', ('sd_IN@devanagari', None)), + ('be_BY.UTF-8@latin', ('be_BY@latin', 'utf8')), + ('be_BY.UTF-8@latin', ('be_BY@latin', 'UTF-8')), + ('be_BY.UTF-8@latin', ('be_BY@latin', None)), + ('sr_RS.UTF-8@latin', ('sr_RS@latin', 'utf8')), + ('sr_RS.UTF-8@latin', ('sr_RS@latin', 'UTF-8')), + ('sr_RS.UTF-8@latin', ('sr_RS@latin', None)), + ('ug_CN.UTF-8@latin', ('ug_CN@latin', 'utf8')), + ('ug_CN.UTF-8@latin', ('ug_CN@latin', 'UTF-8')), + ('ug_CN.UTF-8@latin', ('ug_CN@latin', None)), + ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', 'utf8')), + ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), + ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', None)), + ]) + def test_setlocale_with_modifier(self, localename, localetuple): + try: + locale.setlocale(locale.LC_CTYPE, localename) + except locale.Error as exc: + self.skipTest(str(exc)) + loc = locale.setlocale(locale.LC_CTYPE, localetuple) + self.assertEqual(loc, localename) + + loctuple = locale.getlocale(locale.LC_CTYPE) + loc = locale.setlocale(locale.LC_CTYPE, loctuple) + self.assertEqual(loc, localename) + + @support.subTests('localename,localetuple', [ + ('fr_FR.iso885915@euro', ('fr_FR@euro', 'ISO8859-15')), + ('fr_FR.ISO8859-15@euro', ('fr_FR@euro', 'ISO8859-15')), + ('fr_FR@euro', ('fr_FR@euro', 'ISO8859-15')), + ('de_DE.iso885915@euro', ('de_DE@euro', 'ISO8859-15')), + ('de_DE.ISO8859-15@euro', ('de_DE@euro', 'ISO8859-15')), + ('de_DE@euro', ('de_DE@euro', 'ISO8859-15')), + ('el_GR.iso88597@euro', ('el_GR@euro', 'ISO8859-7')), + ('el_GR.ISO8859-7@euro', ('el_GR@euro', 'ISO8859-7')), + ('el_GR@euro', ('el_GR@euro', 'ISO8859-7')), + ('ca_ES.iso885915@euro', ('ca_ES@euro', 'ISO8859-15')), + ('ca_ES.ISO8859-15@euro', ('ca_ES@euro', 'ISO8859-15')), + ('ca_ES@euro', ('ca_ES@euro', 'ISO8859-15')), + ('ca_ES.utf8@valencia', ('ca_ES@valencia', 'UTF-8')), + ('ca_ES.UTF-8@valencia', ('ca_ES@valencia', 'UTF-8')), + ('ca_ES@valencia', ('ca_ES@valencia', 'UTF-8')), + ('ks_IN.utf8@devanagari', ('ks_IN@devanagari', 'UTF-8')), + ('ks_IN.UTF-8@devanagari', ('ks_IN@devanagari', 'UTF-8')), + ('ks_IN@devanagari', ('ks_IN@devanagari', 'UTF-8')), + ('sd_IN.utf8@devanagari', ('sd_IN@devanagari', 'UTF-8')), + ('sd_IN.UTF-8@devanagari', ('sd_IN@devanagari', 'UTF-8')), + ('sd_IN@devanagari', ('sd_IN@devanagari', 'UTF-8')), + ('be_BY.utf8@latin', ('be_BY@latin', 'UTF-8')), + ('be_BY.UTF-8@latin', ('be_BY@latin', 'UTF-8')), + ('be_BY@latin', ('be_BY@latin', 'UTF-8')), + ('sr_RS.utf8@latin', ('sr_RS@latin', 'UTF-8')), + ('sr_RS.UTF-8@latin', ('sr_RS@latin', 'UTF-8')), + ('sr_RS@latin', ('sr_RS@latin', 'UTF-8')), + ('ug_CN.utf8@latin', ('ug_CN@latin', 'UTF-8')), + ('ug_CN.UTF-8@latin', ('ug_CN@latin', 'UTF-8')), + ('ug_CN@latin', ('ug_CN@latin', 'UTF-8')), + ('uz_UZ.utf8@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), + ('uz_UZ.UTF-8@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), + ('uz_UZ@cyrillic', ('uz_UZ@cyrillic', 'UTF-8')), + ]) + def test_getlocale_with_modifier(self, localename, localetuple): + try: + locale.setlocale(locale.LC_CTYPE, localename) + except locale.Error as exc: + self.skipTest(str(exc)) + loctuple = locale.getlocale(locale.LC_CTYPE) + self.assertEqual(loctuple, localetuple) + + locale.setlocale(locale.LC_CTYPE, loctuple) + self.assertEqual(locale.getlocale(locale.LC_CTYPE), localetuple) + class TestMiscellaneous(unittest.TestCase): def test_defaults_UTF8(self): diff --git a/Lib/test/test_resource.py b/Lib/test/test_resource.py index fe05224828bd27..7391ce59da0ec4 100644 --- a/Lib/test/test_resource.py +++ b/Lib/test/test_resource.py @@ -40,7 +40,10 @@ def test_fsize_ismax(self): # we need to test that the get/setrlimit functions properly convert # the number to a C long long and that the conversion doesn't raise # an error. + self.assertGreater(resource.RLIM_INFINITY, 0) self.assertEqual(resource.RLIM_INFINITY, max) + self.assertLessEqual(cur, max) + resource.setrlimit(resource.RLIMIT_FSIZE, (max, max)) resource.setrlimit(resource.RLIMIT_FSIZE, (cur, max)) @unittest.skipIf(sys.platform == "vxworks", @@ -113,56 +116,53 @@ def test_fsize_not_too_big(self): self.addCleanup(resource.setrlimit, resource.RLIMIT_FSIZE, (cur, max)) def expected(cur): - if resource.RLIM_INFINITY < 0: - return [(cur, max), (resource.RLIM_INFINITY, max)] - elif resource.RLIM_INFINITY < cur: - return [(resource.RLIM_INFINITY, max)] - else: - return [(cur, max)] + return (min(cur, resource.RLIM_INFINITY), max) resource.setrlimit(resource.RLIMIT_FSIZE, (2**31-5, max)) self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**31-5, max)) + resource.setrlimit(resource.RLIMIT_FSIZE, (2**31, max)) + self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**31)) + resource.setrlimit(resource.RLIMIT_FSIZE, (2**32-5, max)) + self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**32-5)) try: resource.setrlimit(resource.RLIMIT_FSIZE, (2**32, max)) except OverflowError: - resource.setrlimit(resource.RLIMIT_FSIZE, (2**31, max)) - self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**31)) - resource.setrlimit(resource.RLIMIT_FSIZE, (2**32-5, max)) - self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**32-5)) + pass else: - self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**32)) - resource.setrlimit(resource.RLIMIT_FSIZE, (2**31, max)) - self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**31, max)) - resource.setrlimit(resource.RLIMIT_FSIZE, (2**32-5, max)) - self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), (2**32-5, max)) + self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**32)) resource.setrlimit(resource.RLIMIT_FSIZE, (2**63-5, max)) - self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**63-5)) + self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**63-5)) try: resource.setrlimit(resource.RLIMIT_FSIZE, (2**63, max)) except ValueError: # There is a hard limit on macOS. pass else: - self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**63)) + self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**63)) resource.setrlimit(resource.RLIMIT_FSIZE, (2**64-5, max)) - self.assertIn(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**64-5)) + self.assertEqual(resource.getrlimit(resource.RLIMIT_FSIZE), expected(2**64-5)) @unittest.skipIf(sys.platform == "vxworks", "setting RLIMIT_FSIZE is not supported on VxWorks") @unittest.skipUnless(hasattr(resource, 'RLIMIT_FSIZE'), 'requires resource.RLIMIT_FSIZE') def test_fsize_negative(self): + self.assertGreater(resource.RLIM_INFINITY, 0) (cur, max) = resource.getrlimit(resource.RLIMIT_FSIZE) for value in -5, -2**31, -2**32-5, -2**63, -2**64-5, -2**1000: with self.subTest(value=value): - # This test assumes that the values don't map to RLIM_INFINITY, - # though Posix doesn't guarantee it. - self.assertNotEqual(value, resource.RLIM_INFINITY) - self.assertRaises(ValueError, resource.setrlimit, resource.RLIMIT_FSIZE, (value, max)) self.assertRaises(ValueError, resource.setrlimit, resource.RLIMIT_FSIZE, (cur, value)) + if resource.RLIM_INFINITY in (2**32-3, 2**32-1, 2**64-3, 2**64-1): + value = (resource.RLIM_INFINITY & 0xffff) - 0x10000 + with self.assertWarnsRegex(DeprecationWarning, "RLIM_INFINITY"): + resource.setrlimit(resource.RLIMIT_FSIZE, (value, max)) + with self.assertWarnsRegex(DeprecationWarning, "RLIM_INFINITY"): + resource.setrlimit(resource.RLIMIT_FSIZE, (cur, value)) + + @unittest.skipUnless(hasattr(resource, "getrusage"), "needs getrusage") def test_getrusage(self): self.assertRaises(TypeError, resource.getrusage) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-08-05-10-22-15.gh-issue-136966.J5lrE0.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-05-10-22-15.gh-issue-136966.J5lrE0.rst new file mode 100644 index 00000000000000..aafd9ca4db4cd3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-08-05-10-22-15.gh-issue-136966.J5lrE0.rst @@ -0,0 +1,4 @@ +The :attr:`object.__dict__` and :attr:`!__weakref__` descriptors now use a +single descriptor instance per interpreter, shared across all types that +need them. +This speeds up class creation, and helps avoid reference cycles. diff --git a/Misc/NEWS.d/next/Library/2022-01-07-16-56-57.bpo-38735.NFfJX6.rst b/Misc/NEWS.d/next/Library/2022-01-07-16-56-57.bpo-38735.NFfJX6.rst new file mode 100644 index 00000000000000..7f4ea04284e10b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-07-16-56-57.bpo-38735.NFfJX6.rst @@ -0,0 +1,2 @@ +Fix failure when importing a module from the root directory on unix-like +platforms with sys.pycache_prefix set. diff --git a/Misc/NEWS.d/next/Library/2025-08-07-12-32-23.gh-issue-137044.abNoIy.rst b/Misc/NEWS.d/next/Library/2025-08-07-12-32-23.gh-issue-137044.abNoIy.rst new file mode 100644 index 00000000000000..4bbf3075dfcafe --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-07-12-32-23.gh-issue-137044.abNoIy.rst @@ -0,0 +1,4 @@ +:data:`resource.RLIM_INFINITY` is now always a positive integer larger than +any limited resource value. This simplifies comparison of the resource +values. Previously, it could be negative, such as -1 or -3, depending on +platform. diff --git a/Misc/NEWS.d/next/Library/2025-08-14-00-00-12.gh-issue-137729.i9NSKP.rst b/Misc/NEWS.d/next/Library/2025-08-14-00-00-12.gh-issue-137729.i9NSKP.rst new file mode 100644 index 00000000000000..b324a42c7f869e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-14-00-00-12.gh-issue-137729.i9NSKP.rst @@ -0,0 +1,3 @@ +:func:`locale.setlocale` now supports language codes with ``@``-modifiers. +``@``-modifier are no longer silently removed in :func:`locale.getlocale`, +but included in the language code. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 46cedf83df1f00..52ed5a532efd27 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -63,8 +63,9 @@ /*[clinic input] module _decimal class _decimal.Decimal "PyObject *" "&dec_spec" +class _decimal.Context "PyObject *" "&ctx_spec" [clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=e0e1f68f1f413f5f]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=8c3aa7cfde934d7b]*/ struct PyDecContextObject; struct DecCondMap; @@ -1566,17 +1567,26 @@ init_extended_context(PyObject *v) } /* Factory function for creating IEEE interchange format contexts */ + +/*[clinic input] +_decimal.IEEEContext + + bits: Py_ssize_t + / + +Return a context, initialized as one of the IEEE interchange formats. + +The argument must be a multiple of 32 and less than +IEEE_CONTEXT_MAX_BITS. +[clinic start generated code]*/ + static PyObject * -ieee_context(PyObject *module, PyObject *v) +_decimal_IEEEContext_impl(PyObject *module, Py_ssize_t bits) +/*[clinic end generated code: output=19a35f320fe19789 input=5cff864d899eb2d7]*/ { PyObject *context; - mpd_ssize_t bits; mpd_context_t ctx; - bits = PyLong_AsSsize_t(v); - if (bits == -1 && PyErr_Occurred()) { - return NULL; - } if (bits <= 0 || bits > INT_MAX) { goto error; } @@ -1775,7 +1785,7 @@ current_context(decimal_state *modstate) /* Return a new reference to the current context */ static PyObject * -PyDec_GetCurrentContext(PyObject *self, PyObject *Py_UNUSED(dummy)) +PyDec_GetCurrentContext(PyObject *self) { PyObject *context; decimal_state *state = get_module_state(self); @@ -1871,7 +1881,7 @@ current_context(decimal_state *state) /* Return a new reference to the current context */ static PyObject * -PyDec_GetCurrentContext(PyObject *self, PyObject *Py_UNUSED(dummy)) +PyDec_GetCurrentContext(PyObject *self) { decimal_state *state = get_module_state(self); return current_context(state); @@ -1910,36 +1920,73 @@ PyDec_SetCurrentContext(PyObject *self, PyObject *v) } #endif +/*[clinic input] +_decimal.getcontext + +Get the current default context. +[clinic start generated code]*/ + +static PyObject * +_decimal_getcontext_impl(PyObject *module) +/*[clinic end generated code: output=5982062c4d39e3dd input=7ac316fe42a1b6f5]*/ +{ + return PyDec_GetCurrentContext(module); +} + +/*[clinic input] +_decimal.setcontext + + context: object + / + +Set a new default context. +[clinic start generated code]*/ + +static PyObject * +_decimal_setcontext(PyObject *module, PyObject *context) +/*[clinic end generated code: output=8065f870be2852ce input=b57d7ee786b022a6]*/ +{ + return PyDec_SetCurrentContext(module, context); +} + /* Context manager object for the 'with' statement. The manager * owns one reference to the global (outer) context and one * to the local (inner) context. */ + +/*[clinic input] +@text_signature "($module, /, ctx=None, **kwargs)" +_decimal.localcontext + + ctx as local: object = None + * + prec: object = None + rounding: object = None + Emin: object = None + Emax: object = None + capitals: object = None + clamp: object = None + flags: object = None + traps: object = None + +Return a context manager for a copy of the supplied context. + +That will set the default context to a copy of ctx on entry to the +with-statement and restore the previous default context when exiting +the with-statement. If no context is specified, a copy of the current +default context is used. +[clinic start generated code]*/ + static PyObject * -ctxmanager_new(PyObject *m, PyObject *args, PyObject *kwds) +_decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec, + PyObject *rounding, PyObject *Emin, + PyObject *Emax, PyObject *capitals, + PyObject *clamp, PyObject *flags, PyObject *traps) +/*[clinic end generated code: output=9bf4e47742a809b0 input=490307b9689c3856]*/ { - static char *kwlist[] = { - "ctx", "prec", "rounding", - "Emin", "Emax", "capitals", - "clamp", "flags", "traps", - NULL - }; - PyObject *local = Py_None; PyObject *global; - PyObject *prec = Py_None; - PyObject *rounding = Py_None; - PyObject *Emin = Py_None; - PyObject *Emax = Py_None; - PyObject *capitals = Py_None; - PyObject *clamp = Py_None; - PyObject *flags = Py_None; - PyObject *traps = Py_None; - - decimal_state *state = get_module_state(m); + decimal_state *state = get_module_state(module); CURRENT_CONTEXT(state, global); - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOOOOOOO", kwlist, &local, - &prec, &rounding, &Emin, &Emax, &capitals, &clamp, &flags, &traps)) { - return NULL; - } if (local == Py_None) { local = global; } @@ -3569,15 +3616,27 @@ pydec_format(PyObject *dec, PyObject *context, PyObject *fmt, decimal_state *sta } /* Formatted representation of a PyDecObject. */ + +/*[clinic input] +_decimal.Decimal.__format__ + + self as dec: self + format_spec as fmtarg: unicode + override: object = NULL + / + +Formats the Decimal according to format_spec. +[clinic start generated code]*/ + static PyObject * -dec_format(PyObject *dec, PyObject *args) +_decimal_Decimal___format___impl(PyObject *dec, PyObject *fmtarg, + PyObject *override) +/*[clinic end generated code: output=4b3640b7f0c8b6a5 input=e53488e49a0fff00]*/ { PyObject *result = NULL; - PyObject *override = NULL; PyObject *dot = NULL; PyObject *sep = NULL; PyObject *grouping = NULL; - PyObject *fmtarg; PyObject *context; mpd_spec_t spec; char *fmt; @@ -3585,42 +3644,29 @@ dec_format(PyObject *dec, PyObject *args) uint32_t status = 0; int replace_fillchar = 0; Py_ssize_t size; - - decimal_state *state = get_module_state_by_def(Py_TYPE(dec)); CURRENT_CONTEXT(state, context); - if (!PyArg_ParseTuple(args, "O|O", &fmtarg, &override)) { + fmt = (char *)PyUnicode_AsUTF8AndSize(fmtarg, &size); + if (fmt == NULL) { return NULL; } - if (PyUnicode_Check(fmtarg)) { - fmt = (char *)PyUnicode_AsUTF8AndSize(fmtarg, &size); - if (fmt == NULL) { + if (size > 0 && fmt[size-1] == 'N') { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Format specifier 'N' is deprecated", 1) < 0) { return NULL; } + } - if (size > 0 && fmt[size-1] == 'N') { - if (PyErr_WarnEx(PyExc_DeprecationWarning, - "Format specifier 'N' is deprecated", 1) < 0) { - return NULL; - } - } - - if (size > 0 && fmt[0] == '\0') { - /* NUL fill character: must be replaced with a valid UTF-8 char - before calling mpd_parse_fmt_str(). */ - replace_fillchar = 1; - fmt = dec_strdup(fmt, size); - if (fmt == NULL) { - return NULL; - } - fmt[0] = '_'; + if (size > 0 && fmt[0] == '\0') { + /* NUL fill character: must be replaced with a valid UTF-8 char + before calling mpd_parse_fmt_str(). */ + replace_fillchar = 1; + fmt = dec_strdup(fmt, size); + if (fmt == NULL) { + return NULL; } - } - else { - PyErr_SetString(PyExc_TypeError, - "format arg must be str"); - return NULL; + fmt[0] = '_'; } if (!mpd_parse_fmt_str(&spec, fmt, CtxCaps(context))) { @@ -5389,7 +5435,7 @@ static PyMethodDef dec_methods [] = /* Special methods */ { "__copy__", dec_copy, METH_NOARGS, NULL }, { "__deepcopy__", dec_copy, METH_O, NULL }, - { "__format__", dec_format, METH_VARARGS, NULL }, + _DECIMAL_DECIMAL___FORMAT___METHODDEF { "__reduce__", dec_reduce, METH_NOARGS, NULL }, { "__round__", PyDec_Round, METH_VARARGS, NULL }, { "__ceil__", dec_ceil, METH_NOARGS, NULL }, @@ -5686,20 +5732,41 @@ ctx_mpd_qdivmod(PyObject *context, PyObject *args) } /* Binary or ternary arithmetic functions */ + +/*[clinic input] +_decimal.Context.power + + self as context: self + a as base: object + b as exp: object + modulo as mod: object = None + +Compute a**b. + +If 'a' is negative, then 'b' must be integral. The result will be +inexact unless 'a' is integral and the result is finite and can be +expressed exactly in 'precision' digits. In the Python version the +result is always correctly rounded, in the C version the result is +almost always correctly rounded. + +If modulo is given, compute (a**b) % modulo. The following +restrictions hold: + + * all three arguments must be integral + * 'b' must be nonnegative + * at least one of 'a' or 'b' must be nonzero + * modulo must be nonzero and less than 10**prec in absolute value +[clinic start generated code]*/ + static PyObject * -ctx_mpd_qpow(PyObject *context, PyObject *args, PyObject *kwds) +_decimal_Context_power_impl(PyObject *context, PyObject *base, PyObject *exp, + PyObject *mod) +/*[clinic end generated code: output=d2e68694ec545245 input=e9aef844813de243]*/ { - static char *kwlist[] = {"a", "b", "modulo", NULL}; - PyObject *base, *exp, *mod = Py_None; PyObject *a, *b, *c = NULL; PyObject *result; uint32_t status = 0; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|O", kwlist, - &base, &exp, &mod)) { - return NULL; - } - CONVERT_BINOP_RAISE(&a, &b, base, exp, context); if (mod != Py_None) { @@ -6020,7 +6087,7 @@ static PyMethodDef context_methods [] = { "subtract", ctx_mpd_qsub, METH_VARARGS, doc_ctx_subtract }, /* Binary or ternary arithmetic functions */ - { "power", _PyCFunction_CAST(ctx_mpd_qpow), METH_VARARGS|METH_KEYWORDS, doc_ctx_power }, + _DECIMAL_CONTEXT_POWER_METHODDEF /* Ternary arithmetic functions */ { "fma", ctx_mpd_qfma, METH_VARARGS, doc_ctx_fma }, @@ -6117,10 +6184,10 @@ static PyType_Spec context_spec = { static PyMethodDef _decimal_methods [] = { - { "getcontext", PyDec_GetCurrentContext, METH_NOARGS, doc_getcontext}, - { "setcontext", PyDec_SetCurrentContext, METH_O, doc_setcontext}, - { "localcontext", _PyCFunction_CAST(ctxmanager_new), METH_VARARGS|METH_KEYWORDS, doc_localcontext}, - { "IEEEContext", ieee_context, METH_O, doc_ieee_context}, + _DECIMAL_GETCONTEXT_METHODDEF + _DECIMAL_SETCONTEXT_METHODDEF + _DECIMAL_LOCALCONTEXT_METHODDEF + _DECIMAL_IEEECONTEXT_METHODDEF { NULL, NULL, 1, NULL } }; diff --git a/Modules/_decimal/clinic/_decimal.c.h b/Modules/_decimal/clinic/_decimal.c.h index 441515edbf60f6..7a2bcce1e9316b 100644 --- a/Modules/_decimal/clinic/_decimal.c.h +++ b/Modules/_decimal/clinic/_decimal.c.h @@ -6,8 +6,206 @@ preserve # include "pycore_gc.h" // PyGC_Head # include "pycore_runtime.h" // _Py_ID() #endif +#include "pycore_abstract.h" // _PyNumber_Index() #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +PyDoc_STRVAR(_decimal_IEEEContext__doc__, +"IEEEContext($module, bits, /)\n" +"--\n" +"\n" +"Return a context, initialized as one of the IEEE interchange formats.\n" +"\n" +"The argument must be a multiple of 32 and less than\n" +"IEEE_CONTEXT_MAX_BITS."); + +#define _DECIMAL_IEEECONTEXT_METHODDEF \ + {"IEEEContext", (PyCFunction)_decimal_IEEEContext, METH_O, _decimal_IEEEContext__doc__}, + +static PyObject * +_decimal_IEEEContext_impl(PyObject *module, Py_ssize_t bits); + +static PyObject * +_decimal_IEEEContext(PyObject *module, PyObject *arg) +{ + PyObject *return_value = NULL; + Py_ssize_t bits; + + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(arg); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + bits = ival; + } + return_value = _decimal_IEEEContext_impl(module, bits); + +exit: + return return_value; +} + +PyDoc_STRVAR(_decimal_getcontext__doc__, +"getcontext($module, /)\n" +"--\n" +"\n" +"Get the current default context."); + +#define _DECIMAL_GETCONTEXT_METHODDEF \ + {"getcontext", (PyCFunction)_decimal_getcontext, METH_NOARGS, _decimal_getcontext__doc__}, + +static PyObject * +_decimal_getcontext_impl(PyObject *module); + +static PyObject * +_decimal_getcontext(PyObject *module, PyObject *Py_UNUSED(ignored)) +{ + return _decimal_getcontext_impl(module); +} + +PyDoc_STRVAR(_decimal_setcontext__doc__, +"setcontext($module, context, /)\n" +"--\n" +"\n" +"Set a new default context."); + +#define _DECIMAL_SETCONTEXT_METHODDEF \ + {"setcontext", (PyCFunction)_decimal_setcontext, METH_O, _decimal_setcontext__doc__}, + +PyDoc_STRVAR(_decimal_localcontext__doc__, +"localcontext($module, /, ctx=None, **kwargs)\n" +"--\n" +"\n" +"Return a context manager for a copy of the supplied context.\n" +"\n" +"That will set the default context to a copy of ctx on entry to the\n" +"with-statement and restore the previous default context when exiting\n" +"the with-statement. If no context is specified, a copy of the current\n" +"default context is used."); + +#define _DECIMAL_LOCALCONTEXT_METHODDEF \ + {"localcontext", _PyCFunction_CAST(_decimal_localcontext), METH_FASTCALL|METH_KEYWORDS, _decimal_localcontext__doc__}, + +static PyObject * +_decimal_localcontext_impl(PyObject *module, PyObject *local, PyObject *prec, + PyObject *rounding, PyObject *Emin, + PyObject *Emax, PyObject *capitals, + PyObject *clamp, PyObject *flags, PyObject *traps); + +static PyObject * +_decimal_localcontext(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 9 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { &_Py_ID(ctx), &_Py_ID(prec), &_Py_ID(rounding), &_Py_ID(Emin), &_Py_ID(Emax), &_Py_ID(capitals), &_Py_ID(clamp), &_Py_ID(flags), &_Py_ID(traps), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"ctx", "prec", "rounding", "Emin", "Emax", "capitals", "clamp", "flags", "traps", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "localcontext", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[9]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + PyObject *local = Py_None; + PyObject *prec = Py_None; + PyObject *rounding = Py_None; + PyObject *Emin = Py_None; + PyObject *Emax = Py_None; + PyObject *capitals = Py_None; + PyObject *clamp = Py_None; + PyObject *flags = Py_None; + PyObject *traps = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (args[0]) { + local = args[0]; + if (!--noptargs) { + goto skip_optional_pos; + } + } +skip_optional_pos: + if (!noptargs) { + goto skip_optional_kwonly; + } + if (args[1]) { + prec = args[1]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[2]) { + rounding = args[2]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[3]) { + Emin = args[3]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[4]) { + Emax = args[4]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[5]) { + capitals = args[5]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[6]) { + clamp = args[6]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (args[7]) { + flags = args[7]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + traps = args[8]; +skip_optional_kwonly: + return_value = _decimal_localcontext_impl(module, local, prec, rounding, Emin, Emax, capitals, clamp, flags, traps); + +exit: + return return_value; +} + PyDoc_STRVAR(_decimal_Decimal_from_float__doc__, "from_float($type, f, /)\n" "--\n" @@ -71,6 +269,45 @@ _decimal_Decimal_from_number(PyObject *type, PyObject *number) return return_value; } +PyDoc_STRVAR(_decimal_Decimal___format____doc__, +"__format__($self, format_spec, override=, /)\n" +"--\n" +"\n" +"Formats the Decimal according to format_spec."); + +#define _DECIMAL_DECIMAL___FORMAT___METHODDEF \ + {"__format__", _PyCFunction_CAST(_decimal_Decimal___format__), METH_FASTCALL, _decimal_Decimal___format____doc__}, + +static PyObject * +_decimal_Decimal___format___impl(PyObject *dec, PyObject *fmtarg, + PyObject *override); + +static PyObject * +_decimal_Decimal___format__(PyObject *dec, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *fmtarg; + PyObject *override = NULL; + + if (!_PyArg_CheckPositional("__format__", nargs, 1, 2)) { + goto exit; + } + if (!PyUnicode_Check(args[0])) { + _PyArg_BadArgument("__format__", "argument 1", "str", args[0]); + goto exit; + } + fmtarg = args[0]; + if (nargs < 2) { + goto skip_optional; + } + override = args[1]; +skip_optional: + return_value = _decimal_Decimal___format___impl(dec, fmtarg, override); + +exit: + return return_value; +} + PyDoc_STRVAR(_decimal_Decimal_as_integer_ratio__doc__, "as_integer_ratio($self, /)\n" "--\n" @@ -846,4 +1083,86 @@ _decimal_Decimal_quantize(PyObject *self, PyObject *const *args, Py_ssize_t narg exit: return return_value; } -/*[clinic end generated code: output=f33166d1bf53e613 input=a9049054013a1b77]*/ + +PyDoc_STRVAR(_decimal_Context_power__doc__, +"power($self, /, a, b, modulo=None)\n" +"--\n" +"\n" +"Compute a**b.\n" +"\n" +"If \'a\' is negative, then \'b\' must be integral. The result will be\n" +"inexact unless \'a\' is integral and the result is finite and can be\n" +"expressed exactly in \'precision\' digits. In the Python version the\n" +"result is always correctly rounded, in the C version the result is\n" +"almost always correctly rounded.\n" +"\n" +"If modulo is given, compute (a**b) % modulo. The following\n" +"restrictions hold:\n" +"\n" +" * all three arguments must be integral\n" +" * \'b\' must be nonnegative\n" +" * at least one of \'a\' or \'b\' must be nonzero\n" +" * modulo must be nonzero and less than 10**prec in absolute value"); + +#define _DECIMAL_CONTEXT_POWER_METHODDEF \ + {"power", _PyCFunction_CAST(_decimal_Context_power), METH_FASTCALL|METH_KEYWORDS, _decimal_Context_power__doc__}, + +static PyObject * +_decimal_Context_power_impl(PyObject *context, PyObject *base, PyObject *exp, + PyObject *mod); + +static PyObject * +_decimal_Context_power(PyObject *context, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 3 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + Py_hash_t ob_hash; + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_hash = -1, + .ob_item = { _Py_LATIN1_CHR('a'), _Py_LATIN1_CHR('b'), &_Py_ID(modulo), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"a", "b", "modulo", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "power", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[3]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2; + PyObject *base; + PyObject *exp; + PyObject *mod = Py_None; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, + /*minpos*/ 2, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf); + if (!args) { + goto exit; + } + base = args[0]; + exp = args[1]; + if (!noptargs) { + goto skip_optional_pos; + } + mod = args[2]; +skip_optional_pos: + return_value = _decimal_Context_power_impl(context, base, exp, mod); + +exit: + return return_value; +} +/*[clinic end generated code: output=6bb5c926552c2760 input=a9049054013a1b77]*/ diff --git a/Modules/_decimal/docstrings.h b/Modules/_decimal/docstrings.h index bd1c1d5295d717..3ecfd34122ab6b 100644 --- a/Modules/_decimal/docstrings.h +++ b/Modules/_decimal/docstrings.h @@ -19,32 +19,6 @@ PyDoc_STRVAR(doc__decimal, "C decimal arithmetic module"); -PyDoc_STRVAR(doc_getcontext, -"getcontext($module, /)\n--\n\n\ -Get the current default context.\n\ -\n"); - -PyDoc_STRVAR(doc_setcontext, -"setcontext($module, context, /)\n--\n\n\ -Set a new default context.\n\ -\n"); - -PyDoc_STRVAR(doc_localcontext, -"localcontext($module, /, ctx=None, **kwargs)\n--\n\n\ -Return a context manager that will set the default context to a copy of ctx\n\ -on entry to the with-statement and restore the previous default context when\n\ -exiting the with-statement. If no context is specified, a copy of the current\n\ -default context is used.\n\ -\n"); - -PyDoc_STRVAR(doc_ieee_context, -"IEEEContext($module, bits, /)\n--\n\n\ -Return a context object initialized to the proper values for one of the\n\ -IEEE interchange formats. The argument must be a multiple of 32 and less\n\ -than IEEE_CONTEXT_MAX_BITS.\n\ -\n"); - - /******************************************************************************/ /* Decimal Object and Methods */ /******************************************************************************/ @@ -619,24 +593,6 @@ Plus corresponds to the unary prefix plus operator in Python, but applies\n\ the context to the result.\n\ \n"); -PyDoc_STRVAR(doc_ctx_power, -"power($self, /, a, b, modulo=None)\n--\n\n\ -Compute a**b. If 'a' is negative, then 'b' must be integral. The result\n\ -will be inexact unless 'a' is integral and the result is finite and can\n\ -be expressed exactly in 'precision' digits. In the Python version the\n\ -result is always correctly rounded, in the C version the result is almost\n\ -always correctly rounded.\n\ -\n\ -If modulo is given, compute (a**b) % modulo. The following restrictions\n\ -hold:\n\ -\n\ - * all three arguments must be integral\n\ - * 'b' must be nonnegative\n\ - * at least one of 'a' or 'b' must be nonzero\n\ - * modulo must be nonzero and less than 10**prec in absolute value\n\ -\n\ -\n"); - PyDoc_STRVAR(doc_ctx_quantize, "quantize($self, x, y, /)\n--\n\n\ Return a value equal to x (rounded), having the exponent of y.\n\ diff --git a/Modules/resource.c b/Modules/resource.c index 2353bc6653abd8..263730288c3dcf 100644 --- a/Modules/resource.c +++ b/Modules/resource.c @@ -164,7 +164,14 @@ py2rlim(PyObject *obj, rlim_t *out) if (bytes < 0) { return -1; } - else if (neg && (*out != RLIM_INFINITY || bytes > (Py_ssize_t)sizeof(*out))) { + else if (neg && *out == RLIM_INFINITY && bytes <= (Py_ssize_t)sizeof(*out)) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "Use RLIM_INFINITY instead of negative limit value.", 1)) + { + return -1; + } + } + else if (neg) { PyErr_SetString(PyExc_ValueError, "Cannot convert negative int"); return -1; @@ -210,9 +217,6 @@ py2rlimit(PyObject *limits, struct rlimit *rl_out) static PyObject* rlim2py(rlim_t value) { - if (value == RLIM_INFINITY) { - return PyLong_FromNativeBytes(&value, sizeof(value), -1); - } return PyLong_FromUnsignedNativeBytes(&value, sizeof(value), -1); } diff --git a/Objects/descrobject.c b/Objects/descrobject.c index d3d17e92b6d1e8..06a81a4fdbd865 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -39,41 +39,41 @@ descr_name(PyDescrObject *descr) } static PyObject * -descr_repr(PyDescrObject *descr, const char *format) +descr_repr(PyDescrObject *descr, const char *kind) { PyObject *name = NULL; if (descr->d_name != NULL && PyUnicode_Check(descr->d_name)) name = descr->d_name; - return PyUnicode_FromFormat(format, name, "?", descr->d_type->tp_name); + if (descr->d_type == &PyBaseObject_Type) { + return PyUnicode_FromFormat("<%s '%V'>", kind, name, "?"); + } + return PyUnicode_FromFormat("<%s '%V' of '%s' objects>", + kind, name, "?", descr->d_type->tp_name); } static PyObject * method_repr(PyObject *descr) { - return descr_repr((PyDescrObject *)descr, - ""); + return descr_repr((PyDescrObject *)descr, "method"); } static PyObject * member_repr(PyObject *descr) { - return descr_repr((PyDescrObject *)descr, - ""); + return descr_repr((PyDescrObject *)descr, "member"); } static PyObject * getset_repr(PyObject *descr) { - return descr_repr((PyDescrObject *)descr, - ""); + return descr_repr((PyDescrObject *)descr, "attribute"); } static PyObject * wrapperdescr_repr(PyObject *descr) { - return descr_repr((PyDescrObject *)descr, - ""); + return descr_repr((PyDescrObject *)descr, "slot wrapper"); } static int diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fb33bc747d885b..9cead729b6fe7a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4039,26 +4039,15 @@ subtype_getweakref(PyObject *obj, void *context) return Py_NewRef(result); } -/* Three variants on the subtype_getsets list. */ - -static PyGetSetDef subtype_getsets_full[] = { - {"__dict__", subtype_dict, subtype_setdict, - PyDoc_STR("dictionary for instance variables")}, - {"__weakref__", subtype_getweakref, NULL, - PyDoc_STR("list of weak references to the object")}, - {0} -}; - -static PyGetSetDef subtype_getsets_dict_only[] = { - {"__dict__", subtype_dict, subtype_setdict, - PyDoc_STR("dictionary for instance variables")}, - {0} +/* getset definitions for common descriptors */ +static PyGetSetDef subtype_getset_dict = { + "__dict__", subtype_dict, subtype_setdict, + PyDoc_STR("dictionary for instance variables"), }; -static PyGetSetDef subtype_getsets_weakref_only[] = { - {"__weakref__", subtype_getweakref, NULL, - PyDoc_STR("list of weak references to the object")}, - {0} +static PyGetSetDef subtype_getset_weakref = { + "__weakref__", subtype_getweakref, NULL, + PyDoc_STR("list of weak references to the object"), }; static int @@ -4594,10 +4583,36 @@ type_new_classmethod(PyObject *dict, PyObject *attr) return 0; } +/* Add __dict__ or __weakref__ descriptor */ +static int +type_add_common_descriptor(PyInterpreterState *interp, + PyObject **cache, + PyGetSetDef *getset_def, + PyObject *dict) +{ +#ifdef Py_GIL_DISABLED + PyMutex_Lock(&interp->cached_objects.descriptor_mutex); +#endif + PyObject *descr = *cache; + if (!descr) { + descr = PyDescr_NewGetSet(&PyBaseObject_Type, getset_def); + *cache = descr; + } +#ifdef Py_GIL_DISABLED + PyMutex_Unlock(&interp->cached_objects.descriptor_mutex); +#endif + if (!descr) { + return -1; + } + if (PyDict_SetDefaultRef(dict, PyDescr_NAME(descr), descr, NULL) < 0) { + return -1; + } + return 0; +} /* Add descriptors for custom slots from __slots__, or for __dict__ */ static int -type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type) +type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type, PyObject *dict) { PyHeapTypeObject *et = (PyHeapTypeObject *)type; Py_ssize_t slotoffset = ctx->base->tp_basicsize; @@ -4635,6 +4650,30 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type) type->tp_basicsize = slotoffset; type->tp_itemsize = ctx->base->tp_itemsize; type->tp_members = _PyHeapType_GET_MEMBERS(et); + + PyInterpreterState *interp = _PyInterpreterState_GET(); + + if (type->tp_dictoffset) { + if (type_add_common_descriptor( + interp, + &interp->cached_objects.dict_descriptor, + &subtype_getset_dict, + dict) < 0) + { + return -1; + } + } + if (type->tp_weaklistoffset) { + if (type_add_common_descriptor( + interp, + &interp->cached_objects.weakref_descriptor, + &subtype_getset_weakref, + dict) < 0) + { + return -1; + } + } + return 0; } @@ -4642,18 +4681,7 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type) static void type_new_set_slots(const type_new_ctx *ctx, PyTypeObject *type) { - if (type->tp_weaklistoffset && type->tp_dictoffset) { - type->tp_getset = subtype_getsets_full; - } - else if (type->tp_weaklistoffset && !type->tp_dictoffset) { - type->tp_getset = subtype_getsets_weakref_only; - } - else if (!type->tp_weaklistoffset && type->tp_dictoffset) { - type->tp_getset = subtype_getsets_dict_only; - } - else { - type->tp_getset = NULL; - } + type->tp_getset = NULL; /* Special case some slots */ if (type->tp_dictoffset != 0 || ctx->nslot > 0) { @@ -4758,7 +4786,7 @@ type_new_set_attrs(const type_new_ctx *ctx, PyTypeObject *type) return -1; } - if (type_new_descriptors(ctx, type) < 0) { + if (type_new_descriptors(ctx, type, dict) < 0) { return -1; } @@ -6642,6 +6670,14 @@ _PyStaticType_FiniBuiltin(PyInterpreterState *interp, PyTypeObject *type) } +void +_PyTypes_FiniCachedDescriptors(PyInterpreterState *interp) +{ + Py_CLEAR(interp->cached_objects.dict_descriptor); + Py_CLEAR(interp->cached_objects.weakref_descriptor); +} + + static void type_dealloc(PyObject *self) { diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index 09ce77fd12608f..a47e4d11b54441 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -1793,37 +1793,6 @@ sys__baserepl(PyObject *module, PyObject *Py_UNUSED(ignored)) return sys__baserepl_impl(module); } -PyDoc_STRVAR(sys__clear_type_descriptors__doc__, -"_clear_type_descriptors($module, type, /)\n" -"--\n" -"\n" -"Private function for clearing certain descriptors from a type\'s dictionary.\n" -"\n" -"See gh-135228 for context."); - -#define SYS__CLEAR_TYPE_DESCRIPTORS_METHODDEF \ - {"_clear_type_descriptors", (PyCFunction)sys__clear_type_descriptors, METH_O, sys__clear_type_descriptors__doc__}, - -static PyObject * -sys__clear_type_descriptors_impl(PyObject *module, PyObject *type); - -static PyObject * -sys__clear_type_descriptors(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - PyObject *type; - - if (!PyObject_TypeCheck(arg, &PyType_Type)) { - _PyArg_BadArgument("_clear_type_descriptors", "argument", (&PyType_Type)->tp_name, arg); - goto exit; - } - type = arg; - return_value = sys__clear_type_descriptors_impl(module, type); - -exit: - return return_value; -} - PyDoc_STRVAR(sys__is_gil_enabled__doc__, "_is_gil_enabled($module, /)\n" "--\n" @@ -1979,4 +1948,4 @@ _jit_is_active(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=9052f399f40ca32d input=a9049054013a1b77]*/ +/*[clinic end generated code: output=449d16326e69dcf6 input=a9049054013a1b77]*/ diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index e22a9cc1c75050..b6b1d2845ec2f1 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1906,6 +1906,7 @@ finalize_interp_clear(PyThreadState *tstate) _PyXI_Fini(tstate->interp); _PyExc_ClearExceptionGroupType(tstate->interp); _Py_clear_generic_types(tstate->interp); + _PyTypes_FiniCachedDescriptors(tstate->interp); /* Clear interpreter state and all thread states */ _PyInterpreterState_Clear(tstate); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 19912b4a4c6198..bedbdfc489872e 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2644,46 +2644,6 @@ sys__baserepl_impl(PyObject *module) Py_RETURN_NONE; } -/*[clinic input] -sys._clear_type_descriptors - - type: object(subclass_of='&PyType_Type') - / - -Private function for clearing certain descriptors from a type's dictionary. - -See gh-135228 for context. -[clinic start generated code]*/ - -static PyObject * -sys__clear_type_descriptors_impl(PyObject *module, PyObject *type) -/*[clinic end generated code: output=5ad17851b762b6d9 input=dc536c97fde07251]*/ -{ - PyTypeObject *typeobj = (PyTypeObject *)type; - if (_PyType_HasFeature(typeobj, Py_TPFLAGS_IMMUTABLETYPE)) { - PyErr_SetString(PyExc_TypeError, "argument is immutable"); - return NULL; - } - PyObject *dict = _PyType_GetDict(typeobj); - PyObject *dunder_dict = NULL; - if (PyDict_Pop(dict, &_Py_ID(__dict__), &dunder_dict) < 0) { - return NULL; - } - PyObject *dunder_weakref = NULL; - if (PyDict_Pop(dict, &_Py_ID(__weakref__), &dunder_weakref) < 0) { - PyType_Modified(typeobj); - Py_XDECREF(dunder_dict); - return NULL; - } - PyType_Modified(typeobj); - // We try to hold onto a reference to these until after we call - // PyType_Modified(), in case their deallocation triggers somer user code - // that tries to do something to the type. - Py_XDECREF(dunder_dict); - Py_XDECREF(dunder_weakref); - Py_RETURN_NONE; -} - /*[clinic input] sys._is_gil_enabled -> bool @@ -2881,7 +2841,6 @@ static PyMethodDef sys_methods[] = { SYS__STATS_DUMP_METHODDEF #endif SYS__GET_CPU_COUNT_CONFIG_METHODDEF - SYS__CLEAR_TYPE_DESCRIPTORS_METHODDEF SYS__IS_GIL_ENABLED_METHODDEF SYS__DUMP_TRACELETS_METHODDEF {NULL, NULL} // sentinel diff --git a/Tools/c-analyzer/cpython/_analyzer.py b/Tools/c-analyzer/cpython/_analyzer.py index 6204353e9bd26a..6f0f464892845f 100644 --- a/Tools/c-analyzer/cpython/_analyzer.py +++ b/Tools/c-analyzer/cpython/_analyzer.py @@ -67,6 +67,7 @@ 'PyMethodDef', 'PyMethodDef[]', 'PyMemberDef[]', + 'PyGetSetDef', 'PyGetSetDef[]', 'PyNumberMethods', 'PySequenceMethods', diff --git a/Tools/i18n/makelocalealias.py b/Tools/i18n/makelocalealias.py index 02af1caff7d499..7f001abc09745d 100755 --- a/Tools/i18n/makelocalealias.py +++ b/Tools/i18n/makelocalealias.py @@ -44,6 +44,13 @@ def parse(filename): # Ignore one letter locale mappings (except for 'c') if len(locale) == 1 and locale != 'c': continue + if '@' in locale and '@' not in alias: + # Do not simply remove the "@euro" modifier. + # Glibc generates separate locales with the "@euro" modifier, and + # not always generates a locale without it with the same encoding. + # It can also affect collation. + if locale.endswith('@euro') and not locale.endswith('.utf-8@euro'): + alias += '@euro' # Normalize encoding, if given if '.' in locale: lang, encoding = locale.split('.')[:2] @@ -51,6 +58,10 @@ def parse(filename): encoding = encoding.replace('_', '') locale = lang + '.' + encoding data[locale] = alias + # Conflict with glibc. + data.pop('el_gr@euro', None) + data.pop('uz_uz@cyrillic', None) + data.pop('uz_uz.utf8@cyrillic', None) return data def parse_glibc_supported(filename): @@ -81,7 +92,7 @@ def parse_glibc_supported(filename): # Add an encoding to alias alias, _, modifier = alias.partition('@') alias = _locale._replace_encoding(alias, alias_encoding) - if modifier and not (modifier == 'euro' and alias_encoding == 'ISO-8859-15'): + if modifier: alias += '@' + modifier data[locale] = alias return data