Skip to content

Commit b2faab4

Browse files
committed
refactor(human.py): pre-compute mappings as module constants
1 parent 47b298f commit b2faab4

1 file changed

Lines changed: 176 additions & 106 deletions

File tree

human.py

Lines changed: 176 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,146 @@
1313
"""
1414

1515
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
16-
__version__ = '2025042001'
16+
__version__ = '2025121201'
1717

1818
import math
1919

20+
# Pre-computed thresholds for bits2human (descending order)
21+
_BITS_THRESHOLDS = (
22+
('YiB', 9671406556917033397649408),
23+
('ZiB', 9444732965739290427392),
24+
('EiB', 9223372036854775808),
25+
('PiB', 9007199254740992),
26+
('TiB', 8796093022208),
27+
('GiB', 8589934592),
28+
('MiB', 8388608),
29+
('KiB', 8192),
30+
('B', 8),
31+
)
32+
33+
# Pre-computed thresholds for bps2human (descending order)
34+
_BPS_THRESHOLDS = (
35+
('Ybps', 1000000000000000000000000),
36+
('Zbps', 1000000000000000000000),
37+
('Ebps', 1000000000000000000),
38+
('Pbps', 1000000000000000),
39+
('Tbps', 1000000000000),
40+
('Gbps', 1000000000),
41+
('Mbps', 1000000),
42+
('Kbps', 1000),
43+
('bps', 1),
44+
)
45+
46+
# Pre-computed thresholds for bytes2human (descending order)
47+
_BYTES_THRESHOLDS = (
48+
('YiB', 1208925819614629174706176),
49+
('ZiB', 1180591620717411303424),
50+
('EiB', 1152921504606846976),
51+
('PiB', 1125899906842624),
52+
('TiB', 1099511627776),
53+
('GiB', 1073741824),
54+
('MiB', 1048576),
55+
('KiB', 1024),
56+
('B', 1),
57+
)
58+
59+
# Pre-computed multipliers for human2bytes (binary units always 1024-based)
60+
_BINARY_MULTIPLIERS = (
61+
('yib', 1208925819614629174706176),
62+
('zib', 1180591620717411303424),
63+
('eib', 1152921504606846976),
64+
('pib', 1125899906842624),
65+
('tib', 1099511627776),
66+
('gib', 1073741824),
67+
('mib', 1048576),
68+
('kib', 1024),
69+
)
70+
71+
# Pre-computed multipliers for human2bytes (decimal, base 1024)
72+
_DECIMAL_1024_MULTIPLIERS = (
73+
('yb', 1208925819614629174706176),
74+
('y', 1208925819614629174706176),
75+
('zb', 1180591620717411303424),
76+
('z', 1180591620717411303424),
77+
('eb', 1152921504606846976),
78+
('e', 1152921504606846976),
79+
('pb', 1125899906842624),
80+
('p', 1125899906842624),
81+
('tb', 1099511627776),
82+
('t', 1099511627776),
83+
('gb', 1073741824),
84+
('g', 1073741824),
85+
('mb', 1048576),
86+
('m', 1048576),
87+
('kb', 1024),
88+
('k', 1024),
89+
)
90+
91+
# Pre-computed multipliers for human2bytes (decimal, base 1000)
92+
_DECIMAL_1000_MULTIPLIERS = (
93+
('yb', 1000000000000000000000000),
94+
('y', 1000000000000000000000000),
95+
('zb', 1000000000000000000000),
96+
('z', 1000000000000000000000),
97+
('eb', 1000000000000000000),
98+
('e', 1000000000000000000),
99+
('pb', 1000000000000000),
100+
('p', 1000000000000000),
101+
('tb', 1000000000000),
102+
('t', 1000000000000),
103+
('gb', 1000000000),
104+
('g', 1000000000),
105+
('mb', 1000000),
106+
('m', 1000000),
107+
('kb', 1000),
108+
('k', 1000),
109+
)
110+
111+
# Pre-computed unit to seconds mapping for human2seconds
112+
_UNIT_TO_SECONDS = {
113+
's': 1,
114+
'm': 60,
115+
'h': 3600,
116+
'D': 86400,
117+
'W': 604800,
118+
'M': 2592000,
119+
'Y': 31536000,
120+
}
121+
122+
# Pre-computed SI prefixes for number2human
123+
_SI_PREFIXES = ('', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
124+
_SI_PREFIXES_MAX_IDX = 8 # len(_SI_PREFIXES) - 1
125+
126+
# Pre-computed time units for seconds2human (full names)
127+
_TIME_UNITS_FULL = (
128+
('years', 31536000),
129+
('months', 2592000),
130+
('weeks', 604800),
131+
('days', 86400),
132+
('hours', 3600),
133+
('minutes', 60),
134+
('seconds', 1),
135+
('millisecs', 1e-3),
136+
('microsecs', 1e-6),
137+
('nanosecs', 1e-9),
138+
('picosecs', 1e-12),
139+
)
140+
141+
# Pre-computed time units for seconds2human (short names)
142+
_TIME_UNITS_SHORT = (
143+
('Y', 31536000),
144+
('M', 2592000),
145+
('W', 604800),
146+
('D', 86400),
147+
('h', 3600),
148+
('m', 60),
149+
('s', 1),
150+
('ms', 1e-3),
151+
('us', 1e-6),
152+
('ns', 1e-9),
153+
('ps', 1e-12),
154+
)
155+
20156

21157
def bits2human(n, decimals=1, space=False):
22158
"""
@@ -44,19 +180,14 @@ def bits2human(n, decimals=1, space=False):
44180
>>> bits2human(8192, decimals=3, space=True)
45181
'1.000 KiB'
46182
"""
47-
symbols = ('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')
48-
prefix = {'B': 8}
49-
for i, symbol in enumerate(symbols[1:], 1):
50-
prefix[symbol] = 1024 ** i * 8
51-
52-
for symbol in reversed(symbols):
53-
if n >= prefix[symbol]:
54-
value = float(n) / prefix[symbol]
55-
sep = ' ' if space else ''
183+
sep = ' ' if space else ''
184+
185+
for symbol, threshold in _BITS_THRESHOLDS:
186+
if n >= threshold:
187+
value = float(n) / threshold
56188
return f'{value:.{decimals}f}{sep}{symbol}'
57189

58-
sep = ' ' if space else ''
59-
return f'{n:.{decimals}f}{sep}{symbols[0]}'
190+
return f'{n:.{decimals}f}{sep}B'
60191

61192

62193
def bps2human(n, decimals=1, space=False):
@@ -87,17 +218,14 @@ def bps2human(n, decimals=1, space=False):
87218
>>> bps2human(72000000, decimals=2, space=True)
88219
'72.00 Mbps'
89220
"""
90-
symbols = ('bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps', 'Pbps', 'Ebps', 'Zbps', 'Ybps')
91-
prefix = {symbol: 1000 ** i for i, symbol in enumerate(symbols)}
221+
sep = ' ' if space else ''
92222

93-
for symbol in reversed(symbols[1:]):
94-
if n >= prefix[symbol]:
95-
value = float(n) / prefix[symbol]
96-
sep = ' ' if space else ''
223+
for symbol, threshold in _BPS_THRESHOLDS:
224+
if n >= threshold:
225+
value = float(n) / threshold
97226
return f'{value:.{decimals}f}{sep}{symbol}'
98227

99-
sep = ' ' if space else ''
100-
return f'{n:.{decimals}f}{sep}{symbols[0]}'
228+
return f'{n:.{decimals}f}{sep}bps'
101229

102230

103231
def bytes2human(n, decimals=1, space=False):
@@ -131,17 +259,14 @@ def bytes2human(n, decimals=1, space=False):
131259
>>> bytes2human(1048576, decimals=1, space=True)
132260
'1.0 MiB'
133261
"""
134-
symbols = ('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')
135-
prefix = {symbol: 1 << (i + 1) * 10 for i, symbol in enumerate(symbols[1:])}
262+
sep = ' ' if space else ''
136263

137-
for symbol in reversed(symbols[1:]):
138-
if n >= prefix[symbol]:
139-
value = float(n) / prefix[symbol]
140-
sep = ' ' if space else ''
264+
for symbol, threshold in _BYTES_THRESHOLDS:
265+
if n >= threshold:
266+
value = float(n) / threshold
141267
return f'{value:.{decimals}f}{sep}{symbol}'
142268

143-
sep = ' ' if space else ''
144-
return f'{n:.{decimals}f}{sep}{symbols[0]}'
269+
return f'{n:.{decimals}f}{sep}B'
145270

146271

147272
def extract_hrnumbers(s, boundaries=None):
@@ -191,7 +316,7 @@ def human2bytes(string, binary=True):
191316
"""
192317
Converts a human-readable string to the equivalent number of bytes.
193318
194-
This function converts a string representation of a file size, such as '3.072GiB' or '3.072GB',
319+
This function converts a string representation of a file size, such as '3.072GiB' or '3.072GB',
195320
into the corresponding number of bytes. It supports both binary (base 1024) and decimal
196321
(base 1000) units.
197322
@@ -212,43 +337,23 @@ def human2bytes(string, binary=True):
212337
3072000000
213338
"""
214339
try:
215-
string = string.lower()
216-
if 'kib' in string:
217-
return int(float(string.replace('kib', '').strip()) * 1024)
218-
if 'mib' in string:
219-
return int(float(string.replace('mib', '').strip()) * 1024**2)
220-
if 'gib' in string:
221-
return int(float(string.replace('gib', '').strip()) * 1024**3)
222-
if 'tib' in string:
223-
return int(float(string.replace('tib', '').strip()) * 1024**4)
224-
if 'pib' in string:
225-
return int(float(string.replace('pib', '').strip()) * 1024**5)
226-
if 'eib' in string:
227-
return int(float(string.replace('eib', '').strip()) * 1024**6)
228-
if 'zib' in string:
229-
return int(float(string.replace('zib', '').strip()) * 1024**7)
230-
if 'yib' in string:
231-
return int(float(string.replace('yib', '').strip()) * 1024**8)
232-
233-
base = 1024 if binary else 1000
234-
if 'k' in string: # matches "kb" or "k"
235-
return int(float(string.replace('kb', '').replace('k', '').strip()) * base)
236-
if 'm' in string:
237-
return int(float(string.replace('mb', '').replace('m', '').strip()) * base**2)
238-
if 'g' in string:
239-
return int(float(string.replace('gb', '').replace('g', '').strip()) * base**3)
240-
if 't' in string:
241-
return int(float(string.replace('tb', '').replace('t', '').strip()) * base**4)
242-
if 'p' in string:
243-
return int(float(string.replace('pb', '').replace('p', '').strip()) * base**5)
244-
if 'e' in string:
245-
return int(float(string.replace('eb', '').replace('e', '').strip()) * base**6)
246-
if 'z' in string:
247-
return int(float(string.replace('zb', '').replace('z', '').strip()) * base**7)
248-
if 'y' in string:
249-
return int(float(string.replace('yb', '').replace('y', '').strip()) * base**8)
250-
if 'b' in string:
251-
return int(float(string.replace('b', '').strip()))
340+
string_lower = string.lower()
341+
342+
# Check binary units first (always 1024-based)
343+
for unit, multiplier in _BINARY_MULTIPLIERS:
344+
if unit in string_lower:
345+
return int(float(string_lower.replace(unit, '').strip()) * multiplier)
346+
347+
# Check decimal units (base depends on binary parameter)
348+
multipliers = _DECIMAL_1024_MULTIPLIERS if binary else _DECIMAL_1000_MULTIPLIERS
349+
for unit, multiplier in multipliers:
350+
if unit in string_lower:
351+
return int(float(string_lower.replace(unit, '').strip()) * multiplier)
352+
353+
# Check for plain 'b' (bytes)
354+
if 'b' in string_lower:
355+
return int(float(string_lower.replace('b', '').strip()))
356+
252357
return 0
253358
except Exception:
254359
return 0
@@ -307,32 +412,22 @@ def human2seconds(string):
307412
>>> human2seconds('invalid')
308413
0
309414
"""
310-
unit_to_seconds = {
311-
's': 1,
312-
'm': 60,
313-
'h': 3600,
314-
'D': 86400,
315-
'W': 604800,
316-
'M': 2592000,
317-
'Y': 31536000,
318-
}
319-
320415
string = string.strip()
321416
if not string or len(string) < 2:
322417
return 0
323418

324419
unit = string[-1]
325420
value_str = string[:-1]
326421

327-
if unit not in unit_to_seconds:
422+
if unit not in _UNIT_TO_SECONDS:
328423
return 0
329424

330425
try:
331426
value = float(value_str)
332427
except (ValueError, TypeError):
333428
return 0
334429

335-
return int(value * unit_to_seconds[unit])
430+
return int(value * _UNIT_TO_SECONDS[unit])
336431

337432

338433
def humanduration2seconds(text):
@@ -457,7 +552,6 @@ def number2human(number):
457552
>>> number2human(9223372036854775808)
458553
'9.2E'
459554
"""
460-
millnames = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
461555
try:
462556
number = float(number)
463557
except Exception:
@@ -468,9 +562,9 @@ def number2human(number):
468562
else:
469563
millidx = int(math.floor(math.log10(abs(number)) / 3))
470564

471-
millidx = max(0, min(len(millnames) - 1, millidx))
565+
millidx = max(0, min(_SI_PREFIXES_MAX_IDX, millidx))
472566
scaled = number / 10**(3 * millidx)
473-
return f'{scaled:.1f}{millnames[millidx]}'
567+
return f'{scaled:.1f}{_SI_PREFIXES[millidx]}'
474568

475569

476570
def seconds2human(seconds, keep_short=True, full_name=False):
@@ -521,31 +615,7 @@ def seconds2human(seconds, keep_short=True, full_name=False):
521615
"""
522616
seconds = float(seconds)
523617

524-
units = (
525-
('years', 60 * 60 * 24 * 365),
526-
('months', 60 * 60 * 24 * 30),
527-
('weeks', 60 * 60 * 24 * 7),
528-
('days', 60 * 60 * 24),
529-
('hours', 60 * 60),
530-
('minutes', 60),
531-
('seconds', 1),
532-
('millisecs', 1e-3),
533-
('microsecs', 1e-6),
534-
('nanosecs', 1e-9),
535-
('picosecs', 1e-12),
536-
) if full_name else (
537-
('Y', 60 * 60 * 24 * 365),
538-
('M', 60 * 60 * 24 * 30),
539-
('W', 60 * 60 * 24 * 7),
540-
('D', 60 * 60 * 24),
541-
('h', 60 * 60),
542-
('m', 60),
543-
('s', 1),
544-
('ms', 1e-3),
545-
('us', 1e-6),
546-
('ns', 1e-9),
547-
('ps', 1e-12),
548-
)
618+
units = _TIME_UNITS_FULL if full_name else _TIME_UNITS_SHORT
549619

550620
result = []
551621
for name, count in units:

0 commit comments

Comments
 (0)