Бібліотека для багаторазового використання, щоб отримати читану людиною версію розміру файлу?


238

В Інтернеті є різні фрагменти, які дають вам функцію повертати читаний людиною розмір з розміру байтів:

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

Але чи існує бібліотека Python, яка забезпечує це?


2
Я думаю, що це підпадає під заголовок "надто мале завдання, щоб вимагати бібліотеки". Якщо ви подивитесь на джерело для hurry.filesize, є лише одна функція, з десяток рядків коду. І навіть це можна було б ущільнити.
Бен Бланк

8
Перевагою використання бібліотеки є те, що вона зазвичай тестується (містить тести, які можна запустити у випадку, якщо в редагуванні введено помилку). Якщо додати тести, то це вже не «десяток рядків коду» :-)
Шрідхар Ратнакумар

Кількість переосмислення колеса в спільноті python є шаленою і смішною. Просто ls -h /path/to/file.ext зробить роботу. Сказавши це, прийнята відповідь робить добру справу. Кудо.
Edward Aung

Відповіді:


522

Просте вирішення вищезазначеного питання "надто мале завдання, щоб вимагати бібліотеку":

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 або латекс, це повинен бути пробіл, який не працює.
Джош

3
просто думка, але для будь-якого (?) суфікса, окрім B(тобто для одиниць, окрім байтів), ви хочете, щоб цей фактор був, 1000.0а не 1024.0ні?
Анентропний

5
Якщо ви хочете збільшити точність десяткової складової, змініть 1на рядках 4 і 6 на будь-яку точність, яку ви хочете.
Матвій Г

44
Звичайно, було б добре, якби вся ця ітерація цього "занадто малого завдання" була захоплена і складена в бібліотеку з тестами.
фіс.

6
@ MD004 Це навпаки. Префікси визначені таким чином, що 1 Кб = 1000 В і 1 КіБ = 1024 Б.
серпня

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

32

Ось моя версія. Він не використовує цикл 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
Відповіді для циклів також є O (1), оскільки для циклів обмежені - їх час обчислення не масштабується з розміром вхідних даних (у нас немає необмежених префіксів 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!
Marti Nito

Потрібно загортати sizeабо (1 << (order * 10)в float()останній рядок (або для python 2).
Харві

FYI: дехто може знадобитися import mathтам.
Монсто

@monsto правда, додано :)
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
FYI, вихід завжди буде округлятися вниз.
wp-overwatch.com

1
чи не було б краще призначити список за замовчуванням для одиниць усередині методу, щоб уникнути використання списку як аргументу за замовчуванням? (та використовуючи units=Noneзамість цього)
Іманол

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 else human_size (fsize / 1024, одиниці [1:]) `` `
Омер

15

Якщо ви використовуєте встановлений Django, ви також можете спробувати fileizeformat :

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

=>

"1.0 GB"

1
Недоліком цього для мене є те, що він використовує GB замість GiB, хоча він ділиться на 1024.
Pepedou

9

Одна з таких бібліотек є hurry.filesize .

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

3
Однак ця бібліотека не дуже настроюється. >>> від hurry.filesize розмір імпорту >>> розмір (1031053) >>> розмір (3033053) '2M' Я очікую, що він покаже, наприклад, '2.4M' або '2423K' .. замість відверто приблизного ' 2М '.
Шрідхар Ратнакумар

Зауважте також, що дуже просто просто схопити код з hurry.filesize і ввести його безпосередньо у свій власний код, якщо ви маєте справу з системами залежності тощо. Це приблизно так коротко, як і тут представлені фрагменти людей.
mlissner

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

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

Мені подобається фіксована точність десяткової версії відправника , тож ось такий собі гібрид цього відповіді з відповіддю 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

Як щодо простого 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 KiB)
  3. Повертається file_size/value_of_closest_unitразом із одиницею.

Однак він не працює, якщо розмір файлів дорівнює 0 або негативний (оскільки журнал не визначений для 0 і -ве числа). Ви можете додати додаткові чеки для них:

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 та KiB. 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відповіді «s, додано:

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.