Skip to content

Commit

Permalink
Merge branch 'main' into superopt
Browse files Browse the repository at this point in the history
* main:
  gh-100227: Only Use deepfreeze for the Main Interpreter (gh-103794)
  gh-103492: Clarify SyntaxWarning with literal comparison (#103493)
  gh-101100: Fix Sphinx warnings in `argparse` module (#103289)
  • Loading branch information
carljm committed Apr 24, 2023
2 parents 0de5bc6 + 19e4f75 commit dbe1665
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 66 deletions.
7 changes: 7 additions & 0 deletions Doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@
if venvdir is not None:
exclude_patterns.append(venvdir + '/*')

nitpick_ignore = [
# Do not error nit-picky mode builds when _SubParsersAction.add_parser cannot
# be resolved, as the method is currently undocumented. For context, see
# https://github.com/python/cpython/pull/103289.
('py:meth', '_SubParsersAction.add_parser'),
]

# Disable Docutils smartquotes for several translations
smartquotes_excludes = {
'languages': ['ja', 'fr', 'zh_TW', 'zh_CN'], 'builders': ['man', 'text'],
Expand Down
16 changes: 9 additions & 7 deletions Doc/howto/argparse.rst
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
.. _argparse-tutorial:

*****************
Argparse Tutorial
*****************

:author: Tshepang Mbambo

.. _argparse-tutorial:
.. currentmodule:: argparse

This tutorial is intended to be a gentle introduction to :mod:`argparse`, the
recommended command-line parsing module in the Python standard library.

.. note::

There are two other modules that fulfill the same task, namely
:mod:`getopt` (an equivalent for :c:func:`getopt` from the C
:mod:`getopt` (an equivalent for ``getopt()`` from the C
language) and the deprecated :mod:`optparse`.
Note also that :mod:`argparse` is based on :mod:`optparse`,
and therefore very similar in terms of usage.
Expand Down Expand Up @@ -137,13 +139,13 @@ And running the code:
Here is what's happening:

* We've added the :meth:`add_argument` method, which is what we use to specify
* We've added the :meth:`~ArgumentParser.add_argument` method, which is what we use to specify
which command-line options the program is willing to accept. In this case,
I've named it ``echo`` so that it's in line with its function.

* Calling our program now requires us to specify an option.

* The :meth:`parse_args` method actually returns some data from the
* The :meth:`~ArgumentParser.parse_args` method actually returns some data from the
options specified, in this case, ``echo``.

* The variable is some form of 'magic' that :mod:`argparse` performs for free
Expand Down Expand Up @@ -256,7 +258,7 @@ Here is what is happening:

* To show that the option is actually optional, there is no error when running
the program without it. Note that by default, if an optional argument isn't
used, the relevant variable, in this case :attr:`args.verbosity`, is
used, the relevant variable, in this case ``args.verbosity``, is
given ``None`` as a value, which is the reason it fails the truth
test of the :keyword:`if` statement.

Expand Down Expand Up @@ -299,7 +301,7 @@ Here is what is happening:
We even changed the name of the option to match that idea.
Note that we now specify a new keyword, ``action``, and give it the value
``"store_true"``. This means that, if the option is specified,
assign the value ``True`` to :data:`args.verbose`.
assign the value ``True`` to ``args.verbose``.
Not specifying it implies ``False``.

* It complains when you specify a value, in true spirit of what flags
Expand Down Expand Up @@ -698,7 +700,7 @@ Conflicting options

So far, we have been working with two methods of an
:class:`argparse.ArgumentParser` instance. Let's introduce a third one,
:meth:`add_mutually_exclusive_group`. It allows for us to specify options that
:meth:`~ArgumentParser.add_mutually_exclusive_group`. It allows for us to specify options that
conflict with each other. Let's also change the rest of the program so that
the new functionality makes more sense:
we'll introduce the ``--quiet`` option,
Expand Down
26 changes: 20 additions & 6 deletions Doc/library/argparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ arguments will never be treated as file references.

.. versionchanged:: 3.12
:class:`ArgumentParser` changed encoding and errors to read arguments files
from default (e.g. :func:`locale.getpreferredencoding(False)` and
from default (e.g. :func:`locale.getpreferredencoding(False) <locale.getpreferredencoding>` and
``"strict"``) to :term:`filesystem encoding and error handler`.
Arguments file should be encoded in UTF-8 instead of ANSI Codepage on Windows.

Expand Down Expand Up @@ -1191,7 +1191,7 @@ done downstream after the arguments are parsed.
For example, JSON or YAML conversions have complex error cases that require
better reporting than can be given by the ``type`` keyword. A
:exc:`~json.JSONDecodeError` would not be well formatted and a
:exc:`FileNotFound` exception would not be handled at all.
:exc:`FileNotFoundError` exception would not be handled at all.

Even :class:`~argparse.FileType` has its limitations for use with the ``type``
keyword. If one argument uses *FileType* and then a subsequent argument fails,
Expand Down Expand Up @@ -1445,7 +1445,7 @@ Action classes
Action classes implement the Action API, a callable which returns a callable
which processes arguments from the command-line. Any object which follows
this API may be passed as the ``action`` parameter to
:meth:`add_argument`.
:meth:`~ArgumentParser.add_argument`.

.. class:: Action(option_strings, dest, nargs=None, const=None, default=None, \
type=None, choices=None, required=False, help=None, \
Expand Down Expand Up @@ -1723,7 +1723,7 @@ Sub-commands
:class:`ArgumentParser` supports the creation of such sub-commands with the
:meth:`add_subparsers` method. The :meth:`add_subparsers` method is normally
called with no arguments and returns a special action object. This object
has a single method, :meth:`~ArgumentParser.add_parser`, which takes a
has a single method, :meth:`~_SubParsersAction.add_parser`, which takes a
command name and any :class:`ArgumentParser` constructor arguments, and
returns an :class:`ArgumentParser` object that can be modified as usual.

Expand Down Expand Up @@ -1789,7 +1789,7 @@ Sub-commands
for that particular parser will be printed. The help message will not
include parent parser or sibling parser messages. (A help message for each
subparser command, however, can be given by supplying the ``help=`` argument
to :meth:`add_parser` as above.)
to :meth:`~_SubParsersAction.add_parser` as above.)

::

Expand Down Expand Up @@ -2157,7 +2157,7 @@ the populated namespace and the list of remaining argument strings.

.. warning::
:ref:`Prefix matching <prefix-matching>` rules apply to
:meth:`parse_known_args`. The parser may consume an option even if it's just
:meth:`~ArgumentParser.parse_known_args`. The parser may consume an option even if it's just
a prefix of one of its known options, instead of leaving it in the remaining
arguments list.

Expand Down Expand Up @@ -2295,3 +2295,17 @@ A partial upgrade path from :mod:`optparse` to :mod:`argparse`:

* Replace the OptionParser constructor ``version`` argument with a call to
``parser.add_argument('--version', action='version', version='<the version>')``.

Exceptions
----------

.. exception:: ArgumentError

An error from creating or using an argument (optional or positional).

The string value of this exception is the message, augmented with
information about the argument that caused it.

.. exception:: ArgumentTypeError

Raised when something goes wrong converting a command line string to a type.
43 changes: 42 additions & 1 deletion Doc/library/optparse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,16 @@ The canonical way to create an :class:`Option` instance is with the

As you can see, most actions involve storing or updating a value somewhere.
:mod:`optparse` always creates a special object for this, conventionally called
``options`` (it happens to be an instance of :class:`optparse.Values`). Option
``options``, which is an instance of :class:`optparse.Values`.

.. class:: Values

An object holding parsed argument names and values as attributes.
Normally created by calling when calling :meth:`OptionParser.parse_args`,
and can be overridden by a custom subclass passed to the *values* argument of
:meth:`OptionParser.parse_args` (as described in :ref:`optparse-parsing-arguments`).

Option
arguments (and various other values) are stored as attributes of this object,
according to the :attr:`~Option.dest` (destination) option attribute.

Expand Down Expand Up @@ -991,6 +1000,14 @@ one that makes sense for *all* options.
Option attributes
^^^^^^^^^^^^^^^^^

.. class:: Option

A single command line argument,
with various attributes passed by keyword to the constructor.
Normally created with :meth:`OptionParser.add_option` rather than directly,
and can be overridden by a custom class via the *option_class* argument
to :class:`OptionParser`.

The following option attributes may be passed as keyword arguments to
:meth:`OptionParser.add_option`. If you pass an option attribute that is not
relevant to a particular option, or fail to pass a required option attribute,
Expand Down Expand Up @@ -2035,3 +2052,27 @@ Features of note:
about setting a default value for the option destinations in question; they
can just leave the default as ``None`` and :meth:`ensure_value` will take care of
getting it right when it's needed.

Exceptions
----------

.. exception:: OptionError

Raised if an :class:`Option` instance is created with invalid or
inconsistent arguments.

.. exception:: OptionConflictError

Raised if conflicting options are added to an :class:`OptionParser`.

.. exception:: OptionValueError

Raised if an invalid option value is encountered on the command line.

.. exception:: BadOptionError

Raised if an invalid option is passed on the command line.

.. exception:: AmbiguousOptionError

Raised if an ambiguous option is passed on the command line.
2 changes: 0 additions & 2 deletions Doc/tools/.nitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ Doc/faq/gui.rst
Doc/faq/library.rst
Doc/faq/programming.rst
Doc/glossary.rst
Doc/howto/argparse.rst
Doc/howto/curses.rst
Doc/howto/descriptor.rst
Doc/howto/enum.rst
Expand All @@ -78,7 +77,6 @@ Doc/library/__future__.rst
Doc/library/_thread.rst
Doc/library/abc.rst
Doc/library/aifc.rst
Doc/library/argparse.rst
Doc/library/ast.rst
Doc/library/asyncio-dev.rst
Doc/library/asyncio-eventloop.rst
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_codeop.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def test_filename(self):
def test_warning(self):
# Test that the warning is only returned once.
with warnings_helper.check_warnings(
('"is" with a literal', SyntaxWarning),
('"is" with \'str\' literal', SyntaxWarning),
("invalid escape sequence", SyntaxWarning),
) as w:
compile_command(r"'\e' is 0")
Expand Down
31 changes: 20 additions & 11 deletions Lib/test/test_grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,9 @@ def check(test, error=False):
check(f"[{num}for x in ()]")
check(f"{num}spam", error=True)

with self.assertWarnsRegex(SyntaxWarning, r'invalid \w+ literal'):
compile(f"{num}is x", "<testcase>", "eval")
with warnings.catch_warnings():
warnings.filterwarnings('ignore', '"is" with a literal',
SyntaxWarning)
with self.assertWarnsRegex(SyntaxWarning,
r'invalid \w+ literal'):
compile(f"{num}is x", "<testcase>", "eval")
warnings.simplefilter('error', SyntaxWarning)
with self.assertRaisesRegex(SyntaxError,
r'invalid \w+ literal'):
Expand Down Expand Up @@ -1467,21 +1464,33 @@ def test_comparison(self):
if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in x is x is not x: pass

def test_comparison_is_literal(self):
def check(test, msg='"is" with a literal'):
def check(test, msg):
self.check_syntax_warning(test, msg)

check('x is 1')
check('x is "thing"')
check('1 is x')
check('x is y is 1')
check('x is not 1', '"is not" with a literal')
check('x is 1', '"is" with \'int\' literal')
check('x is "thing"', '"is" with \'str\' literal')
check('1 is x', '"is" with \'int\' literal')
check('x is y is 1', '"is" with \'int\' literal')
check('x is not 1', '"is not" with \'int\' literal')
check('x is not (1, 2)', '"is not" with \'tuple\' literal')
check('(1, 2) is not x', '"is not" with \'tuple\' literal')

check('None is 1', '"is" with \'int\' literal')
check('1 is None', '"is" with \'int\' literal')

check('x == 3 is y', '"is" with \'int\' literal')
check('x == "thing" is y', '"is" with \'str\' literal')

with warnings.catch_warnings():
warnings.simplefilter('error', SyntaxWarning)
compile('x is None', '<testcase>', 'exec')
compile('x is False', '<testcase>', 'exec')
compile('x is True', '<testcase>', 'exec')
compile('x is ...', '<testcase>', 'exec')
compile('None is x', '<testcase>', 'exec')
compile('False is x', '<testcase>', 'exec')
compile('True is x', '<testcase>', 'exec')
compile('... is x', '<testcase>', 'exec')

def test_warn_missed_comma(self):
def check(test):
Expand Down
2 changes: 1 addition & 1 deletion Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,7 @@ Tools/build/freeze_modules.py: $(FREEZE_MODULE)

.PHONY: regen-frozen
regen-frozen: Tools/build/freeze_modules.py $(FROZEN_FILES_IN)
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/freeze_modules.py
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/build/freeze_modules.py --frozen-modules
@echo "The Makefile was updated, you may need to re-run make."

############################################################################
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Clarify :exc:`SyntaxWarning` with literal ``is`` comparison by specifying which literal is problematic, since comparisons using ``is`` with e.g. None and bool literals are idiomatic.
16 changes: 12 additions & 4 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2291,6 +2291,8 @@ check_is_arg(expr_ty e)
|| value == Py_Ellipsis);
}

static PyTypeObject * infer_type(expr_ty e);

/* Check operands of identity checks ("is" and "is not").
Emit a warning if any operand is a constant except named singletons.
*/
Expand All @@ -2299,19 +2301,25 @@ check_compare(struct compiler *c, expr_ty e)
{
Py_ssize_t i, n;
bool left = check_is_arg(e->v.Compare.left);
expr_ty left_expr = e->v.Compare.left;
n = asdl_seq_LEN(e->v.Compare.ops);
for (i = 0; i < n; i++) {
cmpop_ty op = (cmpop_ty)asdl_seq_GET(e->v.Compare.ops, i);
bool right = check_is_arg((expr_ty)asdl_seq_GET(e->v.Compare.comparators, i));
expr_ty right_expr = (expr_ty)asdl_seq_GET(e->v.Compare.comparators, i);
bool right = check_is_arg(right_expr);
if (op == Is || op == IsNot) {
if (!right || !left) {
const char *msg = (op == Is)
? "\"is\" with a literal. Did you mean \"==\"?"
: "\"is not\" with a literal. Did you mean \"!=\"?";
return compiler_warn(c, LOC(e), msg);
? "\"is\" with '%.200s' literal. Did you mean \"==\"?"
: "\"is not\" with '%.200s' literal. Did you mean \"!=\"?";
expr_ty literal = !left ? left_expr : right_expr;
return compiler_warn(
c, LOC(e), msg, infer_type(literal)->tp_name
);
}
}
left = right;
left_expr = right_expr;
}
return SUCCESS;
}
Expand Down

0 comments on commit dbe1665

Please sign in to comment.