[Python-checkins] cpython: Issue #23731: Implement PEP 488.

brett.cannon python-checkins at python.org
Mon Apr 13 20:21:10 CEST 2015


https://hg.python.org/cpython/rev/a86d630555bd
changeset:   95593:a86d630555bd
user:        Brett Cannon <brett at python.org>
date:        Mon Apr 13 14:21:02 2015 -0400
summary:
  Issue #23731: Implement PEP 488.

The concept of .pyo files no longer exists. Now .pyc files have an
optional `opt-` tag which specifies if any extra optimizations beyond
the peepholer were applied.

files:
  Doc/c-api/import.rst                    |     6 +-
  Doc/distutils/apiref.rst                |    11 +-
  Doc/distutils/introduction.rst          |     6 +-
  Doc/library/compileall.rst              |    16 +
  Doc/library/imp.rst                     |    11 +-
  Doc/library/importlib.rst               |    46 +-
  Doc/library/py_compile.rst              |     9 +-
  Doc/library/sys.rst                     |     3 +-
  Doc/library/tracemalloc.rst             |     7 +-
  Doc/library/zipfile.rst                 |     4 +-
  Doc/library/zipimport.rst               |     5 +-
  Doc/reference/import.rst                |     2 +-
  Doc/tutorial/modules.rst                |    11 +-
  Doc/using/cmdline.rst                   |     2 +-
  Doc/using/windows.rst                   |     6 +-
  Doc/whatsnew/3.5.rst                    |    35 +-
  Lib/compileall.py                       |     7 +-
  Lib/distutils/command/build_py.py       |     4 +-
  Lib/distutils/command/install_lib.py    |    16 +-
  Lib/distutils/tests/test_build_py.py    |     4 +-
  Lib/distutils/tests/test_install_lib.py |    13 +-
  Lib/distutils/util.py                   |     9 +-
  Lib/doctest.py                          |     4 +-
  Lib/imp.py                              |    19 +-
  Lib/importlib/_bootstrap.py             |    84 +-
  Lib/modulefinder.py                     |     2 +-
  Lib/msilib/__init__.py                  |     7 +-
  Lib/py_compile.py                       |    11 +-
  Lib/pydoc.py                            |     2 +-
  Lib/test/support/__init__.py            |    20 +-
  Lib/test/test_compile.py                |     2 +-
  Lib/test/test_compileall.py             |    21 +-
  Lib/test/test_imp.py                    |   115 +-
  Lib/test/test_import/__init__.py        |    37 +-
  Lib/test/test_importlib/test_util.py    |   114 +-
  Lib/test/test_py_compile.py             |     4 +
  Lib/test/test_runpy.py                  |     2 +-
  Lib/test/test_trace.py                  |     4 +-
  Lib/test/test_tracemalloc.py            |     6 +-
  Lib/test/test_zipfile.py                |    11 +-
  Lib/test/test_zipimport.py              |     2 +-
  Lib/tkinter/__init__.py                 |     2 +-
  Lib/tkinter/test/runtktests.py          |     2 +-
  Lib/trace.py                            |     2 +-
  Lib/tracemalloc.py                      |     2 +-
  Lib/unittest/loader.py                  |     4 +-
  Lib/warnings.py                         |     2 +-
  Lib/zipfile.py                          |    57 +-
  Makefile.pre.in                         |    11 +-
  Misc/NEWS                               |     2 +
  Misc/python.man                         |    25 +-
  Modules/getpath.c                       |     5 +-
  Modules/zipimport.c                     |    17 +-
  Python/_warnings.c                      |     5 +-
  Python/importlib.h                      |  8513 +++++-----
  Python/pythonrun.c                      |     5 +-
  56 files changed, 4732 insertions(+), 4622 deletions(-)


diff --git a/Doc/c-api/import.rst b/Doc/c-api/import.rst
--- a/Doc/c-api/import.rst
+++ b/Doc/c-api/import.rst
@@ -183,9 +183,9 @@
 
 .. c:function:: long PyImport_GetMagicNumber()
 
-   Return the magic number for Python bytecode files (a.k.a. :file:`.pyc` and
-   :file:`.pyo` files).  The magic number should be present in the first four bytes
-   of the bytecode file, in little-endian byte order. Returns -1 on error.
+   Return the magic number for Python bytecode files (a.k.a. :file:`.pyc` file).
+   The magic number should be present in the first four bytes of the bytecode
+   file, in little-endian byte order. Returns -1 on error.
 
    .. versionchanged:: 3.3
       Return value of -1 upon failure.
diff --git a/Doc/distutils/apiref.rst b/Doc/distutils/apiref.rst
--- a/Doc/distutils/apiref.rst
+++ b/Doc/distutils/apiref.rst
@@ -1193,12 +1193,12 @@
 
 .. function:: byte_compile(py_files[, optimize=0, force=0, prefix=None, base_dir=None, verbose=1, dry_run=0, direct=None])
 
-   Byte-compile a collection of Python source files to either :file:`.pyc` or
-   :file:`.pyo` files in a :file:`__pycache__` subdirectory (see :pep:`3147`).
+   Byte-compile a collection of Python source files to :file:`.pyc` files in a
+   :file:`__pycache__` subdirectory (see :pep:`3147` and :pep:`488`).
    *py_files* is a list of files to compile; any files that don't end in
    :file:`.py` are silently skipped.  *optimize* must be one of the following:
 
-   * ``0`` - don't optimize (generate :file:`.pyc`)
+   * ``0`` - don't optimize
    * ``1`` - normal optimization (like ``python -O``)
    * ``2`` - extra optimization (like ``python -OO``)
 
@@ -1222,10 +1222,13 @@
    doing, leave it set to ``None``.
 
    .. versionchanged:: 3.2.3
-      Create ``.pyc`` or ``.pyo`` files with an :func:`import magic tag
+      Create ``.pyc`` files with an :func:`import magic tag
       <imp.get_tag>` in their name, in a :file:`__pycache__` subdirectory
       instead of files without tag in the current directory.
 
+   .. versionchanged: 3.5
+      Create ``.pyc`` files according to :pep:`488`.
+
 
 .. function:: rfc822_escape(header)
 
diff --git a/Doc/distutils/introduction.rst b/Doc/distutils/introduction.rst
--- a/Doc/distutils/introduction.rst
+++ b/Doc/distutils/introduction.rst
@@ -156,8 +156,8 @@
 
 pure Python module
    a module written in Python and contained in a single :file:`.py` file (and
-   possibly associated :file:`.pyc` and/or :file:`.pyo` files).  Sometimes referred
-   to as a "pure module."
+   possibly associated :file:`.pyc` files).  Sometimes referred to as a
+   "pure module."
 
 extension module
    a module written in the low-level language of the Python implementation: C/C++
@@ -210,5 +210,3 @@
    the top-level directory of your source tree (or  source distribution); the
    directory where :file:`setup.py` exists.  Generally  :file:`setup.py` will be
    run from this directory.
-
-
diff --git a/Doc/library/compileall.rst b/Doc/library/compileall.rst
--- a/Doc/library/compileall.rst
+++ b/Doc/library/compileall.rst
@@ -93,6 +93,10 @@
 .. versionchanged:: 3.5
    ``-q`` option was changed to a multilevel value.
 
+.. versionchanged:: 3.5
+   ``-b`` will always produce a byte-code file ending in ``.pyc``, never
+   ``.pyo``.
+
 
 There is no command-line option to control the optimization level used by the
 :func:`compile` function, because the Python interpreter itself already
@@ -150,6 +154,10 @@
    .. versionchanged:: 3.5
       *quiet* parameter was changed to a multilevel value.
 
+   .. versionchanged:: 3.5
+      The *legacy* parameter only writes out ``.pyc`` files, not ``.pyo`` files
+      no matter what the value of *optimize* is.
+
 .. function:: compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, legacy=False, optimize=-1)
 
    Compile the file with path *fullname*.
@@ -182,6 +190,10 @@
    .. versionchanged:: 3.5
       *quiet* parameter was changed to a multilevel value.
 
+   .. versionchanged:: 3.5
+      The *legacy* parameter only writes out ``.pyc`` files, not ``.pyo`` files
+      no matter what the value of *optimize* is.
+
 .. function:: compile_path(skip_curdir=True, maxlevels=0, force=False, quiet=0, legacy=False, optimize=-1)
 
    Byte-compile all the :file:`.py` files found along ``sys.path``. If
@@ -196,6 +208,10 @@
    .. versionchanged:: 3.5
       *quiet* parameter was changed to a multilevel value.
 
+   .. versionchanged:: 3.5
+      The *legacy* parameter only writes out ``.pyc`` files, not ``.pyo`` files
+      no matter what the value of *optimize* is.
+
 To force a recompile of all the :file:`.py` files in the :file:`Lib/`
 subdirectory and all its subdirectories::
 
diff --git a/Doc/library/imp.rst b/Doc/library/imp.rst
--- a/Doc/library/imp.rst
+++ b/Doc/library/imp.rst
@@ -203,11 +203,9 @@
    value would be ``/foo/bar/__pycache__/baz.cpython-32.pyc`` for Python 3.2.
    The ``cpython-32`` string comes from the current magic tag (see
    :func:`get_tag`; if :attr:`sys.implementation.cache_tag` is not defined then
-   :exc:`NotImplementedError` will be raised).  The returned path will end in
-   ``.pyc`` when ``__debug__`` is ``True`` or ``.pyo`` for an optimized Python
-   (i.e. ``__debug__`` is ``False``).  By passing in ``True`` or ``False`` for
-   *debug_override* you can override the system's value for ``__debug__`` for
-   extension selection.
+   :exc:`NotImplementedError` will be raised). By passing in ``True`` or
+   ``False`` for *debug_override* you can override the system's value for
+   ``__debug__``, leading to optimized bytecode.
 
    *path* need not exist.
 
@@ -218,6 +216,9 @@
    .. deprecated:: 3.4
       Use :func:`importlib.util.cache_from_source` instead.
 
+   .. versionchanged:: 3.5
+      The *debug_override* parameter no longer creates a ``.pyo`` file.
+
 
 .. function:: source_from_cache(path)
 
diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst
--- a/Doc/library/importlib.rst
+++ b/Doc/library/importlib.rst
@@ -711,6 +711,9 @@
 
    .. versionadded:: 3.3
 
+   .. deprecated:: 3.5
+      Use :attr:`BYTECODE_SUFFIXES` instead.
+
 .. attribute:: OPTIMIZED_BYTECODE_SUFFIXES
 
    A list of strings representing the file suffixes for optimized bytecode
@@ -718,14 +721,19 @@
 
    .. versionadded:: 3.3
 
+   .. deprecated:: 3.5
+      Use :attr:`BYTECODE_SUFFIXES` instead.
+
 .. attribute:: BYTECODE_SUFFIXES
 
    A list of strings representing the recognized file suffixes for bytecode
-   modules. Set to either :attr:`DEBUG_BYTECODE_SUFFIXES` or
-   :attr:`OPTIMIZED_BYTECODE_SUFFIXES` based on whether ``__debug__`` is true.
+   modules (including the leading dot).
 
    .. versionadded:: 3.3
 
+   .. versionchanged:: 3.5
+      The value is no longer dependent on ``__debug__``.
+
 .. attribute:: EXTENSION_SUFFIXES
 
    A list of strings representing the recognized file suffixes for
@@ -1074,23 +1082,37 @@
 
    .. versionadded:: 3.4
 
-.. function:: cache_from_source(path, debug_override=None)
+.. function:: cache_from_source(path, debug_override=None, *, optimization=None)
 
-   Return the :pep:`3147` path to the byte-compiled file associated with the
-   source *path*.  For example, if *path* is ``/foo/bar/baz.py`` the return
+   Return the :pep:`3147`/:pep:`488` path to the byte-compiled file associated
+   with the source *path*.  For example, if *path* is ``/foo/bar/baz.py`` the return
    value would be ``/foo/bar/__pycache__/baz.cpython-32.pyc`` for Python 3.2.
    The ``cpython-32`` string comes from the current magic tag (see
    :func:`get_tag`; if :attr:`sys.implementation.cache_tag` is not defined then
-   :exc:`NotImplementedError` will be raised).  The returned path will end in
-   ``.pyc`` when ``__debug__`` is ``True`` or ``.pyo`` for an optimized Python
-   (i.e. ``__debug__`` is ``False``).  By passing in ``True`` or ``False`` for
-   *debug_override* you can override the system's value for ``__debug__`` for
-   extension selection.
+   :exc:`NotImplementedError` will be raised).
 
-   *path* need not exist.
+   The *optimization* parameter is used to specify the optimization level of the
+   bytecode file. An empty string represents no optimization, so
+   ``/foo/bar/baz.py`` with an *optimization* of ``''`` will result in a
+   bytecode path of ``/foo/bar/__pycache__/baz.cpython-32.pyc``. ``None`` causes
+   the interpter's optimization level to be used. Any other value's string
+   representation being used, so ``/foo/bar/baz.py`` with an *optimization* of
+   ``2`` will lead to the bytecode path of
+   ``/foo/bar/__pycache__/baz.cpython-32.opt-2.pyc``. The string representation
+   of *optimization* can only be alphanumeric, else :exc:`ValueError` is raised.
+
+   The *debug_override* parameter is deprecated and can be used to override
+   the system's value for ``__debug__``. A ``True`` value is the equivalent of
+   setting *optimization* to the empty string. A ``False`` value is the same as
+   setting *optimization* to ``1``. If both *debug_override* an *optimization*
+   are not ``None`` then :exc:`TypeError` is raised.
 
    .. versionadded:: 3.4
 
+   .. versionchanged ::3.5
+      The *optimization* parameter was added and the *debug_override* parameter
+      was deprecated.
+
 
 .. function:: source_from_cache(path)
 
@@ -1098,7 +1120,7 @@
    file path.  For example, if *path* is
    ``/foo/bar/__pycache__/baz.cpython-32.pyc`` the returned path would be
    ``/foo/bar/baz.py``.  *path* need not exist, however if it does not conform
-   to :pep:`3147` format, a ``ValueError`` is raised. If
+   to :pep:`3147` or :pep`488` format, a ``ValueError`` is raised. If
    :attr:`sys.implementation.cache_tag` is not defined,
    :exc:`NotImplementedError` is raised.
 
diff --git a/Doc/library/py_compile.rst b/Doc/library/py_compile.rst
--- a/Doc/library/py_compile.rst
+++ b/Doc/library/py_compile.rst
@@ -29,9 +29,9 @@
 .. function:: compile(file, cfile=None, dfile=None, doraise=False, optimize=-1)
 
    Compile a source file to byte-code and write out the byte-code cache file.
-   The source code is loaded from the file name *file*.  The  byte-code is
-   written to *cfile*, which defaults to the :PEP:`3147` path, ending in
-   ``.pyc`` (``.pyo`` if optimization is enabled in the current interpreter).
+   The source code is loaded from the file name *file*.  The byte-code is
+   written to *cfile*, which defaults to the :pep:`3147`/:pep`488` path, ending
+   in ``.pyc``.
    For example, if *file* is ``/foo/bar/baz.py`` *cfile* will default to
    ``/foo/bar/__pycache__/baz.cpython-32.pyc`` for Python 3.2.  If *dfile* is
    specified, it is used as the name of the source file in error messages when
@@ -68,7 +68,7 @@
 .. function:: main(args=None)
 
    Compile several source files.  The files named in *args* (or on the command
-   line, if *args* is ``None``) are compiled and the resulting bytecode is
+   line, if *args* is ``None``) are compiled and the resulting byte-code is
    cached in the normal manner.  This function does not search a directory
    structure to locate source files; it only compiles files named explicitly.
    If ``'-'`` is the only parameter in args, the list of files is taken from
@@ -86,4 +86,3 @@
 
    Module :mod:`compileall`
       Utilities to compile all Python source files in a directory tree.
-
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -167,7 +167,7 @@
 
 .. data:: dont_write_bytecode
 
-   If this is true, Python won't try to write ``.pyc`` or ``.pyo`` files on the
+   If this is true, Python won't try to write ``.pyc`` files on the
    import of source modules.  This value is initially set to ``True`` or
    ``False`` depending on the :option:`-B` command line option and the
    :envvar:`PYTHONDONTWRITEBYTECODE` environment variable, but you can set it
@@ -1231,4 +1231,3 @@
 .. rubric:: Citations
 
 .. [C99] ISO/IEC 9899:1999.  "Programming languages -- C."  A public draft of this standard is available at http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf\ .
-
diff --git a/Doc/library/tracemalloc.rst b/Doc/library/tracemalloc.rst
--- a/Doc/library/tracemalloc.rst
+++ b/Doc/library/tracemalloc.rst
@@ -363,7 +363,7 @@
    Filter on traces of memory blocks.
 
    See the :func:`fnmatch.fnmatch` function for the syntax of
-   *filename_pattern*. The ``'.pyc'`` and ``'.pyo'`` file extensions are
+   *filename_pattern*. The ``'.pyc'`` file extension is
    replaced with ``'.py'``.
 
    Examples:
@@ -374,6 +374,10 @@
      :mod:`tracemalloc` module
    * ``Filter(False, "<unknown>")`` excludes empty tracebacks
 
+
+   .. versionchanged:: 3.5
+      The ``'.pyo'`` file extension is no longer replaced with ``'.py'``.
+
    .. attribute:: inclusive
 
       If *inclusive* is ``True`` (include), only trace memory blocks allocated
@@ -631,4 +635,3 @@
               obj = Object()
             File "test.py", line 12
               tb = tracemalloc.get_object_traceback(f())
-
diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst
--- a/Doc/library/zipfile.rst
+++ b/Doc/library/zipfile.rst
@@ -405,8 +405,7 @@
       archive.
 
       If the *optimize* parameter to :class:`PyZipFile` was not given or ``-1``,
-      the corresponding file is a :file:`\*.pyo` file if available, else a
-      :file:`\*.pyc` file, compiling if necessary.
+      the corresponding file is a :file:`\*.pyc` file, compiling if necessary.
 
       If the *optimize* parameter to :class:`PyZipFile` was ``0``, ``1`` or
       ``2``, only files with that optimization level (see :func:`compile`) are
@@ -569,4 +568,3 @@
 .. attribute:: ZipInfo.file_size
 
    Size of the uncompressed file.
-
diff --git a/Doc/library/zipimport.rst b/Doc/library/zipimport.rst
--- a/Doc/library/zipimport.rst
+++ b/Doc/library/zipimport.rst
@@ -20,10 +20,10 @@
 import from the :file:`lib/` subdirectory within the archive.
 
 Any files may be present in the ZIP archive, but only files :file:`.py` and
-:file:`.py[co]` are available for import.  ZIP import of dynamic modules
+:file:`.pyc` are available for import.  ZIP import of dynamic modules
 (:file:`.pyd`, :file:`.so`) is disallowed. Note that if an archive only contains
 :file:`.py` files, Python will not attempt to modify the archive by adding the
-corresponding :file:`.pyc` or :file:`.pyo` file, meaning that if a ZIP archive
+corresponding :file:`.pyc` file, meaning that if a ZIP archive
 doesn't contain :file:`.pyc` files, importing may be rather slow.
 
 ZIP archives with an archive comment are currently not supported.
@@ -161,4 +161,3 @@
    >>> import jwzthreading
    >>> jwzthreading.__file__
    'example.zip/jwzthreading.py'
-
diff --git a/Doc/reference/import.rst b/Doc/reference/import.rst
--- a/Doc/reference/import.rst
+++ b/Doc/reference/import.rst
@@ -646,7 +646,7 @@
 
 The default set of path entry finders implement all the semantics for finding
 modules on the file system, handling special file types such as Python source
-code (``.py`` files), Python byte code (``.pyc`` and ``.pyo`` files) and
+code (``.py`` files), Python byte code (``.pyc`` files) and
 shared libraries (e.g. ``.so`` files). When supported by the :mod:`zipimport`
 module in the standard library, the default path entry finders also handle
 loading all of these file types (other than shared libraries) from zipfiles.
diff --git a/Doc/tutorial/modules.rst b/Doc/tutorial/modules.rst
--- a/Doc/tutorial/modules.rst
+++ b/Doc/tutorial/modules.rst
@@ -216,15 +216,15 @@
   statements, the ``-OO`` switch removes both assert statements and __doc__
   strings.  Since some programs may rely on having these available, you should
   only use this option if you know what you're doing.  "Optimized" modules have
-  a .pyo rather than a .pyc suffix and are usually smaller.  Future releases may
+  an ``opt-`` tag and are usually smaller.  Future releases may
   change the effects of optimization.
 
-* A program doesn't run any faster when it is read from a ``.pyc`` or ``.pyo``
+* A program doesn't run any faster when it is read from a ``.pyc``
   file than when it is read from a ``.py`` file; the only thing that's faster
-  about ``.pyc`` or ``.pyo`` files is the speed with which they are loaded.
+  about ``.pyc`` files is the speed with which they are loaded.
 
-* The module :mod:`compileall` can create .pyc files (or .pyo files when
-  :option:`-O` is used) for all modules in a directory.
+* The module :mod:`compileall` can create .pyc files for all modules in a
+  directory.
 
 * There is more detail on this process, including a flow chart of the
   decisions, in PEP 3147.
@@ -548,4 +548,3 @@
 .. [#] In fact function definitions are also 'statements' that are 'executed'; the
    execution of a module-level function definition enters the function name in
    the module's global symbol table.
-
diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst
--- a/Doc/using/cmdline.rst
+++ b/Doc/using/cmdline.rst
@@ -199,7 +199,7 @@
 
 .. cmdoption:: -B
 
-   If given, Python won't try to write ``.pyc`` or ``.pyo`` files on the
+   If given, Python won't try to write ``.pyc``` files on the
    import of source modules.  See also :envvar:`PYTHONDONTWRITEBYTECODE`.
 
 
diff --git a/Doc/using/windows.rst b/Doc/using/windows.rst
--- a/Doc/using/windows.rst
+++ b/Doc/using/windows.rst
@@ -111,7 +111,7 @@
 |                           | launcher is also installed.          |                          |
 +---------------------------+--------------------------------------+--------------------------+
 | CompileAll                | Compile all ``.py`` files to         | 0                        |
-|                           | ``.pyc`` and ``.pyo``.               |                          |
+|                           | ``.pyc``.                            |                          |
 +---------------------------+--------------------------------------+--------------------------+
 | PrependPath               | Add install and Scripts directories  | 0                        |
 |                           | tho :envvar:`PATH` and ``.PY`` to    |                          |
@@ -451,7 +451,7 @@
 ^^^^^^^^^^^^^^^^^^^^^^
 
 The launcher should have been associated with Python files (i.e. ``.py``,
-``.pyw``, ``.pyc``, ``.pyo`` files) when it was installed.  This means that
+``.pyw``, ``.pyc`` files) when it was installed.  This means that
 when you double-click on one of these files from Windows explorer the launcher
 will be used, and therefore you can use the same facilities described above to
 have the script specify the version which should be used.
@@ -796,5 +796,3 @@
 
    :pep:`397` - Python launcher for Windows
       The proposal for the launcher to be included in the Python distribution.
-
-
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -88,6 +88,8 @@
   ``surrogateescape`` error handler, instead of the ``strict`` error handler
   (:issue:`19977`).
 
+* :pep:`488`, the elimination of ``.pyo`` files.
+
 Significantly Improved Library Modules:
 
 * None yet.
@@ -195,6 +197,24 @@
 
     :pep:`486` -- Make the Python Launcher aware of virtual environments
 
+
+PEP 488: Elimination of PYO files
+---------------------------------
+
+:pep:`488` does away with the concept of ``.pyo`` files. This means that
+``.pyc`` files represent both unoptimized and optimized bytecode. To prevent
+the need to constantly regenerate bytecode files, ``.pyc`` files now have an
+optional ``opt-`` tag in their name when the bytecode is optimized. This has
+the side-effect of no more bytecode file name clashes when running under either
+``-O`` or ``-OO``, thus allowing unoptimized, ``-O``, and ``-OO`` bytecode files
+to all exist simultaneously. :func:`importlib.util.cache_from_source` has an
+updated API to help with this change.
+
+.. seealso::
+
+   :pep:`488` -- Elimination of PYO files
+
+
 Other Language Changes
 ======================
 
@@ -535,7 +555,7 @@
   ``FindFirstFile``/``FindNextFile`` system calls. (Contributed by
   Ben Hoyt with help from Victor Stinner in :issue:`23605`.)
 
-* Construction of ``bytes(int)`` (filled by zero bytes) is faster and use less
+* Construction of ``bytes(int)`` (filled by zero bytes) is faster and uses less
   memory for large objects. ``calloc()`` is used instead of ``malloc()`` to
   allocate memory for these objects.
 
@@ -630,6 +650,8 @@
   3.4, and has now been removed.
   (Contributed by Matt Chaput in :issue:`6623`.)
 
+* The concept of ``.pyo`` files has been removed.
+
 * The JoinableQueue class in the provisional asyncio module was deprecated
   in 3.4.4 and is now removed (:issue:`23464`).
 
@@ -759,6 +781,17 @@
   *LegalChars* parameter of :func:`~http.cookies.Morsel.set` is deprecated and
   is now ignored.  (:issue:`2211`)
 
+* :pep:`488` has removed ``.pyo`` files from Python and introduced the optional
+  ``opt-`` tag in ``.pyc`` file names. The
+  :func:`importlib.util.cache_from_source` has gained an *optimization*
+  parameter to help control the ``opt-`` tag. Because of this, the
+  *debug_override* parameter of the function is now deprecated. `.pyo` files
+  are also no longer supported as a file argument to the Python interpreter and
+  thus serve no purpose when distributed on their own (i.e. sourcless code
+  distribution). Due to the fact that the magic number for bytecode has changed
+  in Python 3.5, all old `.pyo` files from previous versions of Python are
+  invalid regardless of this PEP.
+
 Changes in the C API
 --------------------
 
diff --git a/Lib/compileall.py b/Lib/compileall.py
--- a/Lib/compileall.py
+++ b/Lib/compileall.py
@@ -1,4 +1,4 @@
-"""Module/script to byte-compile all .py files to .pyc (or .pyo) files.
+"""Module/script to byte-compile all .py files to .pyc files.
 
 When called as a script with arguments, this compiles the directories
 given as arguments recursively; the -l option prevents it from
@@ -118,11 +118,12 @@
             return success
     if os.path.isfile(fullname):
         if legacy:
-            cfile = fullname + ('c' if __debug__ else 'o')
+            cfile = fullname + 'c'
         else:
             if optimize >= 0:
+                opt = optimize if optimize >= 1 else ''
                 cfile = importlib.util.cache_from_source(
-                                fullname, debug_override=not optimize)
+                                fullname, optimization=opt)
             else:
                 cfile = importlib.util.cache_from_source(fullname)
             cache_dir = os.path.dirname(cfile)
diff --git a/Lib/distutils/command/build_py.py b/Lib/distutils/command/build_py.py
--- a/Lib/distutils/command/build_py.py
+++ b/Lib/distutils/command/build_py.py
@@ -314,10 +314,10 @@
             if include_bytecode:
                 if self.compile:
                     outputs.append(importlib.util.cache_from_source(
-                        filename, debug_override=True))
+                        filename, optimization=''))
                 if self.optimize > 0:
                     outputs.append(importlib.util.cache_from_source(
-                        filename, debug_override=False))
+                        filename, optimization=self.optimize))
 
         outputs += [
             os.path.join(build_dir, filename)
diff --git a/Lib/distutils/command/install_lib.py b/Lib/distutils/command/install_lib.py
--- a/Lib/distutils/command/install_lib.py
+++ b/Lib/distutils/command/install_lib.py
@@ -22,15 +22,15 @@
     # possible scenarios:
     #   1) no compilation at all (--no-compile --no-optimize)
     #   2) compile .pyc only (--compile --no-optimize; default)
-    #   3) compile .pyc and "level 1" .pyo (--compile --optimize)
-    #   4) compile "level 1" .pyo only (--no-compile --optimize)
-    #   5) compile .pyc and "level 2" .pyo (--compile --optimize-more)
-    #   6) compile "level 2" .pyo only (--no-compile --optimize-more)
+    #   3) compile .pyc and "opt-1" .pyc (--compile --optimize)
+    #   4) compile "opt-1" .pyc only (--no-compile --optimize)
+    #   5) compile .pyc and "opt-2" .pyc (--compile --optimize-more)
+    #   6) compile "opt-2" .pyc only (--no-compile --optimize-more)
     #
-    # The UI for this is two option, 'compile' and 'optimize'.
+    # The UI for this is two options, 'compile' and 'optimize'.
     # 'compile' is strictly boolean, and only decides whether to
     # generate .pyc files.  'optimize' is three-way (0, 1, or 2), and
-    # decides both whether to generate .pyo files and what level of
+    # decides both whether to generate .pyc files and what level of
     # optimization to use.
 
     user_options = [
@@ -166,10 +166,10 @@
                 continue
             if self.compile:
                 bytecode_files.append(importlib.util.cache_from_source(
-                    py_file, debug_override=True))
+                    py_file, optimization=''))
             if self.optimize > 0:
                 bytecode_files.append(importlib.util.cache_from_source(
-                    py_file, debug_override=False))
+                    py_file, optimization=self.optimize))
 
         return bytecode_files
 
diff --git a/Lib/distutils/tests/test_build_py.py b/Lib/distutils/tests/test_build_py.py
--- a/Lib/distutils/tests/test_build_py.py
+++ b/Lib/distutils/tests/test_build_py.py
@@ -120,8 +120,8 @@
         found = os.listdir(cmd.build_lib)
         self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py'])
         found = os.listdir(os.path.join(cmd.build_lib, '__pycache__'))
-        self.assertEqual(sorted(found),
-                         ['boiledeggs.%s.pyo' % sys.implementation.cache_tag])
+        expect = 'boiledeggs.{}.opt-1.pyc'.format(sys.implementation.cache_tag)
+        self.assertEqual(sorted(found), [expect])
 
     def test_dir_in_package_data(self):
         """
diff --git a/Lib/distutils/tests/test_install_lib.py b/Lib/distutils/tests/test_install_lib.py
--- a/Lib/distutils/tests/test_install_lib.py
+++ b/Lib/distutils/tests/test_install_lib.py
@@ -44,12 +44,11 @@
         f = os.path.join(project_dir, 'foo.py')
         self.write_file(f, '# python file')
         cmd.byte_compile([f])
-        pyc_file = importlib.util.cache_from_source('foo.py',
-                                                    debug_override=True)
-        pyo_file = importlib.util.cache_from_source('foo.py',
-                                                    debug_override=False)
+        pyc_file = importlib.util.cache_from_source('foo.py', optimization='')
+        pyc_opt_file = importlib.util.cache_from_source('foo.py',
+                                                    optimization=cmd.optimize)
         self.assertTrue(os.path.exists(pyc_file))
-        self.assertTrue(os.path.exists(pyo_file))
+        self.assertTrue(os.path.exists(pyc_opt_file))
 
     def test_get_outputs(self):
         project_dir, dist = self.create_dist()
@@ -66,8 +65,8 @@
         cmd.distribution.packages = ['spam']
         cmd.distribution.script_name = 'setup.py'
 
-        # get_outputs should return 4 elements: spam/__init__.py, .pyc and
-        # .pyo, foo.import-tag-abiflags.so / foo.pyd
+        # get_outputs should return 4 elements: spam/__init__.py and .pyc,
+        # foo.import-tag-abiflags.so / foo.pyd
         outputs = cmd.get_outputs()
         self.assertEqual(len(outputs), 4, outputs)
 
diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py
--- a/Lib/distutils/util.py
+++ b/Lib/distutils/util.py
@@ -322,11 +322,11 @@
                   prefix=None, base_dir=None,
                   verbose=1, dry_run=0,
                   direct=None):
-    """Byte-compile a collection of Python source files to either .pyc
-    or .pyo files in a __pycache__ subdirectory.  'py_files' is a list
+    """Byte-compile a collection of Python source files to .pyc
+    files in a __pycache__ subdirectory.  'py_files' is a list
     of files to compile; any files that don't end in ".py" are silently
     skipped.  'optimize' must be one of the following:
-      0 - don't optimize (generate .pyc)
+      0 - don't optimize
       1 - normal optimization (like "python -O")
       2 - extra optimization (like "python -OO")
     If 'force' is true, all files are recompiled regardless of
@@ -438,8 +438,9 @@
             #   cfile - byte-compiled file
             #   dfile - purported source filename (same as 'file' by default)
             if optimize >= 0:
+                opt = '' if optimize == 0 else optimize
                 cfile = importlib.util.cache_from_source(
-                    file, debug_override=not optimize)
+                    file, optimization=opt)
             else:
                 cfile = importlib.util.cache_from_source(file)
             dfile = file
diff --git a/Lib/doctest.py b/Lib/doctest.py
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -1051,7 +1051,7 @@
             filename = None
         else:
             filename = getattr(module, '__file__', module.__name__)
-            if filename[-4:] in (".pyc", ".pyo"):
+            if filename[-4:] == ".pyc":
                 filename = filename[:-1]
         return self._parser.get_doctest(docstring, globs, name,
                                         filename, lineno)
@@ -2378,7 +2378,7 @@
             continue
         if not test.filename:
             filename = module.__file__
-            if filename[-4:] in (".pyc", ".pyo"):
+            if filename[-4:] == ".pyc":
                 filename = filename[:-1]
             test.filename = filename
         suite.addTest(DocTestCase(test, **options))
diff --git a/Lib/imp.py b/Lib/imp.py
--- a/Lib/imp.py
+++ b/Lib/imp.py
@@ -58,24 +58,23 @@
 def get_magic():
     """**DEPRECATED**
 
-    Return the magic number for .pyc or .pyo files.
+    Return the magic number for .pyc files.
     """
     return util.MAGIC_NUMBER
 
 
 def get_tag():
-    """Return the magic tag for .pyc or .pyo files."""
+    """Return the magic tag for .pyc files."""
     return sys.implementation.cache_tag
 
 
 def cache_from_source(path, debug_override=None):
     """**DEPRECATED**
 
-    Given the path to a .py file, return the path to its .pyc/.pyo file.
+    Given the path to a .py file, return the path to its .pyc file.
 
     The .py file does not need to exist; this simply returns the path to the
-    .pyc/.pyo file calculated as if the .py file were imported.  The extension
-    will be .pyc unless sys.flags.optimize is non-zero, then it will be .pyo.
+    .pyc file calculated as if the .py file were imported.
 
     If debug_override is not None, then it must be a boolean and is used in
     place of sys.flags.optimize.
@@ -83,16 +82,18 @@
     If sys.implementation.cache_tag is None then NotImplementedError is raised.
 
     """
-    return util.cache_from_source(path, debug_override)
+    with warnings.catch_warnings():
+        warnings.simplefilter('ignore')
+        return util.cache_from_source(path, debug_override)
 
 
 def source_from_cache(path):
     """**DEPRECATED**
 
-    Given the path to a .pyc./.pyo file, return the path to its .py file.
+    Given the path to a .pyc. file, return the path to its .py file.
 
-    The .pyc/.pyo file does not need to exist; this simply returns the path to
-    the .py file calculated to correspond to the .pyc/.pyo file.  If path does
+    The .pyc file does not need to exist; this simply returns the path to
+    the .py file calculated to correspond to the .pyc file.  If path does
     not conform to PEP 3147 format, ValueError will be raised. If
     sys.implementation.cache_tag is None then NotImplementedError is raised.
 
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -429,45 +429,64 @@
 _RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little')  # For import.c
 
 _PYCACHE = '__pycache__'
+_OPT = 'opt-'
 
 SOURCE_SUFFIXES = ['.py']  # _setup() adds .pyw as needed.
 
-DEBUG_BYTECODE_SUFFIXES = ['.pyc']
-OPTIMIZED_BYTECODE_SUFFIXES = ['.pyo']
-
-def cache_from_source(path, debug_override=None):
-    """Given the path to a .py file, return the path to its .pyc/.pyo file.
+BYTECODE_SUFFIXES = ['.pyc']
+# Deprecated.
+DEBUG_BYTECODE_SUFFIXES = OPTIMIZED_BYTECODE_SUFFIXES = BYTECODE_SUFFIXES
+
+def cache_from_source(path, debug_override=None, *, optimization=None):
+    """Given the path to a .py file, return the path to its .pyc file.
 
     The .py file does not need to exist; this simply returns the path to the
-    .pyc/.pyo file calculated as if the .py file were imported.  The extension
-    will be .pyc unless sys.flags.optimize is non-zero, then it will be .pyo.
-
-    If debug_override is not None, then it must be a boolean and is used in
-    place of sys.flags.optimize.
+    .pyc file calculated as if the .py file were imported.
+
+    The 'optimization' parameter controls the presumed optimization level of
+    the bytecode file. If 'optimization' is not None, the string representation
+    of the argument is taken and verified to be alphanumeric (else ValueError
+    is raised).
+
+    The debug_override parameter is deprecated. If debug_override is not None,
+    a True value is the same as setting 'optimization' to the empty string
+    while a False value is equivalent to setting 'optimization' to '1'.
 
     If sys.implementation.cache_tag is None then NotImplementedError is raised.
 
     """
-    debug = not sys.flags.optimize if debug_override is None else debug_override
-    if debug:
-        suffixes = DEBUG_BYTECODE_SUFFIXES
-    else:
-        suffixes = OPTIMIZED_BYTECODE_SUFFIXES
+    if debug_override is not None:
+        _warnings.warn('the debug_override parameter is deprecated; use '
+                       "'optimization' instead", DeprecationWarning)
+        if optimization is not None:
+            message = 'debug_override or optimization must be set to None'
+            raise TypeError(message)
+        optimization = '' if debug_override else 1
     head, tail = _path_split(path)
     base, sep, rest = tail.rpartition('.')
     tag = sys.implementation.cache_tag
     if tag is None:
         raise NotImplementedError('sys.implementation.cache_tag is None')
-    filename = ''.join([(base if base else rest), sep, tag, suffixes[0]])
-    return _path_join(head, _PYCACHE, filename)
+    almost_filename = ''.join([(base if base else rest), sep, tag])
+    if optimization is None:
+        if sys.flags.optimize == 0:
+            optimization = ''
+        else:
+            optimization = sys.flags.optimize
+    optimization = str(optimization)
+    if optimization != '':
+        if not optimization.isalnum():
+            raise ValueError('{!r} is not alphanumeric'.format(optimization))
+        almost_filename = '{}.{}{}'.format(almost_filename, _OPT, optimization)
+    return _path_join(head, _PYCACHE, almost_filename + BYTECODE_SUFFIXES[0])
 
 
 def source_from_cache(path):
-    """Given the path to a .pyc./.pyo file, return the path to its .py file.
-
-    The .pyc/.pyo file does not need to exist; this simply returns the path to
-    the .py file calculated to correspond to the .pyc/.pyo file.  If path does
-    not conform to PEP 3147 format, ValueError will be raised. If
+    """Given the path to a .pyc. file, return the path to its .py file.
+
+    The .pyc file does not need to exist; this simply returns the path to
+    the .py file calculated to correspond to the .pyc file.  If path does
+    not conform to PEP 3147/488 format, ValueError will be raised. If
     sys.implementation.cache_tag is None then NotImplementedError is raised.
 
     """
@@ -478,9 +497,19 @@
     if pycache != _PYCACHE:
         raise ValueError('{} not bottom-level directory in '
                          '{!r}'.format(_PYCACHE, path))
-    if pycache_filename.count('.') != 2:
-        raise ValueError('expected only 2 dots in '
+    dot_count = pycache_filename.count('.')
+    if dot_count not in {2, 3}:
+        raise ValueError('expected only 2 or 3 dots in '
                          '{!r}'.format(pycache_filename))
+    elif dot_count == 3:
+        optimization = pycache_filename.rsplit('.', 2)[-2]
+        if not optimization.startswith(_OPT):
+            raise ValueError("optimization portion of filename does not start "
+                             "with {!r}".format(_OPT))
+        opt_level = optimization[len(_OPT):]
+        if not opt_level.isalnum():
+            raise ValueError("optimization level {!r} is not an alphanumeric "
+                             "value".format(optimization))
     base_filename = pycache_filename.partition('.')[0]
     return _path_join(head, base_filename + SOURCE_SUFFIXES[0])
 
@@ -2337,15 +2366,10 @@
     modules, those two modules must be explicitly passed in.
 
     """
-    global _imp, sys, BYTECODE_SUFFIXES
+    global _imp, sys
     _imp = _imp_module
     sys = sys_module
 
-    if sys.flags.optimize:
-        BYTECODE_SUFFIXES = OPTIMIZED_BYTECODE_SUFFIXES
-    else:
-        BYTECODE_SUFFIXES = DEBUG_BYTECODE_SUFFIXES
-
     # Set up the spec for existing builtin/frozen modules.
     module_type = type(sys)
     for name, module in sys.modules.items():
diff --git a/Lib/modulefinder.py b/Lib/modulefinder.py
--- a/Lib/modulefinder.py
+++ b/Lib/modulefinder.py
@@ -223,7 +223,7 @@
         if not m.__path__:
             return
         modules = {}
-        # 'suffixes' used to be a list hardcoded to [".py", ".pyc", ".pyo"].
+        # 'suffixes' used to be a list hardcoded to [".py", ".pyc"].
         # But we must also collect Python extension modules - although
         # we cannot separate normal dlls from Python extensions.
         suffixes = []
diff --git a/Lib/msilib/__init__.py b/Lib/msilib/__init__.py
--- a/Lib/msilib/__init__.py
+++ b/Lib/msilib/__init__.py
@@ -361,7 +361,7 @@
         #             [(logical, 0, filehash.IntegerData(1),
         #               filehash.IntegerData(2), filehash.IntegerData(3),
         #               filehash.IntegerData(4))])
-        # Automatically remove .pyc/.pyo files on uninstall (2)
+        # Automatically remove .pyc files on uninstall (2)
         # XXX: adding so many RemoveFile entries makes installer unbelievably
         # slow. So instead, we have to use wildcard remove entries
         if file.endswith(".py"):
@@ -382,10 +382,9 @@
         return files
 
     def remove_pyc(self):
-        "Remove .pyc/.pyo files on uninstall"
+        "Remove .pyc files on uninstall"
         add_data(self.db, "RemoveFile",
-                 [(self.component+"c", self.component, "*.pyc", self.logical, 2),
-                  (self.component+"o", self.component, "*.pyo", self.logical, 2)])
+                 [(self.component+"c", self.component, "*.pyc", self.logical, 2)])
 
 class Binary:
     def __init__(self, fname):
diff --git a/Lib/py_compile.py b/Lib/py_compile.py
--- a/Lib/py_compile.py
+++ b/Lib/py_compile.py
@@ -1,4 +1,4 @@
-"""Routine to "compile" a .py file to a .pyc (or .pyo) file.
+"""Routine to "compile" a .py file to a .pyc file.
 
 This module has intimate knowledge of the format of .pyc files.
 """
@@ -67,7 +67,7 @@
 
     :param file: The source file name.
     :param cfile: The target byte compiled file name.  When not given, this
-        defaults to the PEP 3147 location.
+        defaults to the PEP 3147/PEP 488 location.
     :param dfile: Purported file name, i.e. the file name that shows up in
         error messages.  Defaults to the source file name.
     :param doraise: Flag indicating whether or not an exception should be
@@ -85,12 +85,12 @@
     Note that it isn't necessary to byte-compile Python modules for
     execution efficiency -- Python itself byte-compiles a module when
     it is loaded, and if it can, writes out the bytecode to the
-    corresponding .pyc (or .pyo) file.
+    corresponding .pyc file.
 
     However, if a Python installation is shared between users, it is a
     good idea to byte-compile all modules upon installation, since
     other users may not be able to write in the source directories,
-    and thus they won't be able to write the .pyc/.pyo file, and then
+    and thus they won't be able to write the .pyc file, and then
     they would be byte-compiling every module each time it is loaded.
     This can slow down program start-up considerably.
 
@@ -105,8 +105,9 @@
     """
     if cfile is None:
         if optimize >= 0:
+            optimization = optimize if optimize >= 1 else ''
             cfile = importlib.util.cache_from_source(file,
-                                                     debug_override=not optimize)
+                                                     optimization=optimization)
         else:
             cfile = importlib.util.cache_from_source(file)
     if os.path.islink(cfile):
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -213,7 +213,7 @@
 def ispackage(path):
     """Guess whether a path refers to a package directory."""
     if os.path.isdir(path):
-        for ext in ('.py', '.pyc', '.pyo'):
+        for ext in ('.py', '.pyc'):
             if os.path.isfile(os.path.join(path, '__init__' + ext)):
                 return True
     return False
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -376,36 +376,32 @@
         pass
 
 def make_legacy_pyc(source):
-    """Move a PEP 3147 pyc/pyo file to its legacy pyc/pyo location.
-
-    The choice of .pyc or .pyo extension is done based on the __debug__ flag
-    value.
+    """Move a PEP 3147/488 pyc file to its legacy pyc location.
 
     :param source: The file system path to the source file.  The source file
-        does not need to exist, however the PEP 3147 pyc file must exist.
+        does not need to exist, however the PEP 3147/488 pyc file must exist.
     :return: The file system path to the legacy pyc file.
     """
     pyc_file = importlib.util.cache_from_source(source)
     up_one = os.path.dirname(os.path.abspath(source))
-    legacy_pyc = os.path.join(up_one, source + ('c' if __debug__ else 'o'))
+    legacy_pyc = os.path.join(up_one, source + 'c')
     os.rename(pyc_file, legacy_pyc)
     return legacy_pyc
 
 def forget(modname):
     """'Forget' a module was ever imported.
 
-    This removes the module from sys.modules and deletes any PEP 3147 or
-    legacy .pyc and .pyo files.
+    This removes the module from sys.modules and deletes any PEP 3147/488 or
+    legacy .pyc files.
     """
     unload(modname)
     for dirname in sys.path:
         source = os.path.join(dirname, modname + '.py')
         # It doesn't matter if they exist or not, unlink all possible
-        # combinations of PEP 3147 and legacy pyc and pyo files.
+        # combinations of PEP 3147/488 and legacy pyc files.
         unlink(source + 'c')
-        unlink(source + 'o')
-        unlink(importlib.util.cache_from_source(source, debug_override=True))
-        unlink(importlib.util.cache_from_source(source, debug_override=False))
+        for opt in ('', 1, 2):
+            unlink(importlib.util.cache_from_source(source, optimization=opt))
 
 # Check whether a gui is actually available
 def _is_gui_available():
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -425,7 +425,7 @@
 
     def test_compile_ast(self):
         fname = __file__
-        if fname.lower().endswith(('pyc', 'pyo')):
+        if fname.lower().endswith('pyc'):
             fname = fname[:-1]
         with open(fname, 'r') as f:
             fcontents = f.read()
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -101,16 +101,16 @@
     def test_optimize(self):
         # make sure compiling with different optimization settings than the
         # interpreter's creates the correct file names
-        optimize = 1 if __debug__ else 0
+        optimize, opt = (1, 1) if __debug__ else (0, '')
         compileall.compile_dir(self.directory, quiet=True, optimize=optimize)
         cached = importlib.util.cache_from_source(self.source_path,
-                                                  debug_override=not optimize)
+                                                  optimization=opt)
         self.assertTrue(os.path.isfile(cached))
         cached2 = importlib.util.cache_from_source(self.source_path2,
-                                                   debug_override=not optimize)
+                                                   optimization=opt)
         self.assertTrue(os.path.isfile(cached2))
         cached3 = importlib.util.cache_from_source(self.source_path3,
-                                                   debug_override=not optimize)
+                                                   optimization=opt)
         self.assertTrue(os.path.isfile(cached3))
 
     @mock.patch('compileall.ProcessPoolExecutor')
@@ -237,11 +237,11 @@
         self.assertNotIn(b'Listing ', quiet)
 
     # Ensure that the default behavior of compileall's CLI is to create
-    # PEP 3147 pyc/pyo files.
+    # PEP 3147/PEP 488 pyc files.
     for name, ext, switch in [
         ('normal', 'pyc', []),
-        ('optimize', 'pyo', ['-O']),
-        ('doubleoptimize', 'pyo', ['-OO']),
+        ('optimize', 'opt-1.pyc', ['-O']),
+        ('doubleoptimize', 'opt-2.pyc', ['-OO']),
     ]:
         def f(self, ext=ext, switch=switch):
             script_helper.assert_python_ok(*(switch +
@@ -258,13 +258,12 @@
 
     def test_legacy_paths(self):
         # Ensure that with the proper switch, compileall leaves legacy
-        # pyc/pyo files, and no __pycache__ directory.
+        # pyc files, and no __pycache__ directory.
         self.assertRunOK('-b', '-q', self.pkgdir)
         # Verify the __pycache__ directory contents.
         self.assertFalse(os.path.exists(self.pkgdir_cachedir))
-        opt = 'c' if __debug__ else 'o'
-        expected = sorted(['__init__.py', '__init__.py' + opt, 'bar.py',
-                           'bar.py' + opt])
+        expected = sorted(['__init__.py', '__init__.pyc', 'bar.py',
+                           'bar.pyc'])
         self.assertEqual(sorted(os.listdir(self.pkgdir)), expected)
 
     def test_multiple_runs(self):
diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py
--- a/Lib/test/test_imp.py
+++ b/Lib/test/test_imp.py
@@ -111,7 +111,6 @@
             del sys.path[0]
             support.unlink(temp_mod_name + '.py')
             support.unlink(temp_mod_name + '.pyc')
-            support.unlink(temp_mod_name + '.pyo')
 
     def test_issue5604(self):
         # Test cannot cover imp.load_compiled function.
@@ -194,7 +193,7 @@
             self.assertEqual(package.b, 2)
         finally:
             del sys.path[0]
-            for ext in ('.py', '.pyc', '.pyo'):
+            for ext in ('.py', '.pyc'):
                 support.unlink(temp_mod_name + ext)
                 support.unlink(init_file_name + ext)
             support.rmtree(test_package_name)
@@ -346,56 +345,6 @@
                               'qux.{}.pyc'.format(self.tag))
         self.assertEqual(imp.cache_from_source(path, True), expect)
 
-    def test_cache_from_source_no_cache_tag(self):
-        # Non cache tag means NotImplementedError.
-        with support.swap_attr(sys.implementation, 'cache_tag', None):
-            with self.assertRaises(NotImplementedError):
-                imp.cache_from_source('whatever.py')
-
-    def test_cache_from_source_no_dot(self):
-        # Directory with a dot, filename without dot.
-        path = os.path.join('foo.bar', 'file')
-        expect = os.path.join('foo.bar', '__pycache__',
-                              'file{}.pyc'.format(self.tag))
-        self.assertEqual(imp.cache_from_source(path, True), expect)
-
-    def test_cache_from_source_optimized(self):
-        # Given the path to a .py file, return the path to its PEP 3147
-        # defined .pyo file (i.e. under __pycache__).
-        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
-        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
-                              'qux.{}.pyo'.format(self.tag))
-        self.assertEqual(imp.cache_from_source(path, False), expect)
-
-    def test_cache_from_source_cwd(self):
-        path = 'foo.py'
-        expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
-        self.assertEqual(imp.cache_from_source(path, True), expect)
-
-    def test_cache_from_source_override(self):
-        # When debug_override is not None, it can be any true-ish or false-ish
-        # value.
-        path = os.path.join('foo', 'bar', 'baz.py')
-        partial_expect = os.path.join('foo', 'bar', '__pycache__',
-                                      'baz.{}.py'.format(self.tag))
-        self.assertEqual(imp.cache_from_source(path, []), partial_expect + 'o')
-        self.assertEqual(imp.cache_from_source(path, [17]),
-                         partial_expect + 'c')
-        # However if the bool-ishness can't be determined, the exception
-        # propagates.
-        class Bearish:
-            def __bool__(self): raise RuntimeError
-        with self.assertRaises(RuntimeError):
-            imp.cache_from_source('/foo/bar/baz.py', Bearish())
-
-    @unittest.skipUnless(os.sep == '\\' and os.altsep == '/',
-                     'test meaningful only where os.altsep is defined')
-    def test_sep_altsep_and_sep_cache_from_source(self):
-        # Windows path and PEP 3147 where sep is right of altsep.
-        self.assertEqual(
-            imp.cache_from_source('\\foo\\bar\\baz/qux.py', True),
-            '\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag))
-
     @unittest.skipUnless(sys.implementation.cache_tag is not None,
                          'requires sys.implementation.cache_tag to not be '
                          'None')
@@ -407,68 +356,6 @@
         expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
         self.assertEqual(imp.source_from_cache(path), expect)
 
-    def test_source_from_cache_no_cache_tag(self):
-        # If sys.implementation.cache_tag is None, raise NotImplementedError.
-        path = os.path.join('blah', '__pycache__', 'whatever.pyc')
-        with support.swap_attr(sys.implementation, 'cache_tag', None):
-            with self.assertRaises(NotImplementedError):
-                imp.source_from_cache(path)
-
-    def test_source_from_cache_bad_path(self):
-        # When the path to a pyc file is not in PEP 3147 format, a ValueError
-        # is raised.
-        self.assertRaises(
-            ValueError, imp.source_from_cache, '/foo/bar/bazqux.pyc')
-
-    def test_source_from_cache_no_slash(self):
-        # No slashes at all in path -> ValueError
-        self.assertRaises(
-            ValueError, imp.source_from_cache, 'foo.cpython-32.pyc')
-
-    def test_source_from_cache_too_few_dots(self):
-        # Too few dots in final path component -> ValueError
-        self.assertRaises(
-            ValueError, imp.source_from_cache, '__pycache__/foo.pyc')
-
-    def test_source_from_cache_too_many_dots(self):
-        # Too many dots in final path component -> ValueError
-        self.assertRaises(
-            ValueError, imp.source_from_cache,
-            '__pycache__/foo.cpython-32.foo.pyc')
-
-    def test_source_from_cache_no__pycache__(self):
-        # Another problem with the path -> ValueError
-        self.assertRaises(
-            ValueError, imp.source_from_cache,
-            '/foo/bar/foo.cpython-32.foo.pyc')
-
-    def test_package___file__(self):
-        try:
-            m = __import__('pep3147')
-        except ImportError:
-            pass
-        else:
-            self.fail("pep3147 module already exists: %r" % (m,))
-        # Test that a package's __file__ points to the right source directory.
-        os.mkdir('pep3147')
-        sys.path.insert(0, os.curdir)
-        def cleanup():
-            if sys.path[0] == os.curdir:
-                del sys.path[0]
-            shutil.rmtree('pep3147')
-        self.addCleanup(cleanup)
-        # Touch the __init__.py file.
-        support.create_empty_file('pep3147/__init__.py')
-        importlib.invalidate_caches()
-        expected___file__ = os.sep.join(('.', 'pep3147', '__init__.py'))
-        m = __import__('pep3147')
-        self.assertEqual(m.__file__, expected___file__, (m.__file__, m.__path__, sys.path, sys.path_importer_cache))
-        # Ensure we load the pyc file.
-        support.unload('pep3147')
-        m = __import__('pep3147')
-        support.unload('pep3147')
-        self.assertEqual(m.__file__, expected___file__, (m.__file__, m.__path__, sys.path, sys.path_importer_cache))
-
 
 class NullImporterTests(unittest.TestCase):
     @unittest.skipIf(support.TESTFN_UNENCODABLE is None,
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -32,7 +32,6 @@
 def remove_files(name):
     for f in (name + ".py",
               name + ".pyc",
-              name + ".pyo",
               name + ".pyw",
               name + "$py.class"):
         unlink(f)
@@ -84,7 +83,6 @@
         def test_with_extension(ext):
             # The extension is normally ".py", perhaps ".pyw".
             source = TESTFN + ext
-            pyo = TESTFN + ".pyo"
             if is_jython:
                 pyc = TESTFN + "$py.class"
             else:
@@ -115,7 +113,6 @@
                 forget(TESTFN)
                 unlink(source)
                 unlink(pyc)
-                unlink(pyo)
 
         sys.path.insert(0, os.curdir)
         try:
@@ -138,7 +135,7 @@
             f.write(']')
 
         try:
-            # Compile & remove .py file; we only need .pyc (or .pyo).
+            # Compile & remove .py file; we only need .pyc.
             # Bytecode must be relocated from the PEP 3147 bytecode-only location.
             py_compile.compile(filename)
         finally:
@@ -252,7 +249,7 @@
             importlib.invalidate_caches()
             mod = __import__(TESTFN)
             base, ext = os.path.splitext(mod.__file__)
-            self.assertIn(ext, ('.pyc', '.pyo'))
+            self.assertEqual(ext, '.pyc')
         finally:
             del sys.path[0]
             remove_files(TESTFN)
@@ -328,7 +325,7 @@
 
 @skip_if_dont_write_bytecode
 class FilePermissionTests(unittest.TestCase):
-    # tests for file mode on cached .pyc/.pyo files
+    # tests for file mode on cached .pyc files
 
     @unittest.skipUnless(os.name == 'posix',
                          "test meaningful only on posix systems")
@@ -339,7 +336,7 @@
             module = __import__(name)
             if not os.path.exists(cached_path):
                 self.fail("__import__ did not result in creation of "
-                          "either a .pyc or .pyo file")
+                          "a .pyc file")
             stat_info = os.stat(cached_path)
 
         # Check that the umask is respected, and the executable bits
@@ -358,7 +355,7 @@
             __import__(name)
             if not os.path.exists(cached_path):
                 self.fail("__import__ did not result in creation of "
-                          "either a .pyc or .pyo file")
+                          "a .pyc file")
             stat_info = os.stat(cached_path)
 
         self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(mode))
@@ -373,7 +370,7 @@
             __import__(name)
             if not os.path.exists(cached_path):
                 self.fail("__import__ did not result in creation of "
-                          "either a .pyc or .pyo file")
+                          "a .pyc file")
             stat_info = os.stat(cached_path)
 
         expected = mode | 0o200 # Account for fix for issue #6074
@@ -404,10 +401,7 @@
             unlink(path)
             unload(name)
             importlib.invalidate_caches()
-            if __debug__:
-                bytecode_only = path + "c"
-            else:
-                bytecode_only = path + "o"
+            bytecode_only = path + "c"
             os.rename(importlib.util.cache_from_source(path), bytecode_only)
             m = __import__(name)
             self.assertEqual(m.x, 'rewritten')
@@ -631,9 +625,7 @@
 
 
 class PycacheTests(unittest.TestCase):
-    # Test the various PEP 3147 related behaviors.
-
-    tag = sys.implementation.cache_tag
+    # Test the various PEP 3147/488-related behaviors.
 
     def _clean(self):
         forget(TESTFN)
@@ -658,9 +650,10 @@
         self.assertFalse(os.path.exists('__pycache__'))
         __import__(TESTFN)
         self.assertTrue(os.path.exists('__pycache__'))
-        self.assertTrue(os.path.exists(os.path.join(
-            '__pycache__', '{}.{}.py{}'.format(
-            TESTFN, self.tag, 'c' if __debug__ else 'o'))))
+        pyc_path = importlib.util.cache_from_source(self.source)
+        self.assertTrue(os.path.exists(pyc_path),
+                        'bytecode file {!r} for {!r} does not '
+                        'exist'.format(pyc_path, TESTFN))
 
     @unittest.skipUnless(os.name == 'posix',
                          "test meaningful only on posix systems")
@@ -673,8 +666,10 @@
         with temp_umask(0o222):
             __import__(TESTFN)
         self.assertTrue(os.path.exists('__pycache__'))
-        self.assertFalse(os.path.exists(os.path.join(
-            '__pycache__', '{}.{}.pyc'.format(TESTFN, self.tag))))
+        pyc_path = importlib.util.cache_from_source(self.source)
+        self.assertFalse(os.path.exists(pyc_path),
+                        'bytecode file {!r} for {!r} '
+                        'exists'.format(pyc_path, TESTFN))
 
     @skip_if_dont_write_bytecode
     def test_missing_source(self):
diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -5,6 +5,7 @@
 importlib_util = util.import_importlib('importlib.util')
 
 import os
+import string
 import sys
 from test import support
 import types
@@ -562,7 +563,8 @@
         path = os.path.join('foo', 'bar', 'baz', 'qux.py')
         expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
                               'qux.{}.pyc'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, True), expect)
+        self.assertEqual(self.util.cache_from_source(path, optimization=''),
+                         expect)
 
     def test_cache_from_source_no_cache_tag(self):
         # No cache tag means NotImplementedError.
@@ -575,43 +577,103 @@
         path = os.path.join('foo.bar', 'file')
         expect = os.path.join('foo.bar', '__pycache__',
                               'file{}.pyc'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, True), expect)
+        self.assertEqual(self.util.cache_from_source(path, optimization=''),
+                         expect)
 
-    def test_cache_from_source_optimized(self):
-        # Given the path to a .py file, return the path to its PEP 3147
-        # defined .pyo file (i.e. under __pycache__).
+    def test_cache_from_source_debug_override(self):
+        # Given the path to a .py file, return the path to its PEP 3147/PEP 488
+        # defined .pyc file (i.e. under __pycache__).
         path = os.path.join('foo', 'bar', 'baz', 'qux.py')
-        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
-                              'qux.{}.pyo'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, False), expect)
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore')
+            self.assertEqual(self.util.cache_from_source(path, False),
+                             self.util.cache_from_source(path, optimization=1))
+            self.assertEqual(self.util.cache_from_source(path, True),
+                             self.util.cache_from_source(path, optimization=''))
+        with warnings.catch_warnings():
+            warnings.simplefilter('error')
+            with self.assertRaises(DeprecationWarning):
+                self.util.cache_from_source(path, False)
+            with self.assertRaises(DeprecationWarning):
+                self.util.cache_from_source(path, True)
 
     def test_cache_from_source_cwd(self):
         path = 'foo.py'
         expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, True), expect)
+        self.assertEqual(self.util.cache_from_source(path, optimization=''),
+                         expect)
 
     def test_cache_from_source_override(self):
         # When debug_override is not None, it can be any true-ish or false-ish
         # value.
         path = os.path.join('foo', 'bar', 'baz.py')
-        partial_expect = os.path.join('foo', 'bar', '__pycache__',
-                                      'baz.{}.py'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, []), partial_expect + 'o')
-        self.assertEqual(self.util.cache_from_source(path, [17]),
-                         partial_expect + 'c')
         # However if the bool-ishness can't be determined, the exception
         # propagates.
         class Bearish:
             def __bool__(self): raise RuntimeError
-        with self.assertRaises(RuntimeError):
-            self.util.cache_from_source('/foo/bar/baz.py', Bearish())
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore')
+            self.assertEqual(self.util.cache_from_source(path, []),
+                             self.util.cache_from_source(path, optimization=1))
+            self.assertEqual(self.util.cache_from_source(path, [17]),
+                             self.util.cache_from_source(path, optimization=''))
+            with self.assertRaises(RuntimeError):
+                self.util.cache_from_source('/foo/bar/baz.py', Bearish())
+
+
+    def test_cache_from_source_optimization_empty_string(self):
+        # Setting 'optimization' to '' leads to no optimization tag (PEP 488).
+        path = 'foo.py'
+        expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
+        self.assertEqual(self.util.cache_from_source(path, optimization=''),
+                         expect)
+
+    def test_cache_from_source_optimization_None(self):
+        # Setting 'optimization' to None uses the interpreter's optimization.
+        # (PEP 488)
+        path = 'foo.py'
+        optimization_level = sys.flags.optimize
+        almost_expect = os.path.join('__pycache__', 'foo.{}'.format(self.tag))
+        if optimization_level == 0:
+            expect = almost_expect + '.pyc'
+        elif optimization_level <= 2:
+            expect = almost_expect + '.opt-{}.pyc'.format(optimization_level)
+        else:
+            msg = '{!r} is a non-standard optimization level'.format(optimization_level)
+            self.skipTest(msg)
+        self.assertEqual(self.util.cache_from_source(path, optimization=None),
+                         expect)
+
+    def test_cache_from_source_optimization_set(self):
+        # The 'optimization' parameter accepts anything that has a string repr
+        # that passes str.alnum().
+        path = 'foo.py'
+        valid_characters = string.ascii_letters + string.digits
+        almost_expect = os.path.join('__pycache__', 'foo.{}'.format(self.tag))
+        got = self.util.cache_from_source(path, optimization=valid_characters)
+        # Test all valid characters are accepted.
+        self.assertEqual(got,
+                         almost_expect + '.opt-{}.pyc'.format(valid_characters))
+        # str() should be called on argument.
+        self.assertEqual(self.util.cache_from_source(path, optimization=42),
+                         almost_expect + '.opt-42.pyc')
+        # Invalid characters raise ValueError.
+        with self.assertRaises(ValueError):
+            self.util.cache_from_source(path, optimization='path/is/bad')
+
+    def test_cache_from_source_debug_override_optimization_both_set(self):
+        # Can only set one of the optimization-related parameters.
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore')
+            with self.assertRaises(TypeError):
+                self.util.cache_from_source('foo.py', False, optimization='')
 
     @unittest.skipUnless(os.sep == '\\' and os.altsep == '/',
                      'test meaningful only where os.altsep is defined')
     def test_sep_altsep_and_sep_cache_from_source(self):
         # Windows path and PEP 3147 where sep is right of altsep.
         self.assertEqual(
-            self.util.cache_from_source('\\foo\\bar\\baz/qux.py', True),
+            self.util.cache_from_source('\\foo\\bar\\baz/qux.py', optimization=''),
             '\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag))
 
     @unittest.skipUnless(sys.implementation.cache_tag is not None,
@@ -649,7 +711,12 @@
             ValueError, self.util.source_from_cache, '__pycache__/foo.pyc')
 
     def test_source_from_cache_too_many_dots(self):
-        # Too many dots in final path component -> ValueError
+        with self.assertRaises(ValueError):
+            self.util.source_from_cache(
+                    '__pycache__/foo.cpython-32.opt-1.foo.pyc')
+
+    def test_source_from_cache_not_opt(self):
+        # Non-`opt-` path component -> ValueError
         self.assertRaises(
             ValueError, self.util.source_from_cache,
             '__pycache__/foo.cpython-32.foo.pyc')
@@ -660,6 +727,17 @@
             ValueError, self.util.source_from_cache,
             '/foo/bar/foo.cpython-32.foo.pyc')
 
+    def test_source_from_cache_optimized_bytecode(self):
+        # Optimized bytecode is not an issue.
+        path = os.path.join('__pycache__', 'foo.{}.opt-1.pyc'.format(self.tag))
+        self.assertEqual(self.util.source_from_cache(path), 'foo.py')
+
+    def test_source_from_cache_missing_optimization(self):
+        # An empty optimization level is a no-no.
+        path = os.path.join('__pycache__', 'foo.{}.opt-.pyc'.format(self.tag))
+        with self.assertRaises(ValueError):
+            self.util.source_from_cache(path)
+
 
 (Frozen_PEP3147Tests,
  Source_PEP3147Tests
diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
--- a/Lib/test/test_py_compile.py
+++ b/Lib/test/test_py_compile.py
@@ -119,6 +119,10 @@
         self.assertTrue(os.path.exists(cache_path))
         self.assertFalse(os.path.exists(pyc_path))
 
+    def test_optimization_path(self):
+        # Specifying optimized bytecode should lead to a path reflecting that.
+        self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py
--- a/Lib/test/test_runpy.py
+++ b/Lib/test/test_runpy.py
@@ -269,7 +269,7 @@
             if verbose > 1: print(ex) # Persist with cleaning up
 
     def _fix_ns_for_legacy_pyc(self, ns, alter_sys):
-        char_to_add = "c" if __debug__ else "o"
+        char_to_add = "c"
         ns["__file__"] += char_to_add
         ns["__cached__"] = ns["__file__"]
         spec = ns["__spec__"]
diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py
--- a/Lib/test/test_trace.py
+++ b/Lib/test/test_trace.py
@@ -13,8 +13,8 @@
 #------------------------------- Utilities -----------------------------------#
 
 def fix_ext_py(filename):
-    """Given a .pyc/.pyo filename converts it to the appropriate .py"""
-    if filename.endswith(('.pyc', '.pyo')):
+    """Given a .pyc filename converts it to the appropriate .py"""
+    if filename.endswith('.pyc'):
         filename = filename[:-1]
     return filename
 
diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py
--- a/Lib/test/test_tracemalloc.py
+++ b/Lib/test/test_tracemalloc.py
@@ -660,11 +660,9 @@
         self.assertFalse(fnmatch('abcdd', 'a*c*e'))
         self.assertFalse(fnmatch('abcbdefef', 'a*bd*eg'))
 
-        # replace .pyc and .pyo suffix with .py
+        # replace .pyc suffix with .py
         self.assertTrue(fnmatch('a.pyc', 'a.py'))
-        self.assertTrue(fnmatch('a.pyo', 'a.py'))
         self.assertTrue(fnmatch('a.py', 'a.pyc'))
-        self.assertTrue(fnmatch('a.py', 'a.pyo'))
 
         if os.name == 'nt':
             # case insensitive
@@ -674,7 +672,6 @@
             self.assertTrue(fnmatch('a.pyc', 'a.PY'))
             self.assertTrue(fnmatch('a.PYO', 'a.py'))
             self.assertTrue(fnmatch('a.py', 'a.PYC'))
-            self.assertTrue(fnmatch('a.PY', 'a.pyo'))
         else:
             # case sensitive
             self.assertFalse(fnmatch('aBC', 'ABc'))
@@ -683,7 +680,6 @@
             self.assertFalse(fnmatch('a.pyc', 'a.PY'))
             self.assertFalse(fnmatch('a.PYO', 'a.py'))
             self.assertFalse(fnmatch('a.py', 'a.PYC'))
-            self.assertFalse(fnmatch('a.PY', 'a.pyo'))
 
         if os.name == 'nt':
             # normalize alternate separator "/" to the standard separator "\"
diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -689,7 +689,7 @@
         self.requiresWriteAccess(os.path.dirname(__file__))
         with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp:
             fn = __file__
-            if fn.endswith('.pyc') or fn.endswith('.pyo'):
+            if fn.endswith('.pyc'):
                 path_split = fn.split(os.sep)
                 if os.altsep is not None:
                     path_split.extend(fn.split(os.altsep))
@@ -706,7 +706,7 @@
 
         with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp:
             fn = __file__
-            if fn.endswith(('.pyc', '.pyo')):
+            if fn.endswith('.pyc'):
                 fn = fn[:-1]
 
             zipfp.writepy(fn, "testpackage")
@@ -762,10 +762,8 @@
         import email
         packagedir = os.path.dirname(email.__file__)
         self.requiresWriteAccess(packagedir)
-        # use .pyc if running test in optimization mode,
-        # use .pyo if running test in debug mode
         optlevel = 1 if __debug__ else 0
-        ext = '.pyo' if optlevel == 1 else '.pyc'
+        ext = '.pyc'
 
         with TemporaryFile() as t, \
              zipfile.PyZipFile(t, "w", optimize=optlevel) as zipfp:
@@ -839,11 +837,10 @@
                 self.assertIn("SyntaxError", s.getvalue())
 
                 # as it will not have compiled the python file, it will
-                # include the .py file not .pyc or .pyo
+                # include the .py file not .pyc
                 names = zipfp.namelist()
                 self.assertIn('mod1.py', names)
                 self.assertNotIn('mod1.pyc', names)
-                self.assertNotIn('mod1.pyo', names)
 
         finally:
             rmtree(TESTFN2)
diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py
--- a/Lib/test/test_zipimport.py
+++ b/Lib/test/test_zipimport.py
@@ -51,7 +51,7 @@
 TEMP_ZIP = os.path.abspath("junk95142.zip")
 
 pyc_file = importlib.util.cache_from_source(TESTMOD + '.py')
-pyc_ext = ('.pyc' if __debug__ else '.pyo')
+pyc_ext = '.pyc'
 
 
 class ImportHooksBaseTestCase(unittest.TestCase):
diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py
--- a/Lib/tkinter/__init__.py
+++ b/Lib/tkinter/__init__.py
@@ -1852,7 +1852,7 @@
             import os
             baseName = os.path.basename(sys.argv[0])
             baseName, ext = os.path.splitext(baseName)
-            if ext not in ('.py', '.pyc', '.pyo'):
+            if ext not in ('.py', '.pyc'):
                 baseName = baseName + ext
         interactive = 0
         self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
diff --git a/Lib/tkinter/test/runtktests.py b/Lib/tkinter/test/runtktests.py
--- a/Lib/tkinter/test/runtktests.py
+++ b/Lib/tkinter/test/runtktests.py
@@ -16,7 +16,7 @@
 
 def is_package(path):
     for name in os.listdir(path):
-        if name in ('__init__.py', '__init__.pyc', '__init.pyo'):
+        if name in ('__init__.py', '__init__.pyc'):
             return True
     return False
 
diff --git a/Lib/trace.py b/Lib/trace.py
--- a/Lib/trace.py
+++ b/Lib/trace.py
@@ -305,7 +305,7 @@
             if self.is_ignored_filename(filename):
                 continue
 
-            if filename.endswith((".pyc", ".pyo")):
+            if filename.endswith(".pyc"):
                 filename = filename[:-1]
 
             if coverdir is None:
diff --git a/Lib/tracemalloc.py b/Lib/tracemalloc.py
--- a/Lib/tracemalloc.py
+++ b/Lib/tracemalloc.py
@@ -297,7 +297,7 @@
 
 def _normalize_filename(filename):
     filename = os.path.normcase(filename)
-    if filename.endswith(('.pyc', '.pyo')):
+    if filename.endswith('.pyc'):
         filename = filename[:-1]
     return filename
 
diff --git a/Lib/unittest/loader.py b/Lib/unittest/loader.py
--- a/Lib/unittest/loader.py
+++ b/Lib/unittest/loader.py
@@ -14,9 +14,9 @@
 
 __unittest = True
 
-# what about .pyc or .pyo (etc)
+# what about .pyc (etc)
 # we would need to avoid loading the same tests multiple times
-# from '.py', '.pyc' *and* '.pyo'
+# from '.py', *and* '.pyc'
 VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE)
 
 
diff --git a/Lib/warnings.py b/Lib/warnings.py
--- a/Lib/warnings.py
+++ b/Lib/warnings.py
@@ -188,7 +188,7 @@
     filename = globals.get('__file__')
     if filename:
         fnl = filename.lower()
-        if fnl.endswith((".pyc", ".pyo")):
+        if fnl.endswith(".pyc"):
             filename = filename[:-1]
     else:
         if module == "__main__":
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -1731,7 +1731,7 @@
         the modules into the archive.  If pathname is a plain
         directory, listdir *.py and enter all modules.  Else, pathname
         must be a Python *.py file and the module will be put into the
-        archive.  Added modules are always module.pyo or module.pyc.
+        archive.  Added modules are always module.pyc.
         This method will compile the module.py into module.pyc if
         necessary.
         If filterfunc(pathname) is given, it is called with every argument.
@@ -1824,46 +1824,59 @@
 
         file_py  = pathname + ".py"
         file_pyc = pathname + ".pyc"
-        file_pyo = pathname + ".pyo"
-        pycache_pyc = importlib.util.cache_from_source(file_py, True)
-        pycache_pyo = importlib.util.cache_from_source(file_py, False)
+        pycache_opt0 = importlib.util.cache_from_source(file_py, optimization='')
+        pycache_opt1 = importlib.util.cache_from_source(file_py, optimization=1)
+        pycache_opt2 = importlib.util.cache_from_source(file_py, optimization=2)
         if self._optimize == -1:
             # legacy mode: use whatever file is present
-            if (os.path.isfile(file_pyo) and
-                os.stat(file_pyo).st_mtime >= os.stat(file_py).st_mtime):
-                # Use .pyo file.
-                arcname = fname = file_pyo
-            elif (os.path.isfile(file_pyc) and
+            if (os.path.isfile(file_pyc) and
                   os.stat(file_pyc).st_mtime >= os.stat(file_py).st_mtime):
                 # Use .pyc file.
                 arcname = fname = file_pyc
-            elif (os.path.isfile(pycache_pyc) and
-                  os.stat(pycache_pyc).st_mtime >= os.stat(file_py).st_mtime):
+            elif (os.path.isfile(pycache_opt0) and
+                  os.stat(pycache_opt0).st_mtime >= os.stat(file_py).st_mtime):
                 # Use the __pycache__/*.pyc file, but write it to the legacy pyc
                 # file name in the archive.
-                fname = pycache_pyc
+                fname = pycache_opt0
                 arcname = file_pyc
-            elif (os.path.isfile(pycache_pyo) and
-                  os.stat(pycache_pyo).st_mtime >= os.stat(file_py).st_mtime):
-                # Use the __pycache__/*.pyo file, but write it to the legacy pyo
+            elif (os.path.isfile(pycache_opt1) and
+                  os.stat(pycache_opt1).st_mtime >= os.stat(file_py).st_mtime):
+                # Use the __pycache__/*.pyc file, but write it to the legacy pyc
                 # file name in the archive.
-                fname = pycache_pyo
-                arcname = file_pyo
+                fname = pycache_opt1
+                arcname = file_pyc
+            elif (os.path.isfile(pycache_opt2) and
+                  os.stat(pycache_opt2).st_mtime >= os.stat(file_py).st_mtime):
+                # Use the __pycache__/*.pyc file, but write it to the legacy pyc
+                # file name in the archive.
+                fname = pycache_opt2
+                arcname = file_pyc
             else:
                 # Compile py into PEP 3147 pyc file.
                 if _compile(file_py):
-                    fname = (pycache_pyc if __debug__ else pycache_pyo)
-                    arcname = (file_pyc if __debug__ else file_pyo)
+                    if sys.flags.optimize == 0:
+                        fname = pycache_opt0
+                    elif sys.flags.optimize == 1:
+                        fname = pycache_opt1
+                    else:
+                        fname = pycache_opt2
+                    arcname = file_pyc
                 else:
                     fname = arcname = file_py
         else:
             # new mode: use given optimization level
             if self._optimize == 0:
-                fname = pycache_pyc
+                fname = pycache_opt0
                 arcname = file_pyc
             else:
-                fname = pycache_pyo
-                arcname = file_pyo
+                arcname = file_pyc
+                if self._optimize == 1:
+                    fname = pycache_opt1
+                elif self._optimize == 2:
+                    fname = pycache_opt2
+                else:
+                    msg = "invalid value for 'optimize': {!r}".format(self._optimize)
+                    raise ValueError(msg)
             if not (os.path.isfile(fname) and
                     os.stat(fname).st_mtime >= os.stat(file_py).st_mtime):
                 if not _compile(file_py, optimize=self._optimize):
diff --git a/Makefile.pre.in b/Makefile.pre.in
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -1263,7 +1263,12 @@
 		-d $(LIBDEST) -f \
 		-x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
 		$(DESTDIR)$(LIBDEST)
-	-PYTHONPATH=$(DESTDIR)$(LIBDEST)  $(RUNSHARED) \
+	-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
+		$(PYTHON_FOR_BUILD) -Wi -OO $(DESTDIR)$(LIBDEST)/compileall.py \
+		-d $(LIBDEST) -f \
+		-x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' \
+		$(DESTDIR)$(LIBDEST)
+	-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
 		$(PYTHON_FOR_BUILD) -Wi $(DESTDIR)$(LIBDEST)/compileall.py \
 		-d $(LIBDEST)/site-packages -f \
 		-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
@@ -1272,6 +1277,10 @@
 		-d $(LIBDEST)/site-packages -f \
 		-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
 	-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
+		$(PYTHON_FOR_BUILD) -Wi -OO $(DESTDIR)$(LIBDEST)/compileall.py \
+		-d $(LIBDEST)/site-packages -f \
+		-x badsyntax $(DESTDIR)$(LIBDEST)/site-packages
+	-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
 		$(PYTHON_FOR_BUILD) -m lib2to3.pgen2.driver $(DESTDIR)$(LIBDEST)/lib2to3/Grammar.txt
 	-PYTHONPATH=$(DESTDIR)$(LIBDEST) $(RUNSHARED) \
 		$(PYTHON_FOR_BUILD) -m lib2to3.pgen2.driver $(DESTDIR)$(LIBDEST)/lib2to3/PatternGrammar.txt
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,8 @@
 Core and Builtins
 -----------------
 
+- Issue #23731: Implement PEP 488: removal of .pyo files.
+
 - Issue #23726: Don't enable GC for user subclasses of non-GC types that
   don't add any new fields.  Patch by Eugene Toder.
 
diff --git a/Misc/python.man b/Misc/python.man
--- a/Misc/python.man
+++ b/Misc/python.man
@@ -104,10 +104,10 @@
 applications.
 See the internal documentation for hints.
 .PP
-Documentation for installed Python modules and packages can be 
-viewed by running the 
+Documentation for installed Python modules and packages can be
+viewed by running the
 .B pydoc
-program.  
+program.
 .SH COMMAND LINE OPTIONS
 .TP
 .B \-B
@@ -150,23 +150,20 @@
 malicious code.
 .TP
 .BI "\-m " module-name
-Searches 
-.I sys.path 
-for the named module and runs the corresponding 
-.I .py 
+Searches
+.I sys.path
+for the named module and runs the corresponding
+.I .py
 file as a script.
 .TP
 .B \-O
-Turn on basic optimizations.  This changes the filename extension for
-compiled (bytecode) files from
-.I .pyc
-to \fI.pyo\fP.  Given twice, causes docstrings to be discarded.
+Turn on basic optimizations.  Given twice, causes docstrings to be discarded.
 .TP
 .B \-OO
 Discard docstrings in addition to the \fB-O\fP optimizations.
 .TP
 .B \-q
-Do not print the version and copyright messages. These messages are 
+Do not print the version and copyright messages. These messages are
 also suppressed in non-interactive mode.
 .TP
 .B \-s
@@ -193,7 +190,7 @@
 .B \-v
 Print a message each time a module is initialized, showing the place
 (filename or built-in module) from which it is loaded.  When given
-twice, print a message for each file that is checked for when 
+twice, print a message for each file that is checked for when
 searching for a module.  Also provides information on module cleanup
 at exit.
 .TP
@@ -418,7 +415,7 @@
 .IP PYTHONVERBOSE
 If this is set to a non-empty string it is equivalent to specifying
 the \fB\-v\fP option. If set to an integer, it is equivalent to
-specifying \fB\-v\fP multiple times. 
+specifying \fB\-v\fP multiple times.
 .IP PYTHONWARNINGS
 If this is set to a comma-separated string it is equivalent to
 specifying the \fB\-W\fP option for each separate value.
diff --git a/Modules/getpath.c b/Modules/getpath.c
--- a/Modules/getpath.c
+++ b/Modules/getpath.c
@@ -170,14 +170,14 @@
 
 
 static int
-ismodule(wchar_t *filename)        /* Is module -- check for .pyc/.pyo too */
+ismodule(wchar_t *filename)        /* Is module -- check for .pyc too */
 {
     if (isfile(filename))
         return 1;
 
     /* Check for the compiled version of prefix. */
     if (wcslen(filename) < MAXPATHLEN) {
-        wcscat(filename, Py_OptimizeFlag ? L"o" : L"c");
+        wcscat(filename, L"c");
         if (isfile(filename))
             return 1;
     }
@@ -891,4 +891,3 @@
 #ifdef __cplusplus
 }
 #endif
-
diff --git a/Modules/zipimport.c b/Modules/zipimport.c
--- a/Modules/zipimport.c
+++ b/Modules/zipimport.c
@@ -20,15 +20,13 @@
 
 /* zip_searchorder defines how we search for a module in the Zip
    archive: we first search for a package __init__, then for
-   non-package .pyc, .pyo and .py entries. The .pyc and .pyo entries
+   non-package .pyc, and .py entries. The .pyc entries
    are swapped by initzipimport() if we run in optimized mode. Also,
    '/' is replaced by SEP there. */
 static struct st_zip_searchorder zip_searchorder[] = {
     {"/__init__.pyc", IS_PACKAGE | IS_BYTECODE},
-    {"/__init__.pyo", IS_PACKAGE | IS_BYTECODE},
     {"/__init__.py", IS_PACKAGE | IS_SOURCE},
     {".pyc", IS_BYTECODE},
-    {".pyo", IS_BYTECODE},
     {".py", IS_SOURCE},
     {"", 0}
 };
@@ -1318,7 +1316,7 @@
     return mktime(&stm);
 }
 
-/* Given a path to a .pyc or .pyo file in the archive, return the
+/* Given a path to a .pyc file in the archive, return the
    modification time of the matching .py file, or 0 if no source
    is available. */
 static time_t
@@ -1481,17 +1479,6 @@
     /* Correct directory separator */
     zip_searchorder[0].suffix[0] = SEP;
     zip_searchorder[1].suffix[0] = SEP;
-    zip_searchorder[2].suffix[0] = SEP;
-    if (Py_OptimizeFlag) {
-        /* Reverse *.pyc and *.pyo */
-        struct st_zip_searchorder tmp;
-        tmp = zip_searchorder[0];
-        zip_searchorder[0] = zip_searchorder[1];
-        zip_searchorder[1] = tmp;
-        tmp = zip_searchorder[3];
-        zip_searchorder[3] = zip_searchorder[4];
-        zip_searchorder[4] = tmp;
-    }
 
     mod = PyModule_Create(&zipimportmodule);
     if (mod == NULL)
diff --git a/Python/_warnings.c b/Python/_warnings.c
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -563,13 +563,12 @@
         data = PyUnicode_DATA(*filename);
 
 #define ascii_lower(c) ((c <= 127) ? Py_TOLOWER(c) : 0)
-        /* if filename.lower().endswith((".pyc", ".pyo")): */
+        /* if filename.lower().endswith(".pyc"): */
         if (len >= 4 &&
             PyUnicode_READ(kind, data, len-4) == '.' &&
             ascii_lower(PyUnicode_READ(kind, data, len-3)) == 'p' &&
             ascii_lower(PyUnicode_READ(kind, data, len-2)) == 'y' &&
-            (ascii_lower(PyUnicode_READ(kind, data, len-1)) == 'c' ||
-                ascii_lower(PyUnicode_READ(kind, data, len-1)) == 'o'))
+            ascii_lower(PyUnicode_READ(kind, data, len-1)) == 'c')
         {
             *filename = PyUnicode_Substring(*filename, 0,
                                             PyUnicode_GET_LENGTH(*filename)-1);
diff --git a/Python/importlib.h b/Python/importlib.h
--- a/Python/importlib.h
+++ b/Python/importlib.h
[stripped]
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -265,7 +265,7 @@
 static int
 maybe_pyc_file(FILE *fp, const char* filename, const char* ext, int closeit)
 {
-    if (strcmp(ext, ".pyc") == 0 || strcmp(ext, ".pyo") == 0)
+    if (strcmp(ext, ".pyc") == 0)
         return 1;
 
     /* Only look into the file if we are allowed to close it, since
@@ -371,9 +371,6 @@
             fprintf(stderr, "python: Can't reopen .pyc file\n");
             goto done;
         }
-        /* Turn on optimization if a .pyo file is given */
-        if (strcmp(ext, ".pyo") == 0)
-            Py_OptimizeFlag = 1;
 
         if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) {
             fprintf(stderr, "python: failed to set __main__.__loader__\n");

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list