Skip to content

Commit 3b32575

Browse files
authored
gh-118131: Command-line interface for the random module (#118132)
1 parent fed8d73 commit 3b32575

File tree

6 files changed

+203
-1
lines changed

6 files changed

+203
-1
lines changed

Doc/library/cmdline.rst

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ The following modules have a command-line interface.
3636
* :mod:`pyclbr`
3737
* :mod:`pydoc`
3838
* :mod:`quopri`
39+
* :ref:`random <random-cli>`
3940
* :mod:`runpy`
4041
* :ref:`site <site-commandline>`
4142
* :ref:`sqlite3 <sqlite3-cli>`

Doc/library/random.rst

+80
Original file line numberDiff line numberDiff line change
@@ -706,3 +706,83 @@ positive unnormalized float and is equal to ``math.ulp(0.0)``.)
706706
<https://allendowney.com/research/rand/downey07randfloat.pdf>`_ a
707707
paper by Allen B. Downey describing ways to generate more
708708
fine-grained floats than normally generated by :func:`.random`.
709+
710+
.. _random-cli:
711+
712+
Command-line usage
713+
------------------
714+
715+
.. versionadded:: 3.13
716+
717+
The :mod:`!random` module can be executed from the command line.
718+
719+
.. code-block:: sh
720+
721+
python -m random [-h] [-c CHOICE [CHOICE ...] | -i N | -f N] [input ...]
722+
723+
The following options are accepted:
724+
725+
.. program:: random
726+
727+
.. option:: -h, --help
728+
729+
Show the help message and exit.
730+
731+
.. option:: -c CHOICE [CHOICE ...]
732+
--choice CHOICE [CHOICE ...]
733+
734+
Print a random choice, using :meth:`choice`.
735+
736+
.. option:: -i <N>
737+
--integer <N>
738+
739+
Print a random integer between 1 and N inclusive, using :meth:`randint`.
740+
741+
.. option:: -f <N>
742+
--float <N>
743+
744+
Print a random floating point number between 1 and N inclusive,
745+
using :meth:`uniform`.
746+
747+
If no options are given, the output depends on the input:
748+
749+
* String or multiple: same as :option:`--choice`.
750+
* Integer: same as :option:`--integer`.
751+
* Float: same as :option:`--float`.
752+
753+
.. _random-cli-example:
754+
755+
Command-line example
756+
--------------------
757+
758+
Here are some examples of the :mod:`!random` command-line interface:
759+
760+
.. code-block:: console
761+
762+
$ # Choose one at random
763+
$ python -m random egg bacon sausage spam "Lobster Thermidor aux crevettes with a Mornay sauce"
764+
Lobster Thermidor aux crevettes with a Mornay sauce
765+
766+
$ # Random integer
767+
$ python -m random 6
768+
6
769+
770+
$ # Random floating-point number
771+
$ python -m random 1.8
772+
1.7080016272295635
773+
774+
$ # With explicit arguments
775+
$ python -m random --choice egg bacon sausage spam "Lobster Thermidor aux crevettes with a Mornay sauce"
776+
egg
777+
778+
$ python -m random --integer 6
779+
3
780+
781+
$ python -m random --float 1.8
782+
1.5666339105010318
783+
784+
$ python -m random --integer 6
785+
5
786+
787+
$ python -m random --float 6
788+
3.1942323316565915

Doc/whatsnew/3.13.rst

+6
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,12 @@ queue
722722
termination.
723723
(Contributed by Laurie Opperman and Yves Duprat in :gh:`104750`.)
724724

725+
random
726+
------
727+
728+
* Add a :ref:`command-line interface <random-cli>`.
729+
(Contributed by Hugo van Kemenade in :gh:`54321`.)
730+
725731
re
726732
--
727733
* Rename :exc:`!re.error` to :exc:`re.PatternError` for improved clarity.

Lib/random.py

+71-1
Original file line numberDiff line numberDiff line change
@@ -996,5 +996,75 @@ def _test(N=10_000):
996996
_os.register_at_fork(after_in_child=_inst.seed)
997997

998998

999+
# ------------------------------------------------------
1000+
# -------------- command-line interface ----------------
1001+
1002+
1003+
def _parse_args(arg_list: list[str] | None):
1004+
import argparse
1005+
parser = argparse.ArgumentParser(
1006+
formatter_class=argparse.RawTextHelpFormatter)
1007+
group = parser.add_mutually_exclusive_group()
1008+
group.add_argument(
1009+
"-c", "--choice", nargs="+",
1010+
help="print a random choice")
1011+
group.add_argument(
1012+
"-i", "--integer", type=int, metavar="N",
1013+
help="print a random integer between 1 and N inclusive")
1014+
group.add_argument(
1015+
"-f", "--float", type=float, metavar="N",
1016+
help="print a random floating point number between 1 and N inclusive")
1017+
group.add_argument(
1018+
"--test", type=int, const=10_000, nargs="?",
1019+
help=argparse.SUPPRESS)
1020+
parser.add_argument("input", nargs="*",
1021+
help="""\
1022+
if no options given, output depends on the input
1023+
string or multiple: same as --choice
1024+
integer: same as --integer
1025+
float: same as --float""")
1026+
args = parser.parse_args(arg_list)
1027+
return args, parser.format_help()
1028+
1029+
1030+
def main(arg_list: list[str] | None = None) -> int | str:
1031+
args, help_text = _parse_args(arg_list)
1032+
1033+
# Explicit arguments
1034+
if args.choice:
1035+
return choice(args.choice)
1036+
1037+
if args.integer is not None:
1038+
return randint(1, args.integer)
1039+
1040+
if args.float is not None:
1041+
return uniform(1, args.float)
1042+
1043+
if args.test:
1044+
_test(args.test)
1045+
return ""
1046+
1047+
# No explicit argument, select based on input
1048+
if len(args.input) == 1:
1049+
val = args.input[0]
1050+
try:
1051+
# Is it an integer?
1052+
val = int(val)
1053+
return randint(1, val)
1054+
except ValueError:
1055+
try:
1056+
# Is it a float?
1057+
val = float(val)
1058+
return uniform(1, val)
1059+
except ValueError:
1060+
# Split in case of space-separated string: "a b c"
1061+
return choice(val.split())
1062+
1063+
if len(args.input) >= 2:
1064+
return choice(args.input)
1065+
1066+
return help_text
1067+
1068+
9991069
if __name__ == '__main__':
1000-
_test()
1070+
print(main())

Lib/test/test_random.py

+43
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import time
66
import pickle
7+
import shlex
78
import warnings
89
import test.support
910

@@ -1397,5 +1398,47 @@ def test_after_fork(self):
13971398
support.wait_process(pid, exitcode=0)
13981399

13991400

1401+
class CommandLineTest(unittest.TestCase):
1402+
def test_parse_args(self):
1403+
args, help_text = random._parse_args(shlex.split("--choice a b c"))
1404+
self.assertEqual(args.choice, ["a", "b", "c"])
1405+
self.assertTrue(help_text.startswith("usage: "))
1406+
1407+
args, help_text = random._parse_args(shlex.split("--integer 5"))
1408+
self.assertEqual(args.integer, 5)
1409+
self.assertTrue(help_text.startswith("usage: "))
1410+
1411+
args, help_text = random._parse_args(shlex.split("--float 2.5"))
1412+
self.assertEqual(args.float, 2.5)
1413+
self.assertTrue(help_text.startswith("usage: "))
1414+
1415+
args, help_text = random._parse_args(shlex.split("a b c"))
1416+
self.assertEqual(args.input, ["a", "b", "c"])
1417+
self.assertTrue(help_text.startswith("usage: "))
1418+
1419+
args, help_text = random._parse_args(shlex.split("5"))
1420+
self.assertEqual(args.input, ["5"])
1421+
self.assertTrue(help_text.startswith("usage: "))
1422+
1423+
args, help_text = random._parse_args(shlex.split("2.5"))
1424+
self.assertEqual(args.input, ["2.5"])
1425+
self.assertTrue(help_text.startswith("usage: "))
1426+
1427+
def test_main(self):
1428+
for command, expected in [
1429+
("--choice a b c", "b"),
1430+
('"a b c"', "b"),
1431+
("a b c", "b"),
1432+
("--choice 'a a' 'b b' 'c c'", "b b"),
1433+
("'a a' 'b b' 'c c'", "b b"),
1434+
("--integer 5", 4),
1435+
("5", 4),
1436+
("--float 2.5", 2.266632777287572),
1437+
("2.5", 2.266632777287572),
1438+
]:
1439+
random.seed(0)
1440+
self.assertEqual(random.main(shlex.split(command)), expected)
1441+
1442+
14001443
if __name__ == "__main__":
14011444
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add command-line interface for the :mod:`random` module. Patch by Hugo van
2+
Kemenade.

0 commit comments

Comments
 (0)