Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2# cardinal_pythonlib/randomness.py 

3 

4""" 

5=============================================================================== 

6 

7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com). 

8 

9 This file is part of cardinal_pythonlib. 

10 

11 Licensed under the Apache License, Version 2.0 (the "License"); 

12 you may not use this file except in compliance with the License. 

13 You may obtain a copy of the License at 

14 

15 https://www.apache.org/licenses/LICENSE-2.0 

16 

17 Unless required by applicable law or agreed to in writing, software 

18 distributed under the License is distributed on an "AS IS" BASIS, 

19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

20 See the License for the specific language governing permissions and 

21 limitations under the License. 

22 

23=============================================================================== 

24 

25**Random number generation.** 

26 

27""" 

28 

29import base64 

30import os 

31from random import random as random_random 

32import secrets 

33import string 

34 

35 

36# ============================================================================= 

37# Creating random strings 

38# ============================================================================= 

39 

40def create_base64encoded_randomness(num_bytes: int) -> str: 

41 """ 

42 Create and return ``num_bytes`` of random data. 

43 

44 The result is encoded in a string with URL-safe ``base64`` encoding. 

45 

46 Used (for example) to generate session tokens. 

47 

48 Which generator to use? See 

49 https://cryptography.io/en/latest/random-numbers/. 

50 

51 Do NOT use these methods: 

52 

53 .. code-block:: python 

54 

55 randbytes = M2Crypto.m2.rand_bytes(num_bytes) # NO! 

56 randbytes = Crypto.Random.get_random_bytes(num_bytes) # NO! 

57 

58 Instead, do this: 

59 

60 .. code-block:: python 

61 

62 randbytes = os.urandom(num_bytes) # YES 

63 """ 

64 randbytes = os.urandom(num_bytes) # YES 

65 return base64.urlsafe_b64encode(randbytes).decode('ascii') 

66 

67 

68def generate_random_string(length: int, 

69 characters: str = None) -> str: 

70 """ 

71 Generates a random string of the specified length. 

72 """ 

73 characters = characters or ( 

74 string.ascii_letters + string.digits + string.punctuation 

75 ) 

76 # We use secrets.choice() rather than random.choices() as it's better 

77 # for security/cryptography purposes. 

78 return "".join(secrets.choice(characters) for _ in range(length)) 

79 

80 

81# ============================================================================= 

82# Coin flips 

83# ============================================================================= 

84 

85def coin(p: float) -> bool: 

86 """ 

87 Flips a biased coin; returns ``True`` or ``False``, with the specified 

88 probability being that of ``True``. 

89 """ 

90 # Slower code: 

91 

92 # assert 0 <= p <= 1 

93 # r = random.random() # range [0.0, 1.0), i.e. 0 <= r < 1 

94 # return r < p 

95 

96 # Check edge cases: 

97 # - if p == 0, impossible that r < p, since r >= 0 

98 # - if p == 1, always true that r < p, since r < 1 

99 

100 # Faster code: 

101 

102 return random_random() < p 

103 

104 

105# ============================================================================= 

106# Testing 

107# ============================================================================= 

108 

109def _test_coin() -> None: 

110 """ 

111 Tests the :func:`coin` function. 

112 """ 

113 probabilities = [0, 0.25, 0.5, 0.75, 1] 

114 n_values = [10, 1000, 1000000] 

115 for p in probabilities: 

116 for n in n_values: 

117 coins = [1 if coin(p) else 0 for _ in range(n)] 

118 s = sum(coins) 

119 print(f"coin: p = {p}, n = {n} -> {s} true") 

120 

121 

122if __name__ == '__main__': 

123 _test_coin()