Skip to content

bpo-2506: Add -X noopt command line option #13600

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,14 @@ PyConfig
by the function computing the :ref:`Path Configuration
<init-path-config>`.

.. c:member:: int optimize

Should the compiler optimize bytecode? If equal to 0, disable the
compiler optimizations and set :c:member:`~PyConfig.optimization_level`
to 0. Set to 0 by :option:`-X noopt <-X>` command line option.

.. versionadded:: 3.9

.. c:member:: int optimization_level

Compilation optimization level:
Expand Down
10 changes: 8 additions & 2 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ are always available. They are listed here in alphabetical order.
For more information on class methods, see :ref:`types`.


.. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
.. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1, noopt=None)

Compile the *source* into a code or AST object. Code objects can be executed
by :func:`exec` or :func:`eval`. *source* can either be a normal string, a
Expand Down Expand Up @@ -271,6 +271,9 @@ are always available. They are listed here in alphabetical order.
``__debug__`` is true), ``1`` (asserts are removed, ``__debug__`` is false)
or ``2`` (docstrings are removed too).

If *noopt* is false, disable compiler optimizations and ignore *optimize*
argument. If it is ``None``, use ``sys.flags.noopt`` value.

This function raises :exc:`SyntaxError` if the compiled source is invalid,
and :exc:`ValueError` if the source contains null bytes.

Expand Down Expand Up @@ -304,10 +307,13 @@ are always available. They are listed here in alphabetical order.
Previously, :exc:`TypeError` was raised when null bytes were encountered
in *source*.

.. versionadded:: 3.8
.. versionchanged:: 3.8
``ast.PyCF_ALLOW_TOP_LEVEL_AWAIT`` can now be passed in flags to enable
support for top-level ``await``, ``async for``, and ``async with``.

.. versionachanged:: 3.9
New *noopt* optional keyword-only parameter.


.. class:: complex([real[, imag]])

Expand Down
10 changes: 9 additions & 1 deletion Doc/library/importlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1366,7 +1366,7 @@ an :term:`importer`.

.. versionadded:: 3.4

.. function:: cache_from_source(path, debug_override=None, *, optimization=None)
.. function:: cache_from_source(path, debug_override=None, *, optimization=None, noopt=None)

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
Expand All @@ -1385,6 +1385,11 @@ an :term:`importer`.
``/foo/bar/__pycache__/baz.cpython-32.opt-2.pyc``. The string representation
of *optimization* can only be alphanumeric, else :exc:`ValueError` is raised.

The *noopt* parameter is used to specify if compiler optimization are
disabled. If it is true, *optimization* is ignored and ``.noopt`` suffix is
used (ex: ``baz.cpython-32.noopt.pyc``). If it is ``None``, use
``sys.flags.noopt`` value.

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
Expand All @@ -1400,6 +1405,9 @@ an :term:`importer`.
.. versionchanged:: 3.6
Accepts a :term:`path-like object`.

.. versionchanged:: 3.9
Added *noopt* parameter.


.. function:: source_from_cache(path)

Expand Down
8 changes: 7 additions & 1 deletion Doc/library/py_compile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ byte-code cache files in the directory containing the source code.
Exception raised when an error occurs while attempting to compile the file.


.. function:: compile(file, cfile=None, dfile=None, doraise=False, optimize=-1, invalidation_mode=PycInvalidationMode.TIMESTAMP)
.. function:: compile(file, cfile=None, dfile=None, doraise=False, optimize=-1, invalidation_mode=PycInvalidationMode.TIMESTAMP, *, noopt=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimize and noopt. It looks slightly weird.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know but I failed to find better names.


Compile a source file to byte-code and write out the byte-code cache file.
The source code is loaded from the file named *file*. The byte-code is
Expand Down Expand Up @@ -60,6 +60,9 @@ byte-code cache files in the directory containing the source code.
:func:`compile` function. The default of ``-1`` selects the optimization
level of the current interpreter.

If *noopt* is true, disable compiler optimizations and ignore *optimize*
argument. If it is ``None``, use ``sys.flags.noopt`` value.

*invalidation_mode* should be a member of the :class:`PycInvalidationMode`
enum and controls how the generated bytecode cache is invalidated at
runtime. The default is :attr:`PycInvalidationMode.CHECKED_HASH` if
Expand Down Expand Up @@ -92,6 +95,9 @@ byte-code cache files in the directory containing the source code.
.. versionchanged:: 3.8
The *quiet* parameter was added.

.. versionchanged:: 3.9
The *noopt* parameter was added.


.. class:: PycInvalidationMode

Expand Down
4 changes: 4 additions & 0 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ always available.
:const:`inspect` :option:`-i`
:const:`interactive` :option:`-i`
:const:`isolated` :option:`-I`
:const:`noopt` :option:`-X noopt <-X>`
:const:`optimize` :option:`-O` or :option:`-OO`
:const:`dont_write_bytecode` :option:`-B`
:const:`no_user_site` :option:`-s`
Expand Down Expand Up @@ -447,6 +448,9 @@ always available.
Added ``dev_mode`` attribute for the new :option:`-X` ``dev`` flag
and ``utf8_mode`` attribute for the new :option:`-X` ``utf8`` flag.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to change these two (and line 424-425) for consistent style :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose to not change these on purpose, it would be an unrelated change. Someone can work on a PR to update these once this PR is merged ;-)


.. versionchanged:: 3.9
Added ``noopt`` attribute for the new :option:`-X noopt <-X>` option.


.. data:: float_info

Expand Down
7 changes: 4 additions & 3 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ Miscellaneous options
* ``-X pycache_prefix=PATH`` enables writing ``.pyc`` files to a parallel
tree rooted at the given directory instead of to the code tree. See also
:envvar:`PYTHONPYCACHEPREFIX`.
* ``-X noopt`` disables the compiler optimizations.

It also allows passing arbitrary values and retrieving them through the
:data:`sys._xoptions` dictionary.
Expand All @@ -477,9 +478,9 @@ Miscellaneous options
The ``-X pycache_prefix`` option. The ``-X dev`` option now logs
``close()`` exceptions in :class:`io.IOBase` destructor.

.. versionchanged:: 3.9
Using ``-X dev`` option, check *encoding* and *errors* arguments on
string encoding and decoding operations.
.. versionadded:: 3.9
The ``-X noopt`` option. Using ``-X dev`` option, check *encoding* and
*errors* arguments on string encoding and decoding operations.


Options you shouldn't use
Expand Down
26 changes: 26 additions & 0 deletions Doc/whatsnew/3.9.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ Other Language Changes
ignored for empty strings.
(Contributed by Victor Stinner in :issue:`37388`.)

* Added :option:`-X noopt <-X>` command line to disable compiler optimizations.
:mod:`importlib` uses ``.noopt.pyc`` suffix for ``.pyc`` filenames if
:data:`sys.flags.noopt` is true.
(Contributed by Yury Selivanov and Victor Stinner in :issue:`2506`)

New Modules
===========
Expand All @@ -109,6 +113,12 @@ New Modules
Improved Modules
================

builtins
--------

The :func:`compile` function gets a new optional keyword-only *noopt* parameter
to disable compiler optimizations.

threading
---------

Expand All @@ -132,6 +142,22 @@ now raises :exc:`ImportError` instead of :exc:`ValueError` for invalid relative
import attempts.
(Contributed by Ngalim Siregar in :issue:`37444`.)

Add a new *noopt* optional keyword-only parameter to
:func:`importlib.util.cache_from_source` to disable compiler optimizations.

py_compile
----------

Add a new *noopt* optional keyword-only parameter to :func:`py_compile.compile`
to disable compiler optimizations.

sys
---

Add new :data:`sys.flags.noopt` flag for the new :option:`-X noopt <-X>` option
(disable compiler optimizations).
(Contributed by Yury Selivanov and Victor Stinner in :issue:`2506`)

Optimizations
=============

Expand Down
8 changes: 8 additions & 0 deletions Include/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ PyAPI_FUNC(PyCodeObject *) PyAST_CompileObject(
PyCompilerFlags *flags,
int optimize,
PyArena *arena);
PyAPI_FUNC(PyCodeObject *) _PyAST_Compile(
struct _mod *mod,
PyObject *filename,
PyCompilerFlags *flags,
int optimization_level,
PyArena *arena,
int noopt);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not it be controlled by flags or optimization_level?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optimization_level=0 still means having optimizations enabled. I cannot change to keep the backward compatibility.

Do you think that it would be better to add a new PyCF_xxx flag to avoid adding this new function?


PyAPI_FUNC(PyFutureFeatures *) PyFuture_FromAST(
struct _mod * mod,
const char *filename /* decoded from the filesystem encoding */
Expand Down
12 changes: 12 additions & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,18 @@ typedef struct {
/* If equal to 0, stop Python initialization before the "main" phase */
int _init_main;

/* --- Configuration version 2 (new fields) ------ */

/* Should the compiler optimize bytecode?

If equal to 0, disable compiler optimizations and set optimization_level
to 0.

If equal to 1, enable compiler optimizations.

Set to 0 by -X noopt. */
int optimize;

} PyConfig;

PyAPI_FUNC(PyStatus) PyConfig_InitPythonConfig(PyConfig *config);
Expand Down
4 changes: 3 additions & 1 deletion Include/internal/pycore_initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ extern PyStatus _PyPreConfig_Write(const PyPreConfig *preconfig);

/* --- PyConfig ---------------------------------------------- */

#define _Py_CONFIG_VERSION 1
/* Version 2: Add optimize field.
Version 1: Initial version (Python 3.8.0) */
#define _Py_CONFIG_VERSION 2

typedef enum {
/* Py_Initialize() API: backward compatibility with Python 3.6 and 3.7 */
Expand Down
6 changes: 6 additions & 0 deletions Include/pythonrun.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ PyAPI_FUNC(PyObject *) Py_CompileStringObject(
PyObject *filename, int start,
PyCompilerFlags *flags,
int optimize);
PyAPI_FUNC(PyObject *) _Py_CompileString(
const char *str,
PyObject *filename, int start,
PyCompilerFlags *flags,
int optimize,
int noopt);
#endif
PyAPI_FUNC(struct symtable *) Py_SymtableString(
const char *str,
Expand Down
4 changes: 2 additions & 2 deletions Lib/distutils/command/build_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ def get_outputs(self, include_bytecode=1):
if include_bytecode:
if self.compile:
outputs.append(importlib.util.cache_from_source(
filename, optimization=''))
filename, optimization='', noopt=False))
if self.optimize > 0:
outputs.append(importlib.util.cache_from_source(
filename, optimization=self.optimize))
filename, optimization=self.optimize, noopt=False))

outputs += [
os.path.join(build_dir, filename)
Expand Down
4 changes: 2 additions & 2 deletions Lib/distutils/command/install_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ def _bytecode_filenames(self, py_filenames):
continue
if self.compile:
bytecode_files.append(importlib.util.cache_from_source(
py_file, optimization=''))
py_file, optimization='', noopt=False))
if self.optimize > 0:
bytecode_files.append(importlib.util.cache_from_source(
py_file, optimization=self.optimize))
py_file, optimization=self.optimize, noopt=False))

return bytecode_files

Expand Down
6 changes: 4 additions & 2 deletions Lib/distutils/tests/test_install_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ def test_byte_compile(self):
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', optimization='')
pyc_file = importlib.util.cache_from_source('foo.py', optimization='',
noopt=False)
pyc_opt_file = importlib.util.cache_from_source('foo.py',
optimization=cmd.optimize)
optimization=cmd.optimize,
noopt=False)
self.assertTrue(os.path.exists(pyc_file))
self.assertTrue(os.path.exists(pyc_opt_file))

Expand Down
9 changes: 5 additions & 4 deletions Lib/distutils/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,10 +443,11 @@ def byte_compile (py_files,
# 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, optimization=opt)
cfile = importlib.util.cache_from_source(file,
optimization=opt,
noopt=False)
else:
cfile = importlib.util.cache_from_source(file)
cfile = importlib.util.cache_from_source(file, noopt=False)
dfile = file
if prefix:
if file[:len(prefix)] != prefix:
Expand All @@ -461,7 +462,7 @@ def byte_compile (py_files,
if force or newer(file, cfile):
log.info("byte-compiling %s to %s", file, cfile_base)
if not dry_run:
compile(file, cfile, dfile)
compile(file, cfile, dfile, noopt=False)
else:
log.debug("skipping byte-compilation of %s to %s",
file, cfile_base)
Expand Down
Loading