python-peps/pep-0540.txt

308 lines
12 KiB
Plaintext
Raw Normal View History

2017-01-05 07:46:03 -05:00
PEP: 540
Title: Add a new UTF-8 Mode
2017-01-05 07:46:03 -05:00
Version: $Revision$
Last-Modified: $Date$
2017-12-05 19:42:16 -05:00
Author: Victor Stinner <victor.stinner@gmail.com>
BDFL-Delegate: INADA Naoki
2017-01-05 07:46:03 -05:00
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
Created: 5-January-2016
Python-Version: 3.7
Abstract
========
Add a new UTF-8 Mode to use the UTF-8 encoding, ignore the locale
encoding, and change ``stdin`` and ``stdout`` error handlers to
``surrogateescape``. This mode is disabled by default and enabled by
the POSIX locale.
2017-12-05 19:42:16 -05:00
The new ``-X utf8`` command line option and ``PYTHONUTF8`` environment
variable are added to control the UTF-8 Mode.
2017-01-05 07:46:03 -05:00
Rationale
=========
2017-12-05 19:42:16 -05:00
Locale encoding and UTF-8
-------------------------
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
Python 3.6 uses the locale encoding for filenames, environment
variables, standard streams, etc. The locale encoding is inherited from
the locale; the encoding and the locale are tightly coupled.
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
Many users inherit the ASCII encoding from the POSIX locale, aka the "C"
locale, but are unable change the locale for different reasons. This
encoding is very limited in term of Unicode support: any non-ASCII
character is likely to cause troubles.
2017-12-05 19:42:16 -05:00
It is not easy to get the expected locale. Locales don't get the exact
same name on all Linux distributions, FreeBSD, macOS, etc. Some
locales, like the recent ``C.UTF-8`` locale, are only supported by a few
platforms. For example, a SSH connection can use a different encoding
than the filesystem or terminal encoding of the local host.
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
On the other side, Python 3.6 is already using UTF-8 by default on
macOS, Android and Windows (PEP 529) for most functions, except of
``open()``. UTF-8 is also the default encoding of Python scripts, XML
and JSON file formats. The Go programming language uses UTF-8 for
strings.
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
When all data are stored as UTF-8 but the locale is often misconfigured,
an obvious solution is to ignore the locale encoding and use UTF-8.
2017-12-05 19:42:16 -05:00
Passthough undecodable bytes: surrogateescape
---------------------------------------------
2017-01-05 07:46:03 -05:00
2017-12-07 19:43:42 -05:00
When decoding bytes from UTF-8 using the ``strict`` error handler, which
is the default, Python 3 raises a ``UnicodeDecodeError`` on the first
undecodable byte.
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
Unix command line tools like ``cat`` or ``grep`` and most Python 2
applications simply do not have this class of bugs: they don't decode
data, but process data as a raw bytes sequence.
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
Python 3 already has a solution to behave like Unix tools and Python 2:
the ``surrogateescape`` error handler (:pep:`383`). It allows to process
data "as bytes" but uses Unicode in practice (undecodable bytes are
stored as surrogate characters).
2017-01-05 07:46:03 -05:00
The UTF-8 Mode uses the ``surrogateescape`` error handler for ``stdin``
and ``stdout`` since these streams as commonly associated to Unix
command line tools.
However, users have a different expectation on files. Files are expected
to be properly encoded. Python is expected to fail early when ``open()``
is called with the wrong options, like opening a JPEG picture in text
mode. The ``open()`` default error handler remains ``strict`` for these
reasons.
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
No change by default for best backward compatibility
----------------------------------------------------
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
While UTF-8 is perfect in most cases, sometimes the locale encoding is
actually the best encoding.
2017-01-05 07:46:03 -05:00
2017-12-05 19:42:16 -05:00
This PEP changes the behaviour for the POSIX locale since this locale
usually gives the ASCII encoding, whereas UTF-8 is a much better choice.
It does not change the behaviour for other locales to prevent any risk
or regression.
2017-01-05 07:46:03 -05:00
As users are responsible to enable explicitly the new UTF-8 Mode, they
2017-12-05 19:42:16 -05:00
are responsible for any potential mojibake issues caused by this mode.
2017-01-05 07:46:03 -05:00
Proposal
========
Add a new UTF-8 Mode to use the UTF-8 encoding, ignore the locale
encoding, and change ``stdin`` and ``stdout`` error handlers to
``surrogateescape``.
The new ``-X utf8`` command line option and ``PYTHONUTF8`` environment
variable are added. The UTF-8 Mode is enabled by ``-X utf8`` or
2017-12-07 19:43:42 -05:00
``PYTHONUTF8=1``.
This mode is disabled by default and enabled by the POSIX locale. The
UTF-8 Mode can be explicitly disabled by ``-X utf8=0`` or
``PYTHONUTF8=0``.
2017-12-05 19:42:16 -05:00
For standard streams, the ``PYTHONIOENCODING`` environment variable has
priority over the UTF-8 Mode.
2017-12-05 19:42:16 -05:00
On Windows, the ``PYTHONLEGACYWINDOWSFSENCODING`` environment variable
(:pep:`529`) has the priority over the UTF-8 Mode.
Effects of the UTF-8 Mode:
* ``sys.getfilesystemencoding()`` returns ``'UTF-8'``.
* ``locale.getpreferredencoding()`` returns ``UTF-8``, its
*do_setlocale* argument and the locale encoding are ignored.
* ``sys.stdin`` and ``sys.stdout`` error handler is set to
``surrogateescape``
Side effects:
* ``open()`` uses the UTF-8 encoding by default.
* ``os.fsdecode()`` and ``os.fsencode()`` use the UTF-8 encoding.
* Command line arguments, environment variables and filenames use the
UTF-8 encoding.
.. note::
In the UTF-8 Mode, ``open()`` still uses the ``strict`` error handler
by default.
Relationship with the locale coercion (PEP 538)
===============================================
The POSIX locale enables the locale coercion (PEP 538) and the UTF-8
mode (PEP 540). When the locale coercion is enabled, enabling the UTF-8
mode has no (additional) effect.
2017-12-10 12:52:56 -05:00
The UTF-8 Mode has the same effect than locale coercion:
2017-12-10 12:18:26 -05:00
``sys.getfilesystemencoding()`` returns ``'UTF-8'``,
``locale.getpreferredencoding()`` returns ``UTF-8``, ``sys.stdin`` and
``sys.stdout`` error handler set to ``surrogateescape``. These changes
only affect Python code. But the locale coercion has addiditonal
effects: the ``LC_CTYPE`` environment variable and the ``LC_CTYPE``
locale are set to a UTF-8 locale like ``C.UTF-8``. The side effect is
that non-Python code is also impacted by the locale coercion. The two
PEPs are complementary.
On platforms where locale coercion is not supported like Centos 7, the
POSIX locale only enables the UTF-8 Mode. In this case, Python code uses
the UTF-8 encoding and ignores the locale encoding, whereas non-Python
code uses the locale encoding which is usually ASCII for the POSIX
locale.
While the UTF-8 Mode is supported on all platforms and can be enabled
with any locale, the locale coercion is not supported by all platforms
and is restricted to the POSIX locale.
The UTF-8 Mode has only an impact on Python child processes when the
``PYTHONUTF8`` environment variable is set to ``1``, whereas the locale
coercion sets the ``LC_CTYPE`` environment variables which impacts all
child processes.
The benefit of the locale coercion approach is that it helps ensure that
encoding handling in binary extension modules and child processes is
consistent with Python's encoding handling. The upside of the UTF-8 Mode
approach is that it allows an embedding application to change the
interpreter's behaviour without having to change the process global
locale settings.
2017-12-05 19:42:16 -05:00
Backward Compatibility
======================
The only backward incompatible change is that the POSIX locale now
enables the UTF-8 Mode by default: use the UTF-8 encoding, ignore the
locale encoding, and change ``stdin`` and ``stdout`` error handlers to
``surrogateescape``.
2017-12-05 19:42:16 -05:00
Annex: Encodings And Error Handlers
===================================
2017-01-05 07:46:03 -05:00
The UTF-8 Mode changes the default encoding and error handler used by
``open()``, ``os.fsdecode()``, ``os.fsencode()``, ``sys.stdin``,
2017-12-05 19:42:16 -05:00
``sys.stdout`` and ``sys.stderr``.
Encoding and error handler
--------------------------
2017-01-05 07:46:03 -05:00
2017-12-07 19:43:42 -05:00
============================ ======================= ==========================
Function Default UTF-8 Mode or POSIX locale
2017-12-07 19:43:42 -05:00
============================ ======================= ==========================
open() locale/strict **UTF-8**/strict
os.fsdecode(), os.fsencode() locale/surrogateescape **UTF-8**/surrogateescape
sys.stdin, sys.stdout locale/strict **UTF-8/surrogateescape**
sys.stderr locale/backslashreplace **UTF-8**/backslashreplace
============================ ======================= ==========================
By comparison, Python 3.6 uses:
============================ ======================= ==========================
Function Default POSIX locale
============================ ======================= ==========================
open() locale/strict locale/strict
os.fsdecode(), os.fsencode() locale/surrogateescape locale/surrogateescape
sys.stdin, sys.stdout locale/strict locale/**surrogateescape**
sys.stderr locale/backslashreplace locale/backslashreplace
============================ ======================= ==========================
Encoding and error handler on Windows
-------------------------------------
On Windows, the encodings and error handlers are different:
2017-12-07 19:43:42 -05:00
============================ ======================= ========================== ==========================
Function Default Legacy Windows FS encoding UTF-8 Mode
2017-12-07 19:43:42 -05:00
============================ ======================= ========================== ==========================
open() mbcs/strict mbcs/strict **UTF-8**/strict
os.fsdecode(), os.fsencode() UTF-8/surrogatepass **mbcs/replace** UTF-8/surrogatepass
sys.stdin, sys.stdout UTF-8/surrogateescape UTF-8/surrogateescape UTF-8/surrogateescape
sys.stderr UTF-8/backslashreplace UTF-8/backslashreplace UTF-8/backslashreplace
============================ ======================= ========================== ==========================
By comparison, Python 3.6 uses:
============================ ======================= ==========================
Function Default Legacy Windows FS encoding
============================ ======================= ==========================
open() mbcs/strict mbcs/strict
os.fsdecode(), os.fsencode() UTF-8/surrogatepass **mbcs/replace**
sys.stdin, sys.stdout UTF-8/surrogateescape UTF-8/surrogateescape
sys.stderr UTF-8/backslashreplace UTF-8/backslashreplace
============================ ======================= ==========================
2017-12-05 19:42:16 -05:00
The "Legacy Windows FS encoding" is enabled by the
``PYTHONLEGACYWINDOWSFSENCODING`` environment variable.
If stdin and/or stdout is redirected to a pipe, ``sys.stdin`` and/or
``sys.output`` use ``mbcs`` encoding by default rather than UTF-8. But
in the UTF-8 Mode, ``sys.stdin`` and ``sys.stdout`` always use the UTF-8
2017-12-05 19:42:16 -05:00
encoding.
2017-12-05 19:42:16 -05:00
.. note:
There is no POSIX locale on Windows. The ANSI code page is used to
the locale encoding, and this code page never uses the ASCII
encoding.
2017-01-05 07:46:03 -05:00
Links
=====
* `bpo-29240: Implementation of the PEP 540: Add a new UTF-8 Mode
2017-12-05 19:42:16 -05:00
<http://bugs.python.org/issue29240>`_
* `PEP 538 <https://www.python.org/dev/peps/pep-0538/>`_:
"Coercing the legacy C locale to C.UTF-8"
* `PEP 529 <https://www.python.org/dev/peps/pep-0529/>`_:
"Change Windows filesystem encoding to UTF-8"
* `PEP 528 <https://www.python.org/dev/peps/pep-0528/>`_:
"Change Windows console encoding to UTF-8"
* `PEP 383 <https://www.python.org/dev/peps/pep-0383/>`_:
"Non-decodable Bytes in System Character Interfaces"
2017-01-05 07:46:03 -05:00
2017-12-05 10:24:40 -05:00
Post History
============
* 2017-12: `[Python-Dev] PEP 540: Add a new UTF-8 Mode
2017-12-05 19:42:16 -05:00
<https://mail.python.org/pipermail/python-dev/2017-December/151054.html>`_
2017-12-05 10:24:40 -05:00
* 2017-04: `[Python-Dev] Proposed BDFL Delegate update for PEPs 538 &
540 (assuming UTF-8 for *nix system boundaries)
<https://mail.python.org/pipermail/python-dev/2017-April/147795.html>`_
* 2017-01: `[Python-ideas] PEP 540: Add a new UTF-8 Mode
2017-12-05 10:24:40 -05:00
<https://mail.python.org/pipermail/python-ideas/2017-January/044089.html>`_
* 2017-01: `bpo-28180: Implementation of the PEP 538: coerce C locale to
C.utf-8 (msg284764) <https://bugs.python.org/issue28180#msg284764>`_
* 2016-08-17: `bpo-27781: Change sys.getfilesystemencoding() on Windows
to UTF-8 (msg272916) <https://bugs.python.org/issue27781#msg272916>`_
-- Victor proposed ``-X utf8`` for the :pep:`529` (Change Windows
filesystem encoding to UTF-8)
Version History
===============
* Version 4: ``locale.getpreferredencoding()`` now returns ``'UTF-8'``
in the UTF-8 Mode.
* Version 3: The UTF-8 Mode does not change the ``open()`` default error
handler (``strict``) anymore, and the Strict UTF-8 Mode has been
removed.
* Version 2: Rewrite the PEP from scratch to make it much shorter and
easier to understand.
* Version 1: First version posted to python-dev.
2017-01-05 07:46:03 -05:00
Copyright
=========
This document has been placed in the public domain.