summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHugo van Kemenade <1324225+hugovk@users.noreply.github.com>2024-05-05 06:30:03 (GMT)
committerGitHub <noreply@github.com>2024-05-05 06:30:03 (GMT)
commit3b32575ed6b0905f434f9395d26293c0ae928032 (patch)
treeeed1443529d3d17b3f14457de994cfdf10270cae
parentfed8d73fde779fca41026398376cb3038e9b2b5f (diff)
downloadcpython-3b32575ed6b0905f434f9395d26293c0ae928032.zip
cpython-3b32575ed6b0905f434f9395d26293c0ae928032.tar.gz
cpython-3b32575ed6b0905f434f9395d26293c0ae928032.tar.bz2
gh-118131: Command-line interface for the `random` module (#118132)
-rw-r--r--Doc/library/cmdline.rst1
-rw-r--r--Doc/library/random.rst80
-rw-r--r--Doc/whatsnew/3.13.rst6
-rw-r--r--Lib/random.py72
-rw-r--r--Lib/test/test_random.py43
-rw-r--r--Misc/NEWS.d/next/Library/2024-04-21-18-55-42.gh-issue-118131.eAT0is.rst2
6 files changed, 203 insertions, 1 deletions
diff --git a/Doc/library/cmdline.rst b/Doc/library/cmdline.rst
index b2379be..5174515 100644
--- a/Doc/library/cmdline.rst
+++ b/Doc/library/cmdline.rst
@@ -36,6 +36,7 @@ The following modules have a command-line interface.
* :mod:`pyclbr`
* :mod:`pydoc`
* :mod:`quopri`
+* :ref:`random <random-cli>`
* :mod:`runpy`
* :ref:`site <site-commandline>`
* :ref:`sqlite3 <sqlite3-cli>`
diff --git a/Doc/library/random.rst b/Doc/library/random.rst
index 6179826..4584bbc 100644
--- a/Doc/library/random.rst
+++ b/Doc/library/random.rst
@@ -706,3 +706,83 @@ positive unnormalized float and is equal to ``math.ulp(0.0)``.)
<https://allendowney.com/research/rand/downey07randfloat.pdf>`_ a
paper by Allen B. Downey describing ways to generate more
fine-grained floats than normally generated by :func:`.random`.
+
+.. _random-cli:
+
+Command-line usage
+------------------
+
+.. versionadded:: 3.13
+
+The :mod:`!random` module can be executed from the command line.
+
+.. code-block:: sh
+
+ python -m random [-h] [-c CHOICE [CHOICE ...] | -i N | -f N] [input ...]
+
+The following options are accepted:
+
+.. program:: random
+
+.. option:: -h, --help
+
+ Show the help message and exit.
+
+.. option:: -c CHOICE [CHOICE ...]
+ --choice CHOICE [CHOICE ...]
+
+ Print a random choice, using :meth:`choice`.
+
+.. option:: -i <N>
+ --integer <N>
+
+ Print a random integer between 1 and N inclusive, using :meth:`randint`.
+
+.. option:: -f <N>
+ --float <N>
+
+ Print a random floating point number between 1 and N inclusive,
+ using :meth:`uniform`.
+
+If no options are given, the output depends on the input:
+
+* String or multiple: same as :option:`--choice`.
+* Integer: same as :option:`--integer`.
+* Float: same as :option:`--float`.
+
+.. _random-cli-example:
+
+Command-line example
+--------------------
+
+Here are some examples of the :mod:`!random` command-line interface:
+
+.. code-block:: console
+
+ $ # Choose one at random
+ $ python -m random egg bacon sausage spam "Lobster Thermidor aux crevettes with a Mornay sauce"
+ Lobster Thermidor aux crevettes with a Mornay sauce
+
+ $ # Random integer
+ $ python -m random 6
+ 6
+
+ $ # Random floating-point number
+ $ python -m random 1.8
+ 1.7080016272295635
+
+ $ # With explicit arguments
+ $ python -m random --choice egg bacon sausage spam "Lobster Thermidor aux crevettes with a Mornay sauce"
+ egg
+
+ $ python -m random --integer 6
+ 3
+
+ $ python -m random --float 1.8
+ 1.5666339105010318
+
+ $ python -m random --integer 6
+ 5
+
+ $ python -m random --float 6
+ 3.1942323316565915
diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst
index a5f5ba4..27a1aea 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -722,6 +722,12 @@ queue
termination.
(Contributed by Laurie Opperman and Yves Duprat in :gh:`104750`.)
+random
+------
+
+* Add a :ref:`command-line interface <random-cli>`.
+ (Contributed by Hugo van Kemenade in :gh:`54321`.)
+
re
--
* Rename :exc:`!re.error` to :exc:`re.PatternError` for improved clarity.
diff --git a/Lib/random.py b/Lib/random.py
index 875beb2..bcc11c7 100644
--- a/Lib/random.py
+++ b/Lib/random.py
@@ -996,5 +996,75 @@ if hasattr(_os, "fork"):
_os.register_at_fork(after_in_child=_inst.seed)
+# ------------------------------------------------------
+# -------------- command-line interface ----------------
+
+
+def _parse_args(arg_list: list[str] | None):
+ import argparse
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawTextHelpFormatter)
+ group = parser.add_mutually_exclusive_group()
+ group.add_argument(
+ "-c", "--choice", nargs="+",
+ help="print a random choice")
+ group.add_argument(
+ "-i", "--integer", type=int, metavar="N",
+ help="print a random integer between 1 and N inclusive")
+ group.add_argument(
+ "-f", "--float", type=float, metavar="N",
+ help="print a random floating point number between 1 and N inclusive")
+ group.add_argument(
+ "--test", type=int, const=10_000, nargs="?",
+ help=argparse.SUPPRESS)
+ parser.add_argument("input", nargs="*",
+ help="""\
+if no options given, output depends on the input
+ string or multiple: same as --choice
+ integer: same as --integer
+ float: same as --float""")
+ args = parser.parse_args(arg_list)
+ return args, parser.format_help()
+
+
+def main(arg_list: list[str] | None = None) -> int | str:
+ args, help_text = _parse_args(arg_list)
+
+ # Explicit arguments
+ if args.choice:
+ return choice(args.choice)
+
+ if args.integer is not None:
+ return randint(1, args.integer)
+
+ if args.float is not None:
+ return uniform(1, args.float)
+
+ if args.test:
+ _test(args.test)
+ return ""
+
+ # No explicit argument, select based on input
+ if len(args.input) == 1:
+ val = args.input[0]
+ try:
+ # Is it an integer?
+ val = int(val)
+ return randint(1, val)
+ except ValueError:
+ try:
+ # Is it a float?
+ val = float(val)
+ return uniform(1, val)
+ except ValueError:
+ # Split in case of space-separated string: "a b c"
+ return choice(val.split())
+
+ if len(args.input) >= 2:
+ return choice(args.input)
+
+ return help_text
+
+
if __name__ == '__main__':
- _test()
+ print(main())
diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py
index b1e4ef4..9a44ab1 100644
--- a/Lib/test/test_random.py
+++ b/Lib/test/test_random.py
@@ -4,6 +4,7 @@ import random
import os
import time
import pickle
+import shlex
import warnings
import test.support
@@ -1397,5 +1398,47 @@ class TestModule(unittest.TestCase):
support.wait_process(pid, exitcode=0)
+class CommandLineTest(unittest.TestCase):
+ def test_parse_args(self):
+ args, help_text = random._parse_args(shlex.split("--choice a b c"))
+ self.assertEqual(args.choice, ["a", "b", "c"])
+ self.assertTrue(help_text.startswith("usage: "))
+
+ args, help_text = random._parse_args(shlex.split("--integer 5"))
+ self.assertEqual(args.integer, 5)
+ self.assertTrue(help_text.startswith("usage: "))
+
+ args, help_text = random._parse_args(shlex.split("--float 2.5"))
+ self.assertEqual(args.float, 2.5)
+ self.assertTrue(help_text.startswith("usage: "))
+
+ args, help_text = random._parse_args(shlex.split("a b c"))
+ self.assertEqual(args.input, ["a", "b", "c"])
+ self.assertTrue(help_text.startswith("usage: "))
+
+ args, help_text = random._parse_args(shlex.split("5"))
+ self.assertEqual(args.input, ["5"])
+ self.assertTrue(help_text.startswith("usage: "))
+
+ args, help_text = random._parse_args(shlex.split("2.5"))
+ self.assertEqual(args.input, ["2.5"])
+ self.assertTrue(help_text.startswith("usage: "))
+
+ def test_main(self):
+ for command, expected in [
+ ("--choice a b c", "b"),
+ ('"a b c"', "b"),
+ ("a b c", "b"),
+ ("--choice 'a a' 'b b' 'c c'", "b b"),
+ ("'a a' 'b b' 'c c'", "b b"),
+ ("--integer 5", 4),
+ ("5", 4),
+ ("--float 2.5", 2.266632777287572),
+ ("2.5", 2.266632777287572),
+ ]:
+ random.seed(0)
+ self.assertEqual(random.main(shlex.split(command)), expected)
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2024-04-21-18-55-42.gh-issue-118131.eAT0is.rst b/Misc/NEWS.d/next/Library/2024-04-21-18-55-42.gh-issue-118131.eAT0is.rst
new file mode 100644
index 0000000..83ed66c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-04-21-18-55-42.gh-issue-118131.eAT0is.rst
@@ -0,0 +1,2 @@
+Add command-line interface for the :mod:`random` module. Patch by Hugo van
+Kemenade.