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/sizeformatter.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 

26from typing import Union 

27 

28 

29def sizeof_fmt(num: float, suffix: str = 'B') -> str: 

30 """ 

31 Formats a number of bytes in a human-readable binary format (e.g. ``2048`` 

32 becomes ``'2 KiB'``); from https://stackoverflow.com/questions/1094841. 

33 """ 

34 for unit in ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'): 

35 if abs(num) < 1024.0: 

36 return "%3.1f%s%s" % (num, unit, suffix) 

37 num /= 1024.0 

38 return "%.1f%s%s" % (num, 'Yi', suffix) 

39 

40 

41# see: https://en.wikipedia.org/wiki/Binary_prefix 

42SYMBOLS = { 

43 'customary': ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'), 

44 'customary_ext': ('byte', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa', 

45 'zetta', 'iotta'), 

46 'iec': ('Bi', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), 

47 'iec_ext': ('byte', 'kibi', 'mebi', 'gibi', 'tebi', 'pebi', 'exbi', 

48 'zebi', 'yobi'), 

49} 

50 

51 

52def bytes2human(n: Union[int, float], 

53 format: str = '%(value).1f %(symbol)s', 

54 symbols: str = 'customary') -> str: 

55 """ 

56 Converts a number of bytes into a human-readable format. 

57 From https://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/. 

58  

59 Args: 

60 n: number of bytes 

61 format: a format specification string 

62 symbols: can be one of ``"customary"``, ``"customary_ext"``, ``"iec"`` 

63 or ``"iec_ext"``; see https://goo.gl/kTQMs 

64 

65 Returns: 

66 the formatted number 

67  

68 Examples: 

69 

70 >>> bytes2human(0) 

71 '0.0 B' 

72 >>> bytes2human(0.9) 

73 '0.0 B' 

74 >>> bytes2human(1) 

75 '1.0 B' 

76 >>> bytes2human(1.9) 

77 '1.0 B' 

78 >>> bytes2human(1024) 

79 '1.0 K' 

80 >>> bytes2human(1048576) 

81 '1.0 M' 

82 >>> bytes2human(1099511627776127398123789121) 

83 '909.5 Y' 

84 

85 >>> bytes2human(9856, symbols="customary") 

86 '9.6 K' 

87 >>> bytes2human(9856, symbols="customary_ext") 

88 '9.6 kilo' 

89 >>> bytes2human(9856, symbols="iec") 

90 '9.6 Ki' 

91 >>> bytes2human(9856, symbols="iec_ext") 

92 '9.6 kibi' 

93 

94 >>> bytes2human(10000, "%(value).1f %(symbol)s/sec") 

95 '9.8 K/sec' 

96 

97 >>> # precision can be adjusted by playing with %f operator 

98 >>> bytes2human(10000, format="%(value).5f %(symbol)s") 

99 '9.76562 K' 

100 

101 """ # noqa 

102 n = int(n) 

103 if n < 0: 

104 raise ValueError("n < 0") 

105 symbols = SYMBOLS[symbols] 

106 prefix = {} 

107 for i, s in enumerate(symbols[1:]): 

108 prefix[s] = 1 << (i + 1) * 10 

109 for symbol in reversed(symbols[1:]): 

110 if n >= prefix[symbol]: 

111 value = float(n) / prefix[symbol] 

112 return format % locals() 

113 return format % dict(symbol=symbols[0], value=n) 

114 

115 

116def human2bytes(s: str) -> int: 

117 """ 

118 Modified from 

119 https://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/. 

120  

121 Attempts to guess the string format based on default symbols 

122 set and return the corresponding bytes as an integer. 

123 When unable to recognize the format, :exc:`ValueError` is raised. 

124 

125 >>> human2bytes('0 B') 

126 0 

127 >>> human2bytes('1 K') 

128 1024 

129 >>> human2bytes('1 M') 

130 1048576 

131 >>> human2bytes('1 Gi') 

132 1073741824 

133 >>> human2bytes('1 tera') 

134 1099511627776 

135 

136 >>> human2bytes('0.5kilo') 

137 512 

138 >>> human2bytes('0.1 byte') 

139 0 

140 >>> human2bytes('1 k') # k is an alias for K 

141 1024 

142 >>> human2bytes('12 foo') 

143 Traceback (most recent call last): 

144 ... 

145 ValueError: can't interpret '12 foo' 

146 """ # noqa 

147 if not s: 

148 raise ValueError(f"Can't interpret {s!r} as integer") 

149 try: 

150 return int(s) 

151 except ValueError: 

152 pass 

153 init = s 

154 num = "" 

155 while s and s[0:1].isdigit() or s[0:1] == '.': 

156 num += s[0] 

157 s = s[1:] 

158 num = float(num) 

159 letter = s.strip() 

160 for name, sset in SYMBOLS.items(): 

161 if letter in sset: 

162 break 

163 else: 

164 if letter == 'k': 

165 # treat 'k' as an alias for 'K' as per https://en.wikipedia.org/wiki/Binary_prefix # noqa 

166 sset = SYMBOLS['customary'] 

167 letter = letter.upper() 

168 else: 

169 raise ValueError("can't interpret %r" % init) 

170 prefix = {sset[0]: 1} 

171 for i, s in enumerate(sset[1:]): 

172 prefix[s] = 1 << (i + 1) * 10 

173 return int(num * prefix[letter])