Байты до человека читаемы и обратно. без потери данных
Мне нужно преобразовать строки, которые содержат использование памяти в байтах, например: 1048576
(что составляет 1 млн.), именно так, в удобочитаемой версии, и наоборот.
Примечание: я уже посмотрел здесь: повторно используемую библиотеку, чтобы получить удобочитаемую версию размера файла?
И здесь (даже если это не python): Как преобразовать читаемый человеком объем памяти в байты?
Ничто до сих пор не помогло мне, поэтому я посмотрел в другом месте.
Я нашел кое-что, что делает это для меня здесь: http://code.google.com/p/pyftpdlib/source/browse/trunk/test/bench.py?spec=svn984&r=984 или, для более короткого URL: http://goo.gl/zeJZl
Код:
def bytes2human(n, format="%(value)i%(symbol)s"):
"""
>>> bytes2human(10000)
'9K'
>>> bytes2human(100001221)
'95M'
"""
symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
prefix = {}
for i, s in enumerate(symbols[1:]):
prefix[s] = 1 << (i+1)*10
for symbol in reversed(symbols[1:]):
if n >= prefix[symbol]:
value = float(n) / prefix[symbol]
return format % locals()
return format % dict(symbol=symbols[0], value=n)
А также функция конвертации в другую сторону (тот же сайт):
def human2bytes(s):
"""
>>> human2bytes('1M')
1048576
>>> human2bytes('1G')
1073741824
"""
symbols = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
letter = s[-1:].strip().upper()
num = s[:-1]
assert num.isdigit() and letter in symbols
num = float(num)
prefix = {symbols[0]:1}
for i, s in enumerate(symbols[1:]):
prefix[s] = 1 << (i+1)*10
return int(num * prefix[letter])
Это здорово и все, но в нем есть потеря информации, например:
>>> bytes2human(10000)
'9K'
>>> human2bytes('9K')
9216
Чтобы попытаться решить эту проблему, я изменяю форматирование в функции bytes2human
В: format="%(value).3f%(symbol)s")
Что гораздо приятнее, давая мне такие результаты:
>>> bytes2human(10000)
'9.766K'
но когда я пытаюсь преобразовать их обратно с human2bytes
функция:
>>> human2bytes('9.766K')
Traceback (most recent call last):
File "<pyshell#366>", line 1, in <module>
human2bytes('9.766K')
File "<pyshell#359>", line 12, in human2bytes
assert num.isdigit() and letter in symbols
AssertionError
Это из-за .
Итак, мой вопрос: как я могу преобразовать читаемую человеком версию обратно в байтовую версию без потери данных?
Примечание: я знаю, что 3 знака после запятой - это тоже небольшая потеря данных. Но для целей этого вопроса давайте сейчас проигнорируем это, я всегда могу изменить это на что-то большее.
2 ответа
Получается, что ответ был намного проще, чем я думал - одна из приведенных мной ссылок фактически привела к гораздо более подробной версии функции:
Который способен справиться с любой областью, которую я ему даю.
Но спасибо за вашу помощь:
Код, скопированный здесь для потомков:
## {{{ http://code.activestate.com/recipes/578019/ (r15)
#!/usr/bin/env python
"""
Bytes-to-human / human-to-bytes converter.
Based on: http://goo.gl/kTQMs
Working with Python 2.x and 3.x.
Author: Giampaolo Rodola' <g.rodola [AT] gmail [DOT] com>
License: MIT
"""
# see: http://goo.gl/kTQMs
SYMBOLS = {
'customary' : ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'),
'customary_ext' : ('byte', 'kilo', 'mega', 'giga', 'tera', 'peta', 'exa',
'zetta', 'iotta'),
'iec' : ('Bi', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'),
'iec_ext' : ('byte', 'kibi', 'mebi', 'gibi', 'tebi', 'pebi', 'exbi',
'zebi', 'yobi'),
}
def bytes2human(n, format='%(value).1f %(symbol)s', symbols='customary'):
"""
Convert n bytes into a human readable string based on format.
symbols can be either "customary", "customary_ext", "iec" or "iec_ext",
see: http://goo.gl/kTQMs
>>> bytes2human(0)
'0.0 B'
>>> bytes2human(0.9)
'0.0 B'
>>> bytes2human(1)
'1.0 B'
>>> bytes2human(1.9)
'1.0 B'
>>> bytes2human(1024)
'1.0 K'
>>> bytes2human(1048576)
'1.0 M'
>>> bytes2human(1099511627776127398123789121)
'909.5 Y'
>>> bytes2human(9856, symbols="customary")
'9.6 K'
>>> bytes2human(9856, symbols="customary_ext")
'9.6 kilo'
>>> bytes2human(9856, symbols="iec")
'9.6 Ki'
>>> bytes2human(9856, symbols="iec_ext")
'9.6 kibi'
>>> bytes2human(10000, "%(value).1f %(symbol)s/sec")
'9.8 K/sec'
>>> # precision can be adjusted by playing with %f operator
>>> bytes2human(10000, format="%(value).5f %(symbol)s")
'9.76562 K'
"""
n = int(n)
if n < 0:
raise ValueError("n < 0")
symbols = SYMBOLS[symbols]
prefix = {}
for i, s in enumerate(symbols[1:]):
prefix[s] = 1 << (i+1)*10
for symbol in reversed(symbols[1:]):
if n >= prefix[symbol]:
value = float(n) / prefix[symbol]
return format % locals()
return format % dict(symbol=symbols[0], value=n)
def human2bytes(s):
"""
Attempts to guess the string format based on default symbols
set and return the corresponding bytes as an integer.
When unable to recognize the format ValueError is raised.
>>> human2bytes('0 B')
0
>>> human2bytes('1 K')
1024
>>> human2bytes('1 M')
1048576
>>> human2bytes('1 Gi')
1073741824
>>> human2bytes('1 tera')
1099511627776
>>> human2bytes('0.5kilo')
512
>>> human2bytes('0.1 byte')
0
>>> human2bytes('1 k') # k is an alias for K
1024
>>> human2bytes('12 foo')
Traceback (most recent call last):
...
ValueError: can't interpret '12 foo'
"""
init = s
num = ""
while s and s[0:1].isdigit() or s[0:1] == '.':
num += s[0]
s = s[1:]
num = float(num)
letter = s.strip()
for name, sset in SYMBOLS.items():
if letter in sset:
break
else:
if letter == 'k':
# treat 'k' as an alias for 'K' as per: http://goo.gl/kTQMs
sset = SYMBOLS['customary']
letter = letter.upper()
else:
raise ValueError("can't interpret %r" % init)
prefix = {sset[0]:1}
for i, s in enumerate(sset[1:]):
prefix[s] = 1 << (i+1)*10
return int(num * prefix[letter])
if __name__ == "__main__":
import doctest
doctest.testmod()
## end of http://code.activestate.com/recipes/578019/ }}}
Вы в значительной степени отвечаете на свой вопрос в своей последней заметке.
В human2bytes(s)
входная строка - 9.766K
например - делится на две части, номер и префикс. После утверждения (которое, как вы правильно заметили, является причиной ошибки), число умножается на соответствующее значение, которое представляет префикс, поэтому 9.766 * 1000 = 9766
, Единственный способ "избежать" потери данных - это принять в качестве входных данных достаточно точное значение с плавающей точкой.
Чтобы сделать human2bytes
принять ввод с плавающей точкой, вы можете удалить num.isdigit()
из утверждения, а затем обернуть num = float(num)
с попробовать-кроме, или проверить его другими способами.