Многоразовая библиотека, чтобы получить удобочитаемую версию размера файла?


238

В Интернете существуют различные фрагменты, которые могут дать вам функцию, которая возвращает читаемый размер человека из размера в байтах:

>>> human_readable(2048)
'2 kilobytes'
>>>

Но есть ли библиотека Python, которая обеспечивает это?


2
Я думаю, что это подпадает под заголовок «слишком маленькая задача, чтобы требовать библиотеки». Если вы посмотрите на источник для hurry.filesize, есть только одна функция с дюжиной строк кода. И даже это может быть уплотнено.
Бен Бланк

8
Преимущество использования библиотеки состоит в том, что она обычно тестируется (содержит тесты, которые можно запустить в случае, если в результате редактирования возникает ошибка). Если вы добавите тесты, то это уже не «дюжина строк кода» :-)
Шридхар Ратнакумар

Количество повторного изобретения колеса в сообществе питонов сумасшедшее и смешное. Просто ls -h /path/to/file.ext выполнит эту работу. Сказав это, принятый ответ делает хорошую работу. Кудо.
Эдвард Аунг

Ответы:


523

Решение указанной выше проблемы «слишком маленькая задача, чтобы требовать библиотеки» с помощью простой реализации:

def sizeof_fmt(num, suffix='B'):
    for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']:
        if abs(num) < 1024.0:
            return "%3.1f%s%s" % (num, unit, suffix)
        num /= 1024.0
    return "%.1f%s%s" % (num, 'Yi', suffix)

Поддержка:

  • все известные на данный момент бинарные префиксы
  • отрицательные и положительные числа
  • числа больше 1000 йобибайт
  • произвольные единицы (возможно, вам нравится считать в гибибитах!)

Пример:

>>> sizeof_fmt(168963795964)
'157.4GiB'

по Фред Cirera


4
Между номером и единицей должен быть пробел. Если вы выводите html или latex, это должен быть неразрывный пробел.
josch

3
просто мысль, но для любого (?) суффикса, отличного от B(т.е. для блоков, отличных от байтов), вы бы хотели, чтобы коэффициент был, 1000.0а не 1024.0нет?
Anentropic

5
Если вы хотите увеличить точность десятичного компонента, измените значения в 1строках 4 и 6 на ту точность, которую вы хотите.
Мэтью Дж

44
Конечно, было бы хорошо, если бы вся эта итерация для этой «слишком маленькой задачи» была захвачена и помещена в библиотеку с тестами.
Фесс.

6
@ MD004 Все наоборот. Префиксы определены так, что 1 KB = 1000 B и 1 KiB = 1024 B.
augurar

116

Библиотека, которая обладает всеми функциями, которые, как вам кажется, вы ищете humanize. humanize.naturalsize()кажется, делает все, что вы ищете.


9
Некоторые примеры использования данных из ОП: humanize.naturalsize(2048) # => '2.0 kB' ,humanize.naturalsize(2048, binary=True) # => '2.0 KiB' humanize.naturalsize(2048, gnu=True) # => '2.0K'
RubenLaguna

33

Вот моя версия. Он не использует цикл for. Он имеет постоянную сложность, O ( 1 ), и в теории более эффективен, чем ответы здесь, которые используют цикл for.

from math import log
unit_list = zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 1, 2, 2, 2])
def sizeof_fmt(num):
    """Human friendly file size"""
    if num > 1:
        exponent = min(int(log(num, 1024)), len(unit_list) - 1)
        quotient = float(num) / 1024**exponent
        unit, num_decimals = unit_list[exponent]
        format_string = '{:.%sf} {}' % (num_decimals)
        return format_string.format(quotient, unit)
    if num == 0:
        return '0 bytes'
    if num == 1:
        return '1 byte'

Чтобы было более понятно, что происходит, мы можем опустить код для форматирования строки. Вот строки, которые на самом деле делают работу:

exponent = int(log(num, 1024))
quotient = num / 1024**exponent
unit_list[exponent]

2
пока вы говорите об оптимизации такого короткого кода, почему бы не использовать if / elif / else? Последняя проверка num == 1 не требуется, если вы не ожидаете отрицательный размер файла. В остальном: хорошая работа, мне нравится эта версия.
Тед

2
Мой код наверняка может быть более оптимизирован. Однако я хотел продемонстрировать, что эту задачу можно решить с постоянной сложностью.
Joctee

37
Ответы с циклами for также O (1), потому что циклы for ограничены - их время вычислений не масштабируется с размером входных данных (у нас нет неограниченных префиксов SI).
Томас Минор

1
Вероятно, следует добавить запятую для форматирования, поэтому 1000будет отображаться как 1,000 bytes.
iTayb

3
Обратите внимание, что при использовании Python 3 zip возвращает итератор, поэтому вам нужно обернуть его списком (). unit_list = list(zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 1, 2, 2, 2]))
Донарб

30

Следующее работает в Python 3.6+, на мой взгляд, самый простой для понимания ответ здесь и позволяет вам настроить количество используемых десятичных разрядов.

def human_readable_size(size, decimal_places=3):
    for unit in ['B','KiB','MiB','GiB','TiB']:
        if size < 1024.0:
            break
        size /= 1024.0
    return f"{size:.{decimal_places}f}{unit}"

26

Хотя я знаю, что этот вопрос древний, недавно я придумал версию, которая избегает циклов, используя log2для определения порядка размера, который удваивается как сдвиг и индекс в списке суффиксов:

from math import log2

_suffixes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']

def file_size(size):
    # determine binary order in steps of size 10 
    # (coerce to int, // still returns a float)
    order = int(log2(size) / 10) if size else 0
    # format file size
    # (.4g results in rounded numbers for exact matches and max 3 decimals, 
    # should never resort to exponent values)
    return '{:.4g} {}'.format(size / (1 << (order * 10)), _suffixes[order])

Впрочем, вполне может считаться непифоническим из-за его читабельности :)


1
Хотя мне нравится вещь log2, вы должны обрабатывать размер == 0!
Марти Нито

Вам нужно завернуть либо size или (1 << (order * 10)в float()в последней строке (для питона 2).
Харви

К вашему сведению: некоторым может понадобиться import mathтам.
monsto

@monsto true, добавлено :)
akaIDIOT

Любите, как это компактно! Спасибо, что поделились.
Джон Кроуфорд

17

Всегда должен быть один из тех парней. Ну, сегодня это я. Вот однострочное решение - или две строки, если вы посчитаете сигнатуру функции.

def human_size(bytes, units=[' bytes','KB','MB','GB','TB', 'PB', 'EB']):
    """ Returns a human readable string reprentation of bytes"""
    return str(bytes) + units[0] if bytes < 1024 else human_size(bytes>>10, units[1:])

>>> human_size(123)
123 bytes
>>> human_size(123456789)
117GB

1
К вашему сведению, выход всегда будет округлен в меньшую сторону.
wp-overwatch.com

1
Не лучше ли назначить список по умолчанию для единиц внутри метода, чтобы избежать использования списка в качестве аргумента по умолчанию? (и использую units=Noneвместо этого)
Imanol

3
@ImanolEizaguirre Лучшие практики утверждают, что это хорошая идея - делать так, как вы предлагали, чтобы не допустить случайного появления ошибок в программе. Однако эта функция в том виде, в каком она написана, безопасна, поскольку списком единиц никогда не манипулируют. Если им манипулируют, изменения будут постоянными, и любые последующие вызовы функций получат манипулированную версию списка в качестве аргумента по умолчанию для аргумента единиц.
wp-overwatch.com

Для Python 3, если вы хотите использовать десятичную точку, используйте это вместо: `` def human_size (fsize, units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']): вернуть формат "{: .2f} {}". (Float (fsize), единицы [0]), если fsize <1024, иначе human_size (fsize / 1024, единицы [1:]) `` `
Омер

15

Если вы используете Django, вы также можете попробовать форматировать файлы :

from django.template.defaultfilters import filesizeformat
filesizeformat(1073741824)

=>

"1.0 GB"

1
Один недостаток этого для меня заключается в том, что он использует ГБ вместо ГиБ, хотя он делится на 1024.
Pepedou

9

Одна такая библиотека - спешите .

>>> from hurry.filesize import alternative
>>> size(1, system=alternative)
'1 byte'
>>> size(10, system=alternative)
'10 bytes'
>>> size(1024, system=alternative)
'1 KB'

3
Однако эта библиотека не очень настраиваема. >>> from hurry.filesize import size >>> size (1031053) >>> size (3033053) '2M' Я ожидаю, что он покажет, например, '2.4M' или '2423K' .. вместо явно аппроксимируемой ' 2М».
Шридхар Ратнакумар

Также обратите внимание, что очень просто просто извлечь код из файла hurry.filesize и поместить его непосредственно в свой собственный код, если вы имеете дело с системами зависимостей и тому подобным. Он примерно такой же короткий, как и фрагменты, которые люди здесь предоставляют.
млисснер

@SridharRatnakumar, чтобы разобраться с проблемой чрезмерного приближения, пожалуйста, посмотрите мой математический взлом . Можно ли еще улучшить этот подход?
Acumenus

9

Использование степеней 1000 или кибибайтов было бы более стандартным:

def sizeof_fmt(num, use_kibibyte=True):
    base, suffix = [(1000.,'B'),(1024.,'iB')][use_kibibyte]
    for x in ['B'] + map(lambda x: x+suffix, list('kMGTP')):
        if -base < num < base:
            return "%3.1f %s" % (num, x)
        num /= base
    return "%3.1f %s" % (num, x)

PS Никогда не доверяйте библиотеке, которая печатает тысячи с суффиксом K (заглавными буквами) :)


P.S. Never trust a library that prints thousands with the K (uppercase) suffix :)Почему нет? Код мог бы быть отлично звучащим, и автор просто не рассматривал корпус за килограмм. Кажется, довольно глупо автоматически отклонять любой код, основанный на вашем правиле ...
Дуглас Гаскелл

7

Это будет делать то, что вам нужно практически в любой ситуации, настраивается с помощью необязательных аргументов и, как вы можете видеть, в значительной степени самодокументируется:

from math import log
def pretty_size(n,pow=0,b=1024,u='B',pre=['']+[p+'i'for p in'KMGTPEZY']):
    pow,n=min(int(log(max(n*b**pow,1),b)),len(pre)-1),n*b**pow
    return "%%.%if %%s%%s"%abs(pow%(-pow-1))%(n/b**float(pow),pre[pow],u)

Пример вывода:

>>> pretty_size(42)
'42 B'

>>> pretty_size(2015)
'2.0 KiB'

>>> pretty_size(987654321)
'941.9 MiB'

>>> pretty_size(9876543210)
'9.2 GiB'

>>> pretty_size(0.5,pow=1)
'512 B'

>>> pretty_size(0)
'0 B'

Расширенные настройки:

>>> pretty_size(987654321,b=1000,u='bytes',pre=['','kilo','mega','giga'])
'987.7 megabytes'

>>> pretty_size(9876543210,b=1000,u='bytes',pre=['','kilo','mega','giga'])
'9.9 gigabytes'

Этот код совместим с Python 2 и Python 3. Соответствие PEP8 - упражнение для читателя. Помните, что это красивый вывод .

Обновить:

Если вам нужны тысячи запятых, просто примените очевидное расширение:

def prettier_size(n,pow=0,b=1024,u='B',pre=['']+[p+'i'for p in'KMGTPEZY']):
    r,f=min(int(log(max(n*b**pow,1),b)),len(pre)-1),'{:,.%if} %s%s'
    return (f%(abs(r%(-r-1)),pre[r],u)).format(n*b**pow/b**float(r))

Например:

>>> pretty_units(987654321098765432109876543210)
'816,968.5 YiB'


6

Перейдя к фрагменту, предоставленному в качестве альтернативы hurry.filesize (), здесь приведен фрагмент, который дает числа с различной точностью в зависимости от используемого префикса. Это не так кратко, как некоторые фрагменты, но мне нравятся результаты.

def human_size(size_bytes):
    """
    format a size in bytes into a 'human' file size, e.g. bytes, KB, MB, GB, TB, PB
    Note that bytes/KB will be reported in whole numbers but MB and above will have greater precision
    e.g. 1 byte, 43 bytes, 443 KB, 4.3 MB, 4.43 GB, etc
    """
    if size_bytes == 1:
        # because I really hate unnecessary plurals
        return "1 byte"

    suffixes_table = [('bytes',0),('KB',0),('MB',1),('GB',2),('TB',2), ('PB',2)]

    num = float(size_bytes)
    for suffix, precision in suffixes_table:
        if num < 1024.0:
            break
        num /= 1024.0

    if precision == 0:
        formatted_size = "%d" % num
    else:
        formatted_size = str(round(num, ndigits=precision))

    return "%s %s" % (formatted_size, suffix)


4

Опираясь на все предыдущие ответы, вот мой взгляд на это. Это объект, который будет хранить размер файла в байтах как целое число. Но когда вы пытаетесь напечатать объект, вы автоматически получаете читаемую человеком версию.

class Filesize(object):
    """
    Container for a size in bytes with a human readable representation
    Use it like this::

        >>> size = Filesize(123123123)
        >>> print size
        '117.4 MB'
    """

    chunk = 1024
    units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']
    precisions = [0, 0, 1, 2, 2, 2]

    def __init__(self, size):
        self.size = size

    def __int__(self):
        return self.size

    def __str__(self):
        if self.size == 0: return '0 bytes'
        from math import log
        unit = self.units[min(int(log(self.size, self.chunk)), len(self.units) - 1)]
        return self.format(unit)

    def format(self, unit):
        if unit not in self.units: raise Exception("Not a valid file size unit: %s" % unit)
        if self.size == 1 and unit == 'bytes': return '1 byte'
        exponent = self.units.index(unit)
        quotient = float(self.size) / self.chunk**exponent
        precision = self.precisions[exponent]
        format_string = '{:.%sf} {}' % (precision)
        return format_string.format(quotient, unit)

3

Мне нравится фиксированная точность десятичной версии senderle , так что это своего рода гибрид этого с ответом joctee выше (знаете ли вы, что вы можете взять журналы с нецелыми основаниями?):

from math import log
def human_readable_bytes(x):
    # hybrid of https://stackoverflow.com/a/10171475/2595465
    #      with https://stackoverflow.com/a/5414105/2595465
    if x == 0: return '0'
    magnitude = int(log(abs(x),10.24))
    if magnitude > 16:
        format_str = '%iP'
        denominator_mag = 15
    else:
        float_fmt = '%2.1f' if magnitude % 3 == 1 else '%1.2f'
        illion = (magnitude + 1) // 3
        format_str = float_fmt + ['', 'K', 'M', 'G', 'T', 'P'][illion]
    return (format_str % (x * 1.0 / (1024 ** illion))).lstrip('0')


2

У современного Django есть собственный шаблон шаблона filesizeformat:

Форматирует значение, например human-readableразмер файла (т. Е. «13 КБ», «4,1 МБ», «102 байта» и т. Д.).

Например:

{{ value|filesizeformat }}

Если значение равно 123456789, вывод будет 117,7 МБ.

Дополнительная информация: https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#filesizeformat.


2

Как насчет простого 2 лайнера:

def humanizeFileSize(filesize):
    p = int(math.floor(math.log(filesize, 2)/10))
    return "%.3f%s" % (filesize/math.pow(1024,p), ['B','KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'][p])

Вот как это работает под капотом:

  1. Вычисляет журнал 2 (размер файла)
  2. Разделите его на 10, чтобы получить ближайшую единицу. (например, если размер равен 5000 байт, ближайшая единица равна Kb, поэтому ответ должен быть X КиБ)
  3. Возвращается file_size/value_of_closest_unitвместе с юнитом.

Это, однако, не работает, если размер файла равен 0 или отрицателен (поскольку журнал не определен для 0 и -ve чисел). Вы можете добавить дополнительные проверки для них:

def humanizeFileSize(filesize):
    filesize = abs(filesize)
    if (filesize==0):
        return "0 Bytes"
    p = int(math.floor(math.log(filesize, 2)/10))
    return "%0.2f %s" % (filesize/math.pow(1024,p), ['Bytes','KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'][p])

Примеры:

>>> humanizeFileSize(538244835492574234)
'478.06 PiB'
>>> humanizeFileSize(-924372537)
'881.55 MiB'
>>> humanizeFileSize(0)
'0 Bytes'

ПРИМЕЧАНИЕ. - Существует разница между килобайтами и килобайтами. KB означает 1000 байтов, тогда как KiB означает 1024 байта. KB, MB, GB - это кратные 1000, тогда как KiB, MiB, GiB и т. Д. Кратны 1024. Подробнее об этом здесь


1
def human_readable_data_quantity(quantity, multiple=1024):
    if quantity == 0:
        quantity = +0
    SUFFIXES = ["B"] + [i + {1000: "B", 1024: "iB"}[multiple] for i in "KMGTPEZY"]
    for suffix in SUFFIXES:
        if quantity < multiple or suffix == SUFFIXES[-1]:
            if suffix == SUFFIXES[0]:
                return "%d%s" % (quantity, suffix)
            else:
                return "%.1f%s" % (quantity, suffix)
        else:
            quantity /= multiple

1

То, что вы собираетесь найти ниже, ни в коем случае не является самым эффективным или самым коротким решением среди уже опубликованных. Вместо этого он фокусируется на одной конкретной проблеме, которую пропускают многие другие ответы.

А именно случай, когда вводится как 999_995:

Python 3.6.1 ...
...
>>> value = 999_995
>>> base = 1000
>>> math.log(value, base)
1.999999276174054

который, будучи усеченным до ближайшего целого числа и примененным обратно ко входу, дает

>>> order = int(math.log(value, base))
>>> value/base**order
999.995

Похоже, это именно то, что мы ожидали, пока нам не нужно контролировать точность вывода . И это когда вещи начинают становиться немного сложнее.

С точностью до 2 цифр получаем:

>>> round(value/base**order, 2)
1000 # K

вместо 1M.

Как мы можем противостоять этому?

Конечно, мы можем проверить это явно:

if round(value/base**order, 2) == base:
    order += 1

Но можем ли мы сделать лучше? Можем ли мы узнать, каким образом orderследует сократить, прежде чем мы сделаем последний шаг?

Оказывается, мы можем.

Предполагая правило округления до 0.5 десятичного знака, вышеприведенное ifусловие переводится в:

введите описание изображения здесь

в результате чего

def abbreviate(value, base=1000, precision=2, suffixes=None):
    if suffixes is None:
        suffixes = ['', 'K', 'M', 'B', 'T']

    if value == 0:
        return f'{0}{suffixes[0]}'

    order_max = len(suffixes) - 1
    order = log(abs(value), base)
    order_corr = order - int(order) >= log(base - 0.5/10**precision, base)
    order = min(int(order) + order_corr, order_max)

    factored = round(value/base**order, precision)

    return f'{factored:,g}{suffixes[order]}'

дающий

>>> abbreviate(999_994)
'999.99K'
>>> abbreviate(999_995)
'1M'
>>> abbreviate(999_995, precision=3)
'999.995K'
>>> abbreviate(2042, base=1024)
'1.99K'
>>> abbreviate(2043, base=1024)
'2K'

0

см. Sridhar Ratnakumarответ, обновленный до:

def formatSize(sizeInBytes, decimalNum=1, isUnitWithI=False, sizeUnitSeperator=""):
  """format size to human readable string"""
  # https://en.wikipedia.org/wiki/Binary_prefix#Specific_units_of_IEC_60027-2_A.2_and_ISO.2FIEC_80000
  # K=kilo, M=mega, G=giga, T=tera, P=peta, E=exa, Z=zetta, Y=yotta
  sizeUnitList = ['','K','M','G','T','P','E','Z']
  largestUnit = 'Y'

  if isUnitWithI:
    sizeUnitListWithI = []
    for curIdx, eachUnit in enumerate(sizeUnitList):
      unitWithI = eachUnit
      if curIdx >= 1:
        unitWithI += 'i'
      sizeUnitListWithI.append(unitWithI)

    # sizeUnitListWithI = ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']
    sizeUnitList = sizeUnitListWithI

    largestUnit += 'i'

  suffix = "B"
  decimalFormat = "." + str(decimalNum) + "f" # ".1f"
  finalFormat = "%" + decimalFormat + sizeUnitSeperator + "%s%s" # "%.1f%s%s"
  sizeNum = sizeInBytes
  for sizeUnit in sizeUnitList:
      if abs(sizeNum) < 1024.0:
        return finalFormat % (sizeNum, sizeUnit, suffix)
      sizeNum /= 1024.0
  return finalFormat % (sizeNum, largestUnit, suffix)

и пример вывода:

def testKb():
  kbSize = 3746
  kbStr = formatSize(kbSize)
  print("%s -> %s" % (kbSize, kbStr))

def testI():
  iSize = 87533
  iStr = formatSize(iSize, isUnitWithI=True)
  print("%s -> %s" % (iSize, iStr))

def testSeparator():
  seperatorSize = 98654
  seperatorStr = formatSize(seperatorSize, sizeUnitSeperator=" ")
  print("%s -> %s" % (seperatorSize, seperatorStr))

def testBytes():
  bytesSize = 352
  bytesStr = formatSize(bytesSize)
  print("%s -> %s" % (bytesSize, bytesStr))

def testMb():
  mbSize = 76383285
  mbStr = formatSize(mbSize, decimalNum=2)
  print("%s -> %s" % (mbSize, mbStr))

def testTb():
  tbSize = 763832854988542
  tbStr = formatSize(tbSize, decimalNum=2)
  print("%s -> %s" % (tbSize, tbStr))

def testPb():
  pbSize = 763832854988542665
  pbStr = formatSize(pbSize, decimalNum=4)
  print("%s -> %s" % (pbSize, pbStr))


def demoFormatSize():
  testKb()
  testI()
  testSeparator()
  testBytes()
  testMb()
  testTb()
  testPb()

  # 3746 -> 3.7KB
  # 87533 -> 85.5KiB
  # 98654 -> 96.3 KB
  # 352 -> 352.0B
  # 76383285 -> 72.84MB
  # 763832854988542 -> 694.70TB
  # 763832854988542665 -> 678.4199PB

0

Это решение может также обратиться к вам, в зависимости от того, как работает ваш разум:

from pathlib import Path    

def get_size(path = Path('.')):
    """ Gets file size, or total directory size """
    if path.is_file():
        size = path.stat().st_size
    elif path.is_dir():
        size = sum(file.stat().st_size for file in path.glob('*.*'))
    return size

def format_size(path, unit="MB"):
    """ Converts integers to common size units used in computing """
    bit_shift = {"B": 0,
            "kb": 7,
            "KB": 10,
            "mb": 17,
            "MB": 20,
            "gb": 27,
            "GB": 30,
            "TB": 40,}
    return "{:,.0f}".format(get_size(path) / float(1 << bit_shift[unit])) + " " + unit

# Tests and test results
>>> get_size("d:\\media\\bags of fun.avi")
'38 MB'
>>> get_size("d:\\media\\bags of fun.avi","KB")
'38,763 KB'
>>> get_size("d:\\media\\bags of fun.avi","kb")
'310,104 kb'
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.