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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 80 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 71 additions & 1 deletion
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

Lines changed: 43 additions & 0 deletions
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()
Lines changed: 2 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy