PEP: 538 Title: Coercing the legacy C locale to a UTF-8 based locale
Version: $Revision$ Last-Modified: $Date$ Author: Nick Coghlan
<ncoghlan@gmail.com> BDFL-Delegate: INADA Naoki Status: Final Type:
Standards Track Content-Type: text/x-rst Created: 28-Dec-2016
Python-Version: 3.7 Post-History: 03-Jan-2017 (linux-sig), 07-Jan-2017
(python-ideas), 05-Mar-2017 (python-dev), 09-May-2017 (python-dev)
Resolution:
https://mail.python.org/pipermail/python-dev/2017-May/148035.html

Abstract
========

An ongoing challenge with Python 3 on \*nix systems is the conflict
between needing to use the configured locale encoding by default for
consistency with other locale-aware components in the same process or
subprocesses, and the fact that the standard C locale (as defined in
POSIX:2001) typically implies a default text encoding of ASCII, which is
entirely inadequate for the development of networked services and client
applications in a multilingual world.

PEP 540 proposes a change to CPython's handling of the legacy C locale
such that CPython will assume the use of UTF-8 in such environments,
rather than persisting with the demonstrably problematic assumption of
ASCII as an appropriate encoding for communicating with operating system
interfaces. This is a good approach for cases where network encoding
interoperability is a more important concern than local encoding
interoperability.

However, it comes at the cost of making CPython's encoding assumptions
diverge from those of other locale-aware components in the same process,
as well as those of components running in subprocesses that share the
same environment.

This can cause interoperability problems with some extension modules
(such as GNU readline's command line history editing), as well as with
components running in subprocesses (such as older Python runtimes).

It also requires non-trivial changes to the internals of how CPython
itself works, rather than relying primarily on existing configuration
settings that are supported by Python versions prior to Python 3.7.

Accordingly, this PEP proposes that independently of the UTF-8 mode
proposed in PEP 540, the way the CPython implementation handles the
default C locale be changed to be roughly equivalent to the following
existing configuration settings (supported since Python 3.1)::

    LC_CTYPE=C.UTF-8
    PYTHONIOENCODING=utf-8:surrogateescape

The exact target locale for coercion will be chosen from a predefined
list at runtime based on the actually available locales.

The reinterpreted locale settings will be written back to the
environment so they're visible to other components in the same process
and in subprocesses, but the changed `PYTHONIOENCODING` default will be
made implicit in order to avoid causing compatibility problems with
Python 2 subprocesses that don't provide the `surrogateescape` error
handler.

The new legacy locale coercion behavior can be disabled either by
setting `LC_ALL` (which may still lead to a Unicode compatibility
warning) or by setting the new `PYTHONCOERCECLOCALE` environment
variable to `0`.

With this change, any \*nix platform that does *not* offer at least one
of the `C.UTF-8`, `C.utf8` or `UTF-8` locales as part of its standard
configuration would only be considered a fully supported platform for
CPython 3.7+ deployments when a suitable locale other than the default
`C` locale is configured explicitly (e.g. `en_AU.UTF-8`,
`zh_CN.gb18030`). If PEP 540 is accepted in addition to this PEP, then
pure Python modules would also be supported when using the proposed
`PYTHONUTF8` mode, but expectations for full Unicode compatibility in
extension modules would continue to be limited to the platforms covered
by this PEP.

As it only reflects a change in default settings rather than a
fundamentally new capability, redistributors (such as Linux
distributions) with a narrower target audience than the upstream CPython
development team may also choose to opt in to this locale coercion
behaviour for the Python 3.6.x series by applying the necessary changes
as a downstream patch.

Implementation Notes
====================

Attempting to implement the PEP as originally accepted showed that the
proposal to emit locale coercion and compatibility warnings by default
simply wasn't practical (there were too many cases where previously
working code failed *because of the warnings*, rather than because of
latent locale handling defects in the affected code).

As a result, the `PY_WARN_ON_C_LOCALE` config flag was removed, and
replaced with a runtime `PYTHONCOERCECLOCALE=warn` environment variable
setting that allows developers and system integrators to opt-in to
receiving locale coercion and compatibility warnings, without emitting
them by default.

The output examples in the PEP itself have also been updated to remove
the warnings and make them easier to read.

Background
==========

While the CPython interpreter is starting up, it may need to convert
from the `char *` format to the `wchar_t *` format, or from one of those
formats to `PyUnicodeObject *`, in a way that's consistent with the
locale settings of the overall system. It handles these cases by relying
on the operating system to do the conversion and then ensuring that the
text encoding name reported by `sys.getfilesystemencoding()` matches the
encoding used during this early bootstrapping process.

On Windows, the limitations of the `mbcs` format used by default in
these conversions proved sufficiently problematic that PEP 528 and PEP
529 were implemented to bypass the operating system supplied interfaces
for binary data handling and force the use of UTF-8 instead.

On Mac OS X, iOS, and Android, many components, including CPython,
already assume the use of UTF-8 as the system encoding, regardless of
the locale setting. However, this isn't the case for all components, and
the discrepancy can cause problems in some situations (for example, when
using the GNU readline module \[16\_\]).

On non-Apple and non-Android \*nix systems, these operations are handled
using the C locale system in glibc, which has the following
characteristics \[4\_\]:

-   by default, all processes start in the `C` locale, which uses
    `ASCII` for these conversions. This is almost never what anyone
    doing multilingual text processing actually wants (including CPython
    and C/C++ GUI frameworks).
-   calling `setlocale(LC_ALL, "")` reconfigures the active locale based
    on the locale categories configured in the current process
    environment
-   if the locale requested by the current environment is unknown, or no
    specific locale is configured, then the default `C` locale will
    remain active

The specific locale category that covers the APIs that CPython depends
on is `LC_CTYPE`, which applies to "classification and conversion of
characters, and to multibyte and wide characters" \[5\_\]. Accordingly,
CPython includes the following key calls to `setlocale`:

-   in the main `python` binary, CPython calls `setlocale(LC_ALL, "")`
    to configure the entire C locale subsystem according to the process
    environment. It does this prior to making any calls into the shared
    CPython library
-   in `Py_Initialize`, CPython calls `setlocale(LC_CTYPE, "")`, such
    that the configured locale settings for that category *always* match
    those set in the environment. It does this unconditionally, and it
    *doesn't* revert the process state change in `Py_Finalize`

(This summary of the locale handling omits several technical details
related to exactly where and when the text encoding declared as part of
the locale settings is used - see PEP 540 for further discussion, as
these particular details matter more when decoupling CPython from the
declared C locale than they do when overriding the locale with one based
on UTF-8)

These calls are usually sufficient to provide sensible behaviour, but
they can still fail in the following cases:

-   SSH environment forwarding means that SSH clients may sometimes
    forward client locale settings to servers that don't have that
    locale installed. This leads to CPython running in the default
    ASCII-based C locale
-   some process environments (such as Linux containers) may not have
    any explicit locale configured at all. As with unknown locales, this
    leads to CPython running in the default ASCII-based C locale
-   on Android, rather than configuring the locale based on environment
    variables, the empty locale `""` is treated as specifically
    requesting the `"C"` locale

The simplest way to deal with this problem for currently released
versions of CPython is to explicitly set a more sensible locale when
launching the application. For example::

    LC_CTYPE=C.UTF-8 python3 ...

The `C.UTF-8` locale is a full locale definition that uses `UTF-8` for
the `LC_CTYPE` category, and the same settings as the `C` locale for all
other categories (including `LC_COLLATE`). It is offered by a number of
Linux distributions (including Debian, Ubuntu, Fedora, Alpine and
Android) as an alternative to the ASCII-based C locale. Some other
platforms (such as `HP-UX`) offer an equivalent locale definition under
the name `C.utf8`.

Mac OS X and other \*BSD systems have taken a different approach:
instead of offering a `C.UTF-8` locale, they offer a partial `UTF-8`
locale that only defines the `LC_CTYPE` category. On such systems, the
preferred environmental locale adjustment is to set `LC_CTYPE=UTF-8`
rather than to set `LC_ALL` or `LANG`. \[17\_\]

In the specific case of Docker containers and similar technologies, the
appropriate locale setting can be specified directly in the container
image definition.

Another common failure case is developers specifying `LANG=C` in order
to see otherwise translated user interface messages in English, rather
than the more narrowly scoped `LC_MESSAGES=C` or `LANGUAGE=en`.

Relationship with other PEPs
============================

This PEP shares a common problem statement with PEP 540 (improving
Python 3's behaviour in the default C locale), but diverges markedly in
the proposed solution:

-   PEP 540 proposes to entirely decouple CPython's default text
    encoding from the C locale system in that case, allowing text
    handling inconsistencies to arise between CPython and other
    locale-aware components running in the same process and in
    subprocesses. This approach aims to make CPython behave less like a
    locale-aware application, and more like locale-independent language
    runtimes like those for Go, Node.js (V8), and Rust
-   this PEP proposes to override the legacy C locale with a more
    recently defined locale that uses UTF-8 as its default text
    encoding. This means that the text encoding override will apply not
    only to CPython, but also to any locale-aware extension modules
    loaded into the current process, as well as to locale-aware
    applications invoked in subprocesses that inherit their environment
    from the parent process. This approach aims to retain CPython's
    traditional strong support for integration with other locale-aware
    components while also actively helping to push forward the adoption
    and standardisation of the C.UTF-8 locale as a Unicode-aware
    replacement for the legacy C locale in the wider C/C++ ecosystem

After reviewing both PEPs, it became clear that they didn't actually
conflict at a technical level, and the proposal in PEP 540 offered a
superior option in cases where no suitable locale was available, as well
as offering a better reference behaviour for platforms where the notion
of a "locale encoding" doesn't make sense (for example, embedded systems
running MicroPython rather than the CPython reference interpreter).

Meanwhile, this PEP offered improved compatibility with other
locale-aware components, and an approach more amenable to being
backported to Python 3.6 by downstream redistributors.

As a result, this PEP was amended to refer to PEP 540 as a complementary
solution that offered improved behaviour when none of the standard UTF-8
based locales were available, as well as extending the changes in the
default settings to APIs that aren't currently independently
configurable (such as the default encoding and error handler for
`open()`).

The availability of PEP 540 also meant that the `LC_CTYPE=en_US.UTF-8`
legacy fallback was removed from the list of UTF-8 locales tried as a
coercion target, with the expectation being that CPython will instead
rely solely on the proposed PYTHONUTF8 mode in such cases.

Motivation
==========

While Linux container technologies like Docker, Kubernetes, and
OpenShift are best known for their use in web service development, the
related container formats and execution models are also being adopted
for Linux command line application development. Technologies like Gnome
Flatpak \[7\_\] and Ubuntu Snappy \[8\_\] further aim to bring these
same techniques to Linux GUI application development.

When using Python 3 for application development in these contexts, it
isn't uncommon to see text encoding related errors akin to the
following::

    $ docker run --rm fedora:25 python3 -c 'print("ℙƴ☂ℌøἤ")'
    Unable to decode the command from the command line:
    UnicodeEncodeError: 'utf-8' codec can't encode character '\udce2' in position 7: surrogates not allowed
    $ docker run --rm ncoghlan/debian-python python3 -c 'print("ℙƴ☂ℌøἤ")'
    Unable to decode the command from the command line:
    UnicodeEncodeError: 'utf-8' codec can't encode character '\udce2' in position 7: surrogates not allowed

Even though the same command is likely to work fine when run locally::

    $ python3 -c 'print("ℙƴ☂ℌøἤ")'
    ℙƴ☂ℌøἤ

The source of the problem can be seen by instead running the `locale`
command in the three environments::

    $ locale | grep -E 'LC_ALL|LC_CTYPE|LANG'
    LANG=en_AU.UTF-8
    LC_CTYPE="en_AU.UTF-8"
    LC_ALL=
    $ docker run --rm fedora:25 locale | grep -E 'LC_ALL|LC_CTYPE|LANG'
    LANG=
    LC_CTYPE="POSIX"
    LC_ALL=
    $ docker run --rm ncoghlan/debian-python locale | grep -E 'LC_ALL|LC_CTYPE|LANG'
    LANG=
    LANGUAGE=
    LC_CTYPE="POSIX"
    LC_ALL=

In this particular example, we can see that the host system locale is
set to "en\_AU.UTF-8", so CPython uses UTF-8 as the default text
encoding. By contrast, the base Docker images for Fedora and Debian
don't have any specific locale set, so they use the POSIX locale by
default, which is an alias for the ASCII-based default C locale.

The simplest way to get Python 3 (regardless of the exact version) to
behave sensibly in Fedora and Debian based containers is to run it in
the `C.UTF-8` locale that both distros provide::

    $ docker run --rm -e LC_CTYPE=C.UTF-8 fedora:25 python3 -c 'print("ℙƴ☂ℌøἤ")'
    ℙƴ☂ℌøἤ
    $ docker run --rm -e LC_CTYPE=C.UTF-8 ncoghlan/debian-python python3 -c 'print("ℙƴ☂ℌøἤ")'
    ℙƴ☂ℌøἤ

    $ docker run --rm -e LC_CTYPE=C.UTF-8 fedora:25 locale | grep -E 'LC_ALL|LC_CTYPE|LANG'
    LANG=
    LC_CTYPE=C.UTF-8
    LC_ALL=
    $ docker run --rm -e LC_CTYPE=C.UTF-8 ncoghlan/debian-python locale | grep -E 'LC_ALL|LC_CTYPE|LANG'
    LANG=
    LANGUAGE=
    LC_CTYPE=C.UTF-8
    LC_ALL=

The Alpine Linux based Python images provided by Docker, Inc. already
use the C.UTF-8 locale by default::

    $ docker run --rm python:3 python3 -c 'print("ℙƴ☂ℌøἤ")'
    ℙƴ☂ℌøἤ
    $ docker run --rm python:3 locale | grep -E 'LC_ALL|LC_CTYPE|LANG'
    LANG=C.UTF-8
    LANGUAGE=
    LC_CTYPE="C.UTF-8"
    LC_ALL=

Similarly, for custom container images (i.e. those adding additional
content on top of a base distro image), a more suitable locale can be
set in the image definition so everything just works by default.
However, it would provide a much nicer and more consistent user
experience if CPython were able to just deal with this problem
automatically rather than relying on redistributors or end users to
handle it through system configuration changes.

While the glibc developers are working towards making the C.UTF-8 locale
universally available for use by glibc based applications like CPython
\[6\_\], this unfortunately doesn't help on platforms that ship older
versions of glibc without that feature, and also don't provide C.UTF-8
(or an equivalent) as an on-disk locale the way Debian and Fedora do.
These platforms are considered out of scope for this PEP - see PEP 540
for further discussion of possible options for improving CPython's
default behaviour in such environments.

Design Principles
=================

The above motivation leads to the following core design principles for
the proposed solution:

-   if a locale other than the default C locale is explicitly
    configured, we'll continue to respect it
-   as far as is feasible, any changes made will use *existing*
    configuration options
-   Python's runtime behaviour in potential coercion target locales
    should be identical regardless of whether the locale was set
    explicitly in the environment or implicitly as a locale coercion
    target
-   for Python 3.7, if we're changing the locale setting without an
    explicit config option, we'll emit a warning on stderr that we're
    doing so rather than silently changing the process configuration.
    This will alert application and system integrators to the change,
    even if they don't closely follow the PEP process or Python release
    announcements. However, to minimize the chance of introducing new
    problems for end users, we'll do this *without* using the warnings
    system, so even running with `-Werror` won't turn it into a runtime
    exception. (Note: these warnings ended up being silenced by default.
    See the Implementation Note above for more details)
-   for Python 3.7, any changed defaults will offer some form of
    explicit "off" switch at build time, runtime, or both

Minimizing the negative impact on systems currently correctly configured
to use GB-18030 or another partially ASCII compatible universal encoding
leads to the following design principle:

-   if a UTF-8 based Linux container is run on a host that is explicitly
    configured to use a non-UTF-8 encoding, and tries to exchange
    locally encoded data with that host rather than exchanging
    explicitly UTF-8 encoded data, CPython will endeavour to correctly
    round-trip host provided data that is concatenated or split solely
    at common ASCII compatible code points, but may otherwise emit
    nonsensical results.

Minimizing the negative impact on systems and programs correctly
configured to use an explicit locale category like `LC_TIME`,
`LC_MONETARY` or `LC_NUMERIC` while otherwise running in the legacy C
locale gives the following design principles:

-   don't make any environmental changes that would alter any existing
    settings for locale categories other than `LC_CTYPE` (most notably:
    don't set `LC_ALL` or `LANG`)

Finally, maintaining compatibility with running arbitrary subprocesses
in orchestration use cases leads to the following design principle:

-   don't make any Python-specific environmental changes that might be
    incompatible with any still supported version of CPython (including
    CPython 2.7)

Specification
=============

To better handle the cases where CPython would otherwise end up
attempting to operate in the `C` locale, this PEP proposes that CPython
automatically attempt to coerce the legacy `C` locale to a UTF-8 based
locale for the `LC_CTYPE` category when it is run as a standalone
command line application.

It further proposes to emit a warning on stderr if the legacy `C` locale
is in effect for the `LC_CTYPE` category at the point where the language
runtime itself is initialized, and the explicit environmental flag to
disable locale coercion is not set, in order to warn system and
application integrators that they're running CPython in an unsupported
configuration.

In addition to these general changes, some additional Android-specific
changes are proposed to handle the differences in the behaviour of
`setlocale` on that platform.

Legacy C locale coercion in the standalone Python interpreter binary
--------------------------------------------------------------------

When run as a standalone application, CPython has the opportunity to
reconfigure the C locale before any locale dependent operations are
executed in the process.

This means that it can change the locale settings not only for the
CPython runtime, but also for any other locale-aware components running
in the current process (e.g. as part of extension modules), as well as
in subprocesses that inherit their environment from the current process.

After calling `setlocale(LC_ALL, "")` to initialize the locale settings
in the current process, the main interpreter binary will be updated to
include the following call::

    const char *ctype_loc = setlocale(LC_CTYPE, NULL);

This cryptic invocation is the API that C provides to query the current
locale setting without changing it. Given that query, it is possible to
check for exactly the `C` locale with `strcmp`::

    ctype_loc != NULL && strcmp(ctype_loc, "C") == 0 # true only in the C locale

This call also returns `"C"` when either no particular locale is set, or
the nominal locale is set to an alias for the `C` locale (such as
`POSIX`).

Given this information, CPython can then attempt to coerce the locale to
one that uses UTF-8 rather than ASCII as the default encoding.

Three such locales will be tried:

-   `C.UTF-8` (available at least in Debian, Ubuntu, Alpine, and Fedora
    25+, and expected to be available by default in a future version of
    glibc)
-   `C.utf8` (available at least in HP-UX)
-   `UTF-8` (available in at least some \*BSD variants, including Mac
    OS X)

The coercion will be implemented by setting the `LC_CTYPE` environment
variable to the candidate locale name, such that future calls to
`setlocale()` will see it, as will other components looking for those
settings (such as GUI development frameworks and Python's own `locale`
module).

To allow for better cross-platform binary portability and to adjust
automatically to future changes in locale availability, these checks
will be implemented at runtime on all platforms other than Windows,
rather than attempting to determine which locales to try at compile
time.

When this locale coercion is activated, the following warning will be
printed on stderr, with the warning containing whichever locale was
successfully configured::

    Python detected LC_CTYPE=C: LC_CTYPE coerced to C.UTF-8 (set another
    locale or PYTHONCOERCECLOCALE=0 to disable this locale coercion behaviour).

(Note: this warning ended up being silenced by default. See the
Implementation Note above for more details)

As long as the current platform provides at least one of the candidate
UTF-8 based environments, this locale coercion will mean that the
standard Python binary *and* locale-aware extensions should once again
"just work" in the three main failure cases we're aware of (missing
locale settings, SSH forwarding of unknown locales via `LANG` or
`LC_CTYPE`, and developers explicitly requesting `LANG=C`).

The one case where failures may still occur is when `stderr` is
specifically being checked for no output, which can be resolved either
by configuring a locale other than the C locale, or else by using a
mechanism other than "there was no output on stderr" to check for
subprocess errors (e.g. checking process return codes).

If none of the candidate locales are successfully configured, or the
`LC_ALL`, locale override is defined in the current process environment,
then initialization will continue in the C locale and the Unicode
compatibility warning described in the next section will be emitted just
as it would for any other application.

If `PYTHONCOERCECLOCALE=0` is explicitly set, initialization will
continue in the C locale and the Unicode compatibility warning described
in the next section will be automatically suppressed.

The interpreter will always check for the `PYTHONCOERCECLOCALE`
environment variable at startup (even when running under the `-E` or
`-I` switches), as the locale coercion check necessarily takes place
before any command line argument processing. For consistency, the
runtime check to determine whether or not to suppress the locale
compatibility warning will be similarly independent of these settings.

Legacy C locale warning during runtime initialization
-----------------------------------------------------

By the time that `Py_Initialize` is called, arbitrary locale-dependent
operations may have taken place in the current process. This means that
by the time it is called, it is *too late* to reliably switch to a
different locale - doing so would introduce inconsistencies in decoded
text, even in the context of the standalone Python interpreter binary.

Accordingly, when `Py_Initialize` is called and CPython detects that the
configured locale is still the default `C` locale and
`PYTHONCOERCECLOCALE=0` is not set, the following warning will be
issued::

Python runtime initialized with LC\_CTYPE=C (a locale with default ASCII
encoding), which may cause Unicode compatibility problems. Using
C.UTF-8, C.utf8, or UTF-8 (if available) as alternative
Unicode-compatible locales is recommended.

(Note: this warning ended up being silenced by default. See the
Implementation Note above for more details)

In this case, no actual change will be made to the locale settings.

Instead, the warning informs both system and application integrators
that they're running Python 3 in a configuration that we don't expect to
work properly.

The second sentence providing recommendations may eventually be
conditionally compiled based on the operating system (e.g. recommending
`LC_CTYPE=UTF-8` on \*BSD systems), but the initial implementation will
just use the common generic message shown above.

New build-time configuration options
------------------------------------

While both of the above behaviours would be enabled by default, they
would also have new associated configuration options and preprocessor
definitions for the benefit of redistributors that want to override
those default settings.

The locale coercion behaviour would be controlled by the flag
`--with[out]-c-locale-coercion`, which would set the
`PY_COERCE_C_LOCALE` preprocessor definition.

The locale warning behaviour would be controlled by the flag
`--with[out]-c-locale-warning`, which would set the
`PY_WARN_ON_C_LOCALE` preprocessor definition.

(Note: this compile time warning option ended up being replaced by a
runtime `PYTHONCOERCECLOCALE=warn` option. See the Implementation Note
above for more details)

On platforms which don't use the `autotools` based build system (i.e.
Windows) these preprocessor variables would always be undefined.

Changes to the default error handling on the standard streams
-------------------------------------------------------------

Since Python 3.5, CPython has defaulted to using `surrogateescape` on
the standard streams (`sys.stdin`, `sys.stdout`) when it detects that
the current locale is `C` and no specific error handled has been set
using either the `PYTHONIOENCODING` environment variable or the
`Py_setStandardStreamEncoding` API. For other locales, the default error
handler for the standard streams is `strict`.

In order to preserve this behaviour without introducing any behavioural
discrepancies between locale coercion and explicitly configuring a
locale, the coercion target locales (`C.UTF-8`, `C.utf8`, and `UTF-8`)
will be added to the list of locales that use `surrogateescape` as their
default error handler for the standard streams.

No changes are proposed to the default error handler for `sys.stderr`:
that will continue to be `backslashreplace`.

Changes to locale settings on Android
-------------------------------------

Independently of the other changes in this PEP, CPython on Android
systems will be updated to call `setlocale(LC_ALL, "C.UTF-8")` where it
currently calls `setlocale(LC_ALL, "")` and
`setlocale(LC_CTYPE, "C.UTF-8")` where it currently calls
`setlocale(LC_CTYPE, "")`.

This Android-specific behaviour is being introduced due to the following
Android-specific details:

-   on Android, passing `""` to `setlocale` is equivalent to passing
    `"C"`
-   the `C.UTF-8` locale is always available

Platform Support Changes
========================

A new "Legacy C Locale" section will be added to PEP 11 that states:

-   as of CPython 3.7, \*nix platforms are expected to provide at least
    one of `C.UTF-8` (full locale), `C.utf8` (full locale) or `UTF-8` (
    `LC_CTYPE`-only locale) as an alternative to the legacy `C` locale.
    Any Unicode related integration problems that occur only in the
    legacy `C` locale and cannot be reproduced in an appropriately
    configured non-ASCII locale will be closed as "won't fix".

Rationale
=========

Improving the handling of the C locale
--------------------------------------

It has been clear for some time that the C locale's default encoding of
`ASCII` is entirely the wrong choice for development of modern networked
services. Newer languages like Rust and Go have eschewed that default
entirely, and instead made it a deployment requirement that systems be
configured to use UTF-8 as the text encoding for operating system
interfaces. Similarly, Node.js assumes UTF-8 by default (a behaviour
inherited from the V8 JavaScript engine) and requires custom build
settings to indicate it should use the system locale settings for
locale-aware operations. Both the JVM and the .NET CLR use UTF-16-LE as
their primary encoding for passing text between applications and the
application runtime (i.e. the JVM/CLR, not the host operating system).

The challenge for CPython has been the fact that in addition to being
used for network service development, it is also extensively used as an
embedded scripting language in larger applications, and as a desktop
application development language, where it is more important to be
consistent with other locale-aware components sharing the same process,
as well as with the user's desktop locale settings, than it is with the
emergent conventions of modern network service development.

The core premise of this PEP is that for *all* of these use cases, the
assumption of ASCII implied by the default "C" locale is the wrong
choice, and furthermore that the following assumptions are valid:

-   in desktop application use cases, the process locale will *already*
    be configured appropriately, and if it isn't, then that is an
    operating system or embedding application level problem that needs
    to be reported to and resolved by the operating system provider or
    application developer
-   in network service development use cases (especially those based on
    Linux containers), the process locale may not be configured *at
    all*, and if it isn't, then the expectation is that components will
    impose their own default encoding the way Rust, Go and Node.js do,
    rather than trusting the legacy C default encoding of ASCII the way
    CPython currently does

Defaulting to "surrogateescape" error handling on the standard IO streams
-------------------------------------------------------------------------

By coercing the locale away from the legacy C default and its assumption
of ASCII as the preferred text encoding, this PEP also disables the
implicit use of the "surrogateescape" error handler on the standard IO
streams that was introduced in Python 3.5 (\[15\_\]), as well as the
automatic use of `surrogateescape` when operating in PEP 540's proposed
UTF-8 mode.

Rather than introducing yet another configuration option to adjust that
behaviour, this PEP instead proposes to extend the "surrogateescape"
default for `stdin` and `stderr` error handling to also apply to the
three potential coercion target locales.

The aim of this behaviour is to attempt to ensure that operating system
provided text values are typically able to be transparently passed
through a Python 3 application even if it is incorrect in assuming that
that text has been encoded as UTF-8.

In particular, GB 18030 \[12\_\] is a Chinese national text encoding
standard that handles all Unicode code points, that is formally
incompatible with both ASCII and UTF-8, but will nevertheless often
tolerate processing as surrogate escaped data - the points where GB
18030 reuses ASCII byte values in an incompatible way are likely to be
invalid in UTF-8, and will therefore be escaped and opaque to string
processing operations that split on or search for the relevant ASCII
code points. Operations that don't involve splitting on or searching for
particular ASCII or Unicode code point values are almost certain to work
correctly.

Similarly, Shift-JIS \[13\_\] and ISO-2022-JP \[14\_\] remain in
widespread use in Japan, and are incompatible with both ASCII and UTF-8,
but will tolerate text processing operations that don't involve
splitting on or searching for particular ASCII or Unicode code point
values.

As an example, consider two files, one encoded with UTF-8 (the default
encoding for `en_AU.UTF-8`), and one encoded with GB-18030 (the default
encoding for `zh_CN.gb18030`)::

    $ python3 -c 'open("utf8.txt", "wb").write("ℙƴ☂ℌøἤ\n".encode("utf-8"))'
    $ python3 -c 'open("gb18030.txt", "wb").write("ℙƴ☂ℌøἤ\n".encode("gb18030"))'

On disk, we can see that these are two very different files::

    $ python3 -c 'print("UTF-8:  ", open("utf8.txt", "rb").read().strip()); \
                  print("GB18030:", open("gb18030.txt", "rb").read().strip())'
    UTF-8:   b'\xe2\x84\x99\xc6\xb4\xe2\x98\x82\xe2\x84\x8c\xc3\xb8\xe1\xbc\xa4\n'
    GB18030: b'\x816\xbd6\x810\x9d0\x817\xa29\x816\xbc4\x810\x8b3\x816\x8d6\n'

That nevertheless can both be rendered correctly to the terminal as long
as they're decoded prior to printing::

    $ python3 -c 'print("UTF-8:  ", open("utf8.txt", "r", encoding="utf-8").read().strip()); \
                  print("GB18030:", open("gb18030.txt", "r", encoding="gb18030").read().strip())'
    UTF-8:   ℙƴ☂ℌøἤ
    GB18030: ℙƴ☂ℌøἤ

By contrast, if we just pass along the raw bytes, as `cat` and similar
C/C++ utilities will tend to do::

    $ LANG=en_AU.UTF-8 cat utf8.txt gb18030.txt
    ℙƴ☂ℌøἤ
    �6�6�0�0�7�9�6�4�0�3�6�6

Even setting a specifically Chinese locale won't help in getting the
GB-18030 encoded file rendered correctly::

    $ LANG=zh_CN.gb18030 cat utf8.txt gb18030.txt
    ℙƴ☂ℌøἤ
    �6�6�0�0�7�9�6�4�0�3�6�6

The problem is that the *terminal* encoding setting remains UTF-8,
regardless of the nominal locale. A GB18030 terminal can be emulated
using the `iconv` utility::

    $ cat utf8.txt gb18030.txt | iconv -f GB18030 -t UTF-8
    鈩櫰粹槀鈩屆羔激
    ℙƴ☂ℌøἤ

This reverses the problem, such that the GB18030 file is rendered
correctly, but the UTF-8 file has been converted to unrelated hanzi
characters, rather than the expected rendering of "Python" as non-ASCII
characters.

With the emulated GB18030 terminal encoding, assuming UTF-8 in Python
results in *both* files being displayed incorrectly::

    $ python3 -c 'print("UTF-8:  ", open("utf8.txt", "r", encoding="utf-8").read().strip()); \
                  print("GB18030:", open("gb18030.txt", "r", encoding="gb18030").read().strip())' \
      | iconv -f GB18030 -t UTF-8
    UTF-8:   鈩櫰粹槀鈩屆羔激
    GB18030: 鈩櫰粹槀鈩屆羔激

However, setting the locale correctly means that the emulated GB18030
terminal now displays both files as originally intended::

    $ LANG=zh_CN.gb18030 \
      python3 -c 'print("UTF-8:  ", open("utf8.txt", "r", encoding="utf-8").read().strip()); \
                  print("GB18030:", open("gb18030.txt", "r", encoding="gb18030").read().strip())' \
      | iconv -f GB18030 -t UTF-8
    UTF-8:   ℙƴ☂ℌøἤ
    GB18030: ℙƴ☂ℌøἤ

The rationale for retaining `surrogateescape` as the default IO encoding
is that it will preserve the following helpful behaviour in the `C`
locale::

    $ cat gb18030.txt \
      | LANG=C python3 -c "import sys; print(sys.stdin.read())" \
      | iconv -f GB18030 -t UTF-8
    ℙƴ☂ℌøἤ

Rather than reverting to the exception currently seen when a UTF-8 based
locale is explicitly configured::

    $ cat gb18030.txt \
      | python3 -c "import sys; print(sys.stdin.read())" \
      | iconv -f GB18030 -t UTF-8
    Traceback (most recent call last):
    File "<string>", line 1, in <module>
    File "/usr/lib64/python3.5/codecs.py", line 321, in decode
        (result, consumed) = self._buffer_decode(data, self.errors, final)
    UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte

As an added benefit, environments explicitly configured to use one of
the coercion target locales will implicitly gain the encoding
transparency behaviour currently enabled by default in the `C` locale.

Avoiding setting PYTHONIOENCODING during UTF-8 locale coercion
--------------------------------------------------------------

Rather than changing the default handling of the standard streams during
interpreter initialization, earlier versions of this PEP proposed
setting `PYTHONIOENCODING` to `utf-8:surrogateescape`. This turned out
to create a significant compatibility problem: since the
`surrogateescape` handler only exists in Python 3.1+, running Python 2.7
processes in subprocesses could potentially break in a confusing way
with that configuration.

The current design means that earlier Python versions will instead
retain their default `strict` error handling on the standard streams,
while Python 3.7+ will consistently use the more permissive
`surrogateescape` handler even when these locales are explicitly
configured (rather than being reached through locale coercion).

Dropping official support for ASCII based text handling in the legacy C locale
------------------------------------------------------------------------------

We've been trying to get strict bytes/text separation to work reliably
in the legacy C locale for over a decade at this point. Not only haven't
we been able to get it to work, neither has anyone else - the only
viable alternatives identified have been to pass the bytes along
verbatim without eagerly decoding them to text (C/C++, Python 2.x, Ruby,
etc), or else to largely ignore the nominal C/C++ locale encoding and
assume the use of either UTF-8 (PEP 540, Rust, Go, Node.js, etc) or
UTF-16-LE (JVM, .NET CLR).

While this PEP ensures that developers that genuinely need to do so can
still opt-in to running their Python code in the legacy C locale (by
setting `LC_ALL=C`, `PYTHONCOERCECLOCALE=0`, or running a custom build
that sets `--without-c-locale-coercion`), it also makes it clear that we
*don't* expect Python 3's Unicode handling to be completely reliable in
that configuration, and the recommended alternative is to use a more
appropriate locale setting (potentially in combination with PEP 540's
UTF-8 mode, if that is available).

Providing implicit locale coercion only when running standalone
---------------------------------------------------------------

The major downside of the proposed design in this PEP is that it
introduces a potential discrepancy between the behaviour of the CPython
runtime when it is run as a standalone application and when it is run as
an embedded component inside a larger system (e.g. `mod_wsgi` running
inside Apache `httpd`).

Over the course of Python 3.x development, multiple attempts have been
made to improve the handling of incorrect locale settings at the point
where the Python interpreter is initialised. The problem that emerged is
that this is ultimately *too late* in the interpreter startup process -
data such as command line arguments and the contents of environment
variables may have already been retrieved from the operating system and
processed under the incorrect ASCII text encoding assumption well before
`Py_Initialize` is called.

The problems created by those inconsistencies were then even harder to
diagnose and debug than those created by believing the operating
system's claim that ASCII was a suitable encoding to use for operating
system interfaces. This was the case even for the default CPython
binary, let alone larger C/C++ applications that embed CPython as a
scripting engine.

The approach proposed in this PEP handles that problem by moving the
locale coercion as early as possible in the interpreter startup sequence
when running standalone: it takes place directly in the C-level `main()`
function, even before calling in to the `Py_Main()` library function
that implements the features of the CPython interpreter CLI.

The `Py_Initialize` API then only gains an explicit warning (emitted on
`stderr`) when it detects use of the `C` locale, and relies on the
embedding application to specify something more reasonable.

That said, the reference implementation for this PEP adds most of the
functionality to the shared library, with the CLI being updated to
unconditionally call two new private APIs::

    if (_Py_LegacyLocaleDetected()) {
        _Py_CoerceLegacyLocale();
    }

These are similar to other "pre-configuration" APIs intended for
embedding applications: they're designed to be called *before*
`Py_Initialize`, and hence change the way the interpreter gets
initialized.

If these were made public (either as part of this PEP or in a subsequent
RFE), then it would be straightforward for other embedding applications
to recreate the same behaviour as is proposed for the CPython CLI.

Allowing restoration of the legacy behaviour
--------------------------------------------

The CPython command line interpreter is often used to investigate faults
that occur in other applications that embed CPython, and those
applications may still be using the C locale even after this PEP is
implemented.

Providing a simple on/off switch for the locale coercion behaviour makes
it much easier to reproduce the behaviour of such applications for
debugging purposes, as well as making it easier to reproduce the
behaviour of older 3.x runtimes even when running a version with this
change applied.

Querying LC\_CTYPE for C locale detection
-----------------------------------------

`LC_CTYPE` is the actual locale category that CPython relies on to drive
the implicit decoding of environment variables, command line arguments,
and other text values received from the operating system.

As such, it makes sense to check it specifically when attempting to
determine whether or not the current locale configuration is likely to
cause Unicode handling problems.

Explicitly setting LC\_CTYPE for UTF-8 locale coercion
------------------------------------------------------

Python is often used as a glue language, integrating other C/C++ ABI
compatible components in the current process, and components written in
arbitrary languages in subprocesses.

Setting `LC_CTYPE` to `C.UTF-8` is important to handle cases where the
problem has arisen from a setting like `LC_CTYPE=UTF-8` being provided
on a system where no `UTF-8` locale is defined (e.g. when a Mac OS X ssh
client is configured to forward locale settings, and the user logs into
a Linux server).

This should be sufficient to ensure that when the locale coercion is
activated, the switch to the UTF-8 based locale will be applied
consistently across the current process and any subprocesses that
inherit the current environment.

Avoiding setting LANG for UTF-8 locale coercion
-----------------------------------------------

Earlier versions of this PEP proposed setting the `LANG` category
independent default locale, in addition to setting `LC_CTYPE`.

This was later removed on the grounds that setting only `LC_CTYPE` is
sufficient to handle all of the problematic scenarios that the PEP aimed
to resolve, while setting `LANG` as well would break cases where `LANG`
was set correctly, and the locale problems were solely due to an
incorrect `LC_CTYPE` setting (\[22\_\]).

For example, consider a Python application that called the Linux `date`
utility in a subprocess rather than doing its own date formatting::

    $ LANG=ja_JP.UTF-8 LC_CTYPE=C date
    2017年  5月 23日 火曜日 17:31:03 JST

    $ LANG=ja_JP.UTF-8 LC_CTYPE=C.UTF-8 date  # Coercing only LC_CTYPE
    2017年  5月 23日 火曜日 17:32:58 JST

    $ LANG=C.UTF-8 LC_CTYPE=C.UTF-8 date  # Coercing both of LC_CTYPE and LANG
    Tue May 23 17:31:10 JST 2017

With only `LC_CTYPE` updated in the Python process, the subprocess would
continue to behave as expected. However, if `LANG` was updated as well,
that would effectively override the `LC_TIME` setting and use the wrong
date formatting conventions.

Avoiding setting LC\_ALL for UTF-8 locale coercion
--------------------------------------------------

Earlier versions of this PEP proposed setting the `LC_ALL` locale
override, in addition to setting `LC_CTYPE`.

This was changed after it was determined that just setting `LC_CTYPE`
and `LANG` should be sufficient to handle all the scenarios the PEP aims
to cover, as it avoids causing any problems in cases like the
following::

    $ LANG=C LC_MONETARY=ja_JP.utf8 ./python -c \
      "from locale import setlocale, LC_ALL, currency; setlocale(LC_ALL, ''); print(currency(1e6))"
    ￥1000000

Skipping locale coercion if LC\_ALL is set in the current environment
---------------------------------------------------------------------

With locale coercion now only setting `LC_CTYPE` and `LANG`, it will
have no effect if `LC_ALL` is also set. To avoid emitting a spurious
locale coercion notice in that case, coercion is instead skipped
entirely.

Considering locale coercion independently of "UTF-8 mode"
---------------------------------------------------------

With both this PEP's locale coercion and PEP 540's UTF-8 mode under
consideration for Python 3.7, it makes sense to ask whether or not we
can limit ourselves to only doing one or the other, rather than making
both changes.

The UTF-8 mode proposed in PEP 540 has two major limitations that make
it a potential complement to this PEP rather than a potential
replacement.

First, unlike this PEP, PEP 540's UTF-8 mode makes it possible to change
default behaviours that are not currently configurable at all. While
that's exactly what makes the proposal interesting, it's also what makes
it an entirely unproven approach. By contrast, the approach proposed in
this PEP builds directly atop existing configuration settings for the C
locale system ( `LC_CTYPE`, `LANG`) and Python's standard streams
(`PYTHONIOENCODING`) that have already been in use for years to handle
the kinds of compatibility problems discussed in this PEP.

Secondly, one of the things we know based on that experience is that the
proposed locale coercion can resolve problems not only in CPython
itself, but also in extension modules that interact with the standard
streams, like GNU readline. As an example, consider the following
interactive session from a PEP 538 enabled CPython build, where each
line after the first is executed by doing "up-arrow, left-arrow x4,
delete, enter"::

    $ LANG=C ./python
    Python 3.7.0a0 (heads/pep538-coerce-c-locale:188e780, May  7 2017, 00:21:13)
    [GCC 6.3.1 20161221 (Red Hat 6.3.1-1)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> print("ℙƴ☂ℌøἤ")
    ℙƴ☂ℌøἤ
    >>> print("ℙƴ☂ℌἤ")
    ℙƴ☂ℌἤ
    >>> print("ℙƴ☂ἤ")
    ℙƴ☂ἤ
    >>> print("ℙƴἤ")
    ℙƴἤ
    >>> print("ℙἤ")
    ℙἤ
    >>> print("ἤ")
    ἤ
    >>>

This is exactly what we'd expect from a well-behaved command history
editor.

By contrast, the following is what currently happens on an older release
if you only change the Python level stream encoding settings without
updating the locale settings::

    $ LANG=C PYTHONIOENCODING=utf-8:surrogateescape python3
    Python 3.5.3 (default, Apr 24 2017, 13:32:13)
    [GCC 6.3.1 20161221 (Red Hat 6.3.1-1)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> print("ℙƴ☂ℌøἤ")
    ℙƴ☂ℌøἤ
    >>> print("ℙƴ☂ℌ�")
     File "<stdin>", line 0

       ^
    SyntaxError: 'utf-8' codec can't decode bytes in position 20-21:
    invalid continuation byte

That particular misbehaviour is coming from GNU readline, *not* CPython
- because the command history editing wasn't UTF-8 aware, it corrupted
the history buffer and fed such nonsense to stdin that even the
surrogateescape error handler was bypassed. While PEP 540's UTF-8 mode
could technically be updated to also reconfigure readline, that's just
*one* extension module that might be interacting with the standard
streams without going through the CPython C API, and any change made by
CPython would only apply when readline is running directly as part of
Python 3.7 rather than in a separate subprocess.

However, if we actually change the configured locale, GNU readline
starts behaving itself, without requiring any changes to the embedding
application::

    $ LANG=C.UTF-8 python3
    Python 3.5.3 (default, Apr 24 2017, 13:32:13)
    [GCC 6.3.1 20161221 (Red Hat 6.3.1-1)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> print("ℙƴ☂ℌøἤ")
    ℙƴ☂ℌøἤ
    >>> print("ℙƴ☂ℌἤ")
    ℙƴ☂ℌἤ
    >>> print("ℙƴ☂ἤ")
    ℙƴ☂ἤ
    >>> print("ℙƴἤ")
    ℙƴἤ
    >>> print("ℙἤ")
    ℙἤ
    >>> print("ἤ")
    ἤ
    >>>
    $ LC_CTYPE=C.UTF-8 python3
    Python 3.5.3 (default, Apr 24 2017, 13:32:13)
    [GCC 6.3.1 20161221 (Red Hat 6.3.1-1)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> print("ℙƴ☂ℌøἤ")
    ℙƴ☂ℌøἤ
    >>> print("ℙƴ☂ℌἤ")
    ℙƴ☂ℌἤ
    >>> print("ℙƴ☂ἤ")
    ℙƴ☂ἤ
    >>> print("ℙƴἤ")
    ℙƴἤ
    >>> print("ℙἤ")
    ℙἤ
    >>> print("ἤ")
    ἤ
    >>>

Enabling C locale coercion and warnings on Mac OS X, iOS and Android
--------------------------------------------------------------------

On Mac OS X, iOS, and Android, CPython already assumes the use of UTF-8
for system interfaces, and we expect most other locale-aware components
to do the same.

Accordingly, this PEP originally proposed to disable locale coercion and
warnings at build time for these platforms, on the assumption that it
would be entirely redundant.

However, that assumption turned out to be incorrect, as subsequent
investigations showed that if you explicitly configure `LANG=C` on these
platforms, extension modules like GNU readline will misbehave in much
the same way as they do on other \*nix systems. \[21\_\]

In addition, Mac OS X is also frequently used as a development and
testing platform for Python software intended for deployment to other
\*nix environments (such as Linux or Android), and Linux is similarly
often used as a development and testing platform for mobile and Mac OS X
applications.

Accordingly, this PEP enables the locale coercion and warning features
by default on all platforms that use CPython's `autotools` based build
toolchain (i.e. everywhere other than Windows).

Implementation
==============

The reference implementation is being developed in the
`pep538-coerce-c-locale` feature branch \[18\_\] in Nick Coghlan's fork
of the CPython repository on GitHub. A work-in-progress PR is available
at \[20\_\].

This reference implementation covers not only the enhancement request in
issue 28180 \[1\_\], but also the Android compatibility fixes needed to
resolve issue 28997 \[16\_\].

Backporting to earlier Python 3 releases
========================================

Backporting to Python 3.6.x
---------------------------

If this PEP is accepted for Python 3.7, redistributors backporting the
change specifically to their initial Python 3.6.x release will be both
allowed and encouraged. However, such backports should only be
undertaken either in conjunction with the changes needed to also provide
a suitable locale by default, or else specifically for platforms where
such a locale is already consistently available.

At least the Fedora project is planning to pursue this approach for the
upcoming Fedora 26 release \[19\_\].

Backporting to other 3.x releases
---------------------------------

While the proposed behavioural change is seen primarily as a bug fix
addressing Python 3's current misbehaviour in the default ASCII-based C
locale, it still represents a reasonably significant change in the way
CPython interacts with the C locale system. As such, while some
redistributors may still choose to backport it to even earlier Python
3.x releases based on the needs and interests of their particular user
base, this wouldn't be encouraged as a general practice.

However, configuring Python 3 *environments* (such as base container
images) to use these configuration settings by default is both allowed
and recommended.

Acknowledgements
================

The locale coercion approach proposed in this PEP is inspired directly
by Armin Ronacher's handling of this problem in the `click` command line
utility development framework \[2\_\]::

    $ LANG=C python3 -c 'import click; cli = click.command()(lambda:None); cli()'
    Traceback (most recent call last):
      ...
    RuntimeError: Click will abort further execution because Python 3 was
    configured to use ASCII as encoding for the environment.  Either run this
    under Python 2 or consult http://click.pocoo.org/python3/ for mitigation
    steps.

    This system supports the C.UTF-8 locale which is recommended.
    You might be able to resolve your issue by exporting the
    following environment variables:

        export LC_ALL=C.UTF-8
        export LANG=C.UTF-8

The change was originally proposed as a downstream patch for Fedora's
system Python 3.6 package \[3\_\], and then reformulated as a PEP for
Python 3.7 with a section allowing for backports to earlier versions by
redistributors. In parallel with the development of the upstream patch,
Charalampos Stratakis has been working on the Fedora 26 backport and
providing feedback on the practical viability of the proposed changes.

The initial draft was posted to the Python Linux SIG for discussion
\[10\_\] and then amended based on both that discussion and Victor
Stinner's work in PEP 540 \[11\_\].

The "ℙƴ☂ℌøἤ" string used in the Unicode handling examples throughout
this PEP is taken from Ned Batchelder's excellent "Pragmatic Unicode"
presentation \[9\_\].

Stephen Turnbull has long provided valuable insight into the text
encoding handling challenges he regularly encounters at the University
of Tsukuba (筑波大学).

References
==========

.. \[1\] CPython: sys.getfilesystemencoding() should default to utf-8
(http://bugs.python.org/issue28180)

.. \[2\] Locale configuration required for click applications under
Python 3
(http://click.pocoo.org/5/python3/\#python-3-surrogate-handling)

.. \[3\] Fedora: force C.UTF-8 when Python 3 is run under the C locale
(https://bugzilla.redhat.com/show\_bug.cgi?id=1404918)

.. \[4\] GNU C: How Programs Set the Locale (
https://www.gnu.org/software/libc/manual/html\_node/Setting-the-Locale.html)

.. \[5\] GNU C: Locale Categories
(https://www.gnu.org/software/libc/manual/html\_node/Locale-Categories.html)

.. \[6\] glibc C.UTF-8 locale proposal
(https://sourceware.org/glibc/wiki/Proposals/C.UTF-8)

.. \[7\] GNOME Flatpak (http://flatpak.org/)

.. \[8\] Ubuntu Snappy (https://www.ubuntu.com/desktop/snappy)

.. \[9\] Pragmatic Unicode (http://nedbatchelder.com/text/unipain.html)

.. \[10\] linux-sig discussion of initial PEP draft
(https://mail.python.org/pipermail/linux-sig/2017-January/000014.html)

.. \[11\] Feedback notes from linux-sig discussion and PEP 540
(https://github.com/python/peps/issues/171)

.. \[12\] GB 18030 (https://en.wikipedia.org/wiki/GB\_18030)

.. \[13\] Shift-JIS (https://en.wikipedia.org/wiki/Shift\_JIS)

.. \[14\] ISO-2022 (https://en.wikipedia.org/wiki/ISO/IEC\_2022)

.. \[15\] Use "surrogateescape" error handler for sys.stdin and
sys.stdout on UNIX for the C locale (https://bugs.python.org/issue19977)

.. \[16\] test\_readline.test\_nonascii fails on Android
(http://bugs.python.org/issue28997)

.. \[17\] UTF-8 locale discussion on "locale.getdefaultlocale() fails on
Mac OS X with default language set to English"
(http://bugs.python.org/issue18378\#msg215215)

.. \[18\] GitHub branch diff for `ncoghlan:pep538-coerce-c-locale`
(https://github.com/python/cpython/compare/master...ncoghlan:pep538-coerce-c-locale)

.. \[19\] Fedora 26 change proposal for locale coercion backport
(https://fedoraproject.org/wiki/Changes/python3\_c.utf-8\_locale)

.. \[20\] GitHub pull request for the reference implementation
(https://github.com/python/cpython/pull/659)

.. \[21\] GNU readline misbehaviour on Mac OS X with `LANG=C`
(https://mail.python.org/pipermail/python-dev/2017-May/147897.html)

.. \[22\] Potential problems when setting LANG in addition to setting
LC\_CTYPE
(https://mail.python.org/pipermail/python-dev/2017-May/147968.html)

Copyright
=========

This document has been placed in the public domain under the terms of
the CC0 1.0 license: https://creativecommons.org/publicdomain/zero/1.0/

 .. Local Variables: mode: indented-text indent-tabs-mode: nil
sentence-end-double-space: t fill-column: 70 coding: utf-8 End:
