Кращий спосіб конвертувати розміри файлів у Python


84

Я використовую бібліотеку, яка читає файл і повертає його розмір у байтах.

Потім цей розмір файлу відображається кінцевому користувачеві; щоб полегшити їм їх розуміння, я явно перетворюю розмір файлу MBна поділ на 1024.0 * 1024.0. Звичайно, це працює, але мені цікаво, чи є кращий спосіб зробити це в Python?

Краще, я маю на увазі, можливо, функцію stdlib, яка може маніпулювати розмірами відповідно до типу, який я хочу. Як якщо я вкажу MB, він автоматично ділить його на 1024.0 * 1024.0. Щось на цих рядках.


4
Тож напишіть одну. Також зауважте, що зараз багато систем використовують MB, щоб означати 10 ^ 6 замість 2 ^ 20.
тк.

5
@AA, @tc: Будь ласка, майте на увазі, що Нормою SI та IEC є kB (Kilo) for 1.000 Byteі KiB (Kibi) for 1.024 Byte. Див. En.wikipedia.org/wiki/Kibibyte .
Боббі

2
@Bobby: кБ насправді означає "кілобель", що дорівнює 10000 дБ. Для байта не існує одиниці SI. IIRC, IEC рекомендує KiB, але не визначає kB або KB.
тк.

2
@tc. Префікс kilo визначається SI як значення 1000. IEC визначає kB тощо, щоб використовувати префікс SI замість 2 ^ 10.
брод

1
Я маю на увазі префікси, як правило, визначаються SI, але скорочення розміру даних не такі: physics.nist.gov/cuu/Units/prefixes.html . Вони визначені IEC: physics.nist.gov/cuu/Units/binary.html
брод

Відповіді:


100

Існує hurry.filesize, який прийме розмір у байтах і створить приємний рядок, якщо він.

>>> from hurry.filesize import size
>>> size(11000)
'10K'
>>> size(198283722)
'189M'

Або якщо ви хочете 1K == 1000 (саме так припускають більшість користувачів):

>>> from hurry.filesize import size, si
>>> size(11000, system=si)
'11K'
>>> size(198283722, system=si)
'198M'

Він також має підтримку IEC (але це не було задокументовано):

>>> from hurry.filesize import size, iec
>>> size(11000, system=iec)
'10Ki'
>>> size(198283722, system=iec)
'189Mi'

Оскільки його написав Awesome Martijn Faassen, код невеликий, зрозумілий та розширюваний. Писати власні системи - просто нелегко.

Ось один:

mysystem = [
    (1024 ** 5, ' Megamanys'),
    (1024 ** 4, ' Lotses'),
    (1024 ** 3, ' Tons'), 
    (1024 ** 2, ' Heaps'), 
    (1024 ** 1, ' Bunches'),
    (1024 ** 0, ' Thingies'),
    ]

Використовується так:

>>> from hurry.filesize import size
>>> size(11000, system=mysystem)
'10 Bunches'
>>> size(198283722, system=mysystem)
'189 Heaps'

1
Хм, зараз мені потрібно одне, щоб піти іншим шляхом. Від "1 kb" до 1024(int).
mlissner

2
Працює лише в python 2
e-info128

2
Цей пакет може бути крутим, але дивна ліцензія та той факт, що в Інтернеті немає вихідного коду, роблять це щось, чого я б дуже радив уникнути. А також, схоже, підтримує лише python2.
Almog Cohen,

1
@AlmogCohen джерело в Інтернеті, доступне безпосередньо з PyPI (деякі пакети не мають сховища Github, лише сторінку PyPI), а ліцензія не така вже й неясна, ZPL - це публічна ліцензія Zope, яка, наскільки мені відомо, , BSD-подібні. Я згоден з тим, що саме ліцензування дивне: не існує стандартного файлу "LICENSE.txt", а також немає преамбули вгорі кожного вихідного файлу.
sleblanc

1
Для того, щоб отримати мегабайт, я зробив таке рівняння за допомогою побітового оператора зсуву: MBFACTOR = float(1 << 20); mb= int(size_in_bytes) / MBFACTOR @LennartRegebro
alper

147

Ось що я використовую:

import math

def convert_size(size_bytes):
   if size_bytes == 0:
       return "0B"
   size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
   i = int(math.floor(math.log(size_bytes, 1024)))
   p = math.pow(1024, i)
   s = round(size_bytes / p, 2)
   return "%s %s" % (s, size_name[i])

NB: розмір слід надсилати в байтах.


11
Якщо ви надсилаєте розмір у байтах, просто додайте "B" як перший елемент size_name.
tuxGurl

Коли у вас є 0 байт файлу, він не вдається. log (0, 1024) не визначено! Ви повинні перевірити 0 байт перед цим твердженням i = int (math.floor (math.log (розмір, 1024))).
genclik27

genclik - ти маєш рацію. Я щойно подав незначну редакцію, яка це виправить, і дозволить перетворення з байтів. Дякую, Сапаме, за оригінал
FarmerGedden

HI @WHK, коли tuxGurl згадав, що це легко виправити.
Джеймс

3
Насправді назви розмірів повинні бути ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"). Для отримання додаткової інформації див. En.wikipedia.org/wiki/Mebibyte .
Олексій

36

Замість дільника розміру 1024 * 1024ви можете використовувати << побітовий оператор зсуву , тобто 1<<20отримати мегабайти, 1<<30отримати гігабайти тощо.

У найпростішому випадку ви можете мати , наприклад , константу , MBFACTOR = float(1<<20)яка потім може бути використана з байтами, тобто: megas = size_in_bytes/MBFACTOR.

Зазвичай мегабайти - це все, що вам потрібно, інакше можна використовувати щось подібне:

# bytes pretty-printing
UNITS_MAPPING = [
    (1<<50, ' PB'),
    (1<<40, ' TB'),
    (1<<30, ' GB'),
    (1<<20, ' MB'),
    (1<<10, ' KB'),
    (1, (' byte', ' bytes')),
]


def pretty_size(bytes, units=UNITS_MAPPING):
    """Get human-readable file sizes.
    simplified version of https://pypi.python.org/pypi/hurry.filesize/
    """
    for factor, suffix in units:
        if bytes >= factor:
            break
    amount = int(bytes / factor)

    if isinstance(suffix, tuple):
        singular, multiple = suffix
        if amount == 1:
            suffix = singular
        else:
            suffix = multiple
    return str(amount) + suffix

print(pretty_size(1))
print(pretty_size(42))
print(pretty_size(4096))
print(pretty_size(238048577))
print(pretty_size(334073741824))
print(pretty_size(96995116277763))
print(pretty_size(3125899904842624))

## [Out] ###########################
1 byte
42 bytes
4 KB
227 MB
311 GB
88 TB
2 PB

10
Чи не так >>?
Tjorriemorrie

2
@Tjorriemorrie: це має бути лівий зсув, правий зсув скине єдиний біт і призведе до 0.
ccpizza

Блискуча відповідь. Дякую.
Борислав Аймалієв

я знаю, що це старе, але чи буде це правильним використанням? def convert_to_mb (data_b): print (data_b / (1 << 20))
roastbeeef

22

Ось компактна функція для обчислення розміру

def GetHumanReadable(size,precision=2):
    suffixes=['B','KB','MB','GB','TB']
    suffixIndex = 0
    while size > 1024 and suffixIndex < 4:
        suffixIndex += 1 #increment the index of the suffix
        size = size/1024.0 #apply the division
    return "%.*f%s"%(precision,size,suffixes[suffixIndex])

Для отримання більш детального виводу та роботи навпаки, будь ласка, зверніться до: http://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/


12

Нижче наведено швидкий і відносно легкий для читання спосіб друку розмірів файлів в одному рядку коду, якщо ви вже знаєте, чого хочете. Ці однокласні вкладиші поєднують чудову відповідь @ccpizza вище з деякими зручними прийомами форматування, які я прочитав тут. Як надрукувати число з комами як роздільники тисяч? .

Байти

print ('{:,.0f}'.format(os.path.getsize(filepath))+" B")

Кілобіт

print ('{:,.0f}'.format(os.path.getsize(filepath)/float(1<<7))+" kb")

Кілобайт

print ('{:,.0f}'.format(os.path.getsize(filepath)/float(1<<10))+" KB")

Мегабіти

print ('{:,.0f}'.format(os.path.getsize(filepath)/float(1<<17))+" mb")

Мегабайти

print ('{:,.0f}'.format(os.path.getsize(filepath)/float(1<<20))+" MB")

Гігабіти

print ('{:,.0f}'.format(os.path.getsize(filepath)/float(1<<27))+" gb")

Гігабайти

print ('{:,.0f}'.format(os.path.getsize(filepath)/float(1<<30))+" GB")

Терабайти

print ('{:,.0f}'.format(os.path.getsize(filepath)/float(1<<40))+" TB")

Очевидно, вони припускають, що ви приблизно знаєте, з яким розміром ви будете мати справу з самого початку, а в моєму випадку (редактор відео на телеканалі South West London) - МБ, а іноді і ГБ для відеокліпів.


ОНОВЛЕННЯ ЗА ВИКОРИСТАННЯМ PATHLIB У відповідь на коментар Хілді , ось моя пропозиція щодо компактної пари функцій (зберігати речі "атомними", а не об'єднувати їх), використовуючи лише стандартну бібліотеку Python:

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
>>> format_size("d:\\media\\bags of fun.avi")
'38 MB'
>>> format_size("d:\\media\\bags of fun.avi","KB")
'38,763 KB'
>>> format_size("d:\\media\\bags of fun.avi","kb")
'310,104 kb'

1
Це досить розумний спосіб це зробити. Цікаво, чи не могли б ви перевести їх у функцію, в якій ви передаєте, чи хочете ви kb. мб та ін. Ви навіть можете мати команду введення, яка запитує, яку ви хочете, що було б дуже зручно, якщо ви багато робите це.
Hildy

Дивіться вище, Хільді ... Ви також можете налаштувати рядок словника, як @ lennart-regebro, описаний вище ..., що може бути корисним для управління сховищем, наприклад, "Розділ", "Кластер", "Диски 4 ТБ", "DVD_RW", " Blu-Ray Disk "," 1 Гб пам’яті “або що завгодно.
Peter F,

Я також щойно додав Kb (кілобіт), Mb (мегабіт) та Gb (гігабіт) - користувачі часто заплутуються в плані швидкості передачі даних у мережі або мережі, тому вважають, що це може бути зручно.
Peter F

9

На всякий випадок, якщо хтось шукає зворотний бік цієї проблеми (як я впевнений), ось що мені підходить:

def get_bytes(size, suffix):
    size = int(float(size))
    suffix = suffix.lower()

    if suffix == 'kb' or suffix == 'kib':
        return size << 10
    elif suffix == 'mb' or suffix == 'mib':
        return size << 20
    elif suffix == 'gb' or suffix == 'gib':
        return size << 30

    return False

Ви не обробляєте випадок десяткових чисел, таких як 1,5 ГБ. Щоб виправити це, просто змініть << 10на * 1024, << 20на * 1024**2і << 30на * 1024**3.
E235,

3

Ось:

def convert_bytes(size):
   for x in ['bytes', 'KB', 'MB', 'GB', 'TB']:
       if size < 1024.0:
           return "%3.1f %s" % (size, x)
       size /= 1024.0

   return size

2

Ось мої два центи, що дозволяє лиття вгору і вниз, і додає настроюється точність:

def convertFloatToDecimal(f=0.0, precision=2):
    '''
    Convert a float to string of decimal.
    precision: by default 2.
    If no arg provided, return "0.00".
    '''
    return ("%." + str(precision) + "f") % f

def formatFileSize(size, sizeIn, sizeOut, precision=0):
    '''
    Convert file size to a string representing its value in B, KB, MB and GB.
    The convention is based on sizeIn as original unit and sizeOut
    as final unit. 
    '''
    assert sizeIn.upper() in {"B", "KB", "MB", "GB"}, "sizeIn type error"
    assert sizeOut.upper() in {"B", "KB", "MB", "GB"}, "sizeOut type error"
    if sizeIn == "B":
        if sizeOut == "KB":
            return convertFloatToDecimal((size/1024.0), precision)
        elif sizeOut == "MB":
            return convertFloatToDecimal((size/1024.0**2), precision)
        elif sizeOut == "GB":
            return convertFloatToDecimal((size/1024.0**3), precision)
    elif sizeIn == "KB":
        if sizeOut == "B":
            return convertFloatToDecimal((size*1024.0), precision)
        elif sizeOut == "MB":
            return convertFloatToDecimal((size/1024.0), precision)
        elif sizeOut == "GB":
            return convertFloatToDecimal((size/1024.0**2), precision)
    elif sizeIn == "MB":
        if sizeOut == "B":
            return convertFloatToDecimal((size*1024.0**2), precision)
        elif sizeOut == "KB":
            return convertFloatToDecimal((size*1024.0), precision)
        elif sizeOut == "GB":
            return convertFloatToDecimal((size/1024.0), precision)
    elif sizeIn == "GB":
        if sizeOut == "B":
            return convertFloatToDecimal((size*1024.0**3), precision)
        elif sizeOut == "KB":
            return convertFloatToDecimal((size*1024.0**2), precision)
        elif sizeOut == "MB":
            return convertFloatToDecimal((size*1024.0), precision)

Додавайте TBтощо за бажанням.


Я проголосую за це, бо це можна розробити лише за допомогою стандартної бібліотеки python
Ciasto piekarz,

1
UNITS = {1000: ['KB', 'MB', 'GB'],
            1024: ['KiB', 'MiB', 'GiB']}

def approximate_size(size, flag_1024_or_1000=True):
    mult = 1024 if flag_1024_or_1000 else 1000
    for unit in UNITS[mult]:
        size = size / mult
        if size < mult:
            return '{0:.3f} {1}'.format(size, unit)

approximate_size(2123, False)

це можна використовувати в багатьох параметрах. рада, що натрапила на цей коментар. дуже дякую.
Саураб Джейн

0

Ось версія, яка відповідає виводу ls -lh .

def human_size(num: int) -> str:
    base = 1
    for unit in ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']:
        n = num / base
        if n < 9.95 and unit != 'B':
            # Less than 10 then keep 1 decimal place
            value = "{:.1f}{}".format(n, unit)
            return value
        if round(n) < 1000:
            # Less than 4 digits so use this
            value = "{}{}".format(round(n), unit)
            return value
        base *= 1024
    value = "{}{}".format(round(n), unit)
    return value

-1

Ось моя реалізація:

from bisect import bisect

def to_filesize(bytes_num, si=True):
    decade = 1000 if si else 1024
    partitions = tuple(decade ** n for n in range(1, 6))
    suffixes = tuple('BKMGTP')

    i = bisect(partitions, bytes_num)
    s = suffixes[i]

    for n in range(i):
        bytes_num /= decade

    f = '{:.3f}'.format(bytes_num)

    return '{}{}'.format(f.rstrip('0').rstrip('.'), s)

На ньому буде надруковано до трьох знаків після коми, а також викреслені нулі та крапки. Булевий параметр siбуде перемикати використання величини розміру на основі 10 та 2.

Це його аналог. Це дозволяє писати чисті конфігураційні файли, такі як {'maximum_filesize': from_filesize('10M'). Він повертає ціле число, яке апроксимує передбачуваний розмір файлу. Я не використовую зсув бітів, оскільки вихідне значення - це число з плаваючою комою (воно сприймається from_filesize('2.15M')чудово). Перетворення його в ціле число / десяткове число буде працювати, але робить код більш складним, і він вже працює як є.

def from_filesize(spec, si=True):
    decade = 1000 if si else 1024
    suffixes = tuple('BKMGTP')

    num = float(spec[:-1])
    s = spec[-1]
    i = suffixes.index(s)

    for n in range(i):
        num *= decade

    return int(num)

-2

Ось ще одна версія зворотної реалізації @ romeo, яка обробляє один вхідний рядок.

import re

def get_bytes(size_string):
    try:
        size_string = size_string.lower().replace(',', '')
        size = re.search('^(\d+)[a-z]i?b$', size_string).groups()[0]
        suffix = re.search('^\d+([kmgtp])i?b$', size_string).groups()[0]
    except AttributeError:
        raise ValueError("Invalid Input")
    shft = suffix.translate(str.maketrans('kmgtp', '12345')) + '0'
    return int(size) << int(shft)

-2

Подібно до відповіді Аарона Дюка, але більш "пітонічного";)

import re


RE_SIZE = re.compile(r'^(\d+)([a-z])i?b?$')

def to_bytes(s):
    parts = RE_SIZE.search(s.lower().replace(',', ''))
    if not parts:
        raise ValueError("Invalid Input")
    size = parts.group(1)
    suffix = parts.group(2)
    shift = suffix.translate(str.maketrans('kmgtp', '12345')) + '0'
    return int(size) << int(shift)

-2

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

def file_size_converter(size):
    magic = lambda x: str(round(size/round(x/1024), 2))
    size_in_int = [int(1 << 10), int(1 << 20), int(1 << 30), int(1 << 40), int(1 << 50)]
    size_in_text = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
    for i in size_in_int:
        if size < i:
            g = size_in_int.index(i)
            position = int((1024 % i) / 1024 * g)
            ss = magic(i)
            return ss + ' ' + size_in_text[position]

-3

Це працює правильно для всіх розмірів файлів:

import math
from os.path import getsize

def convert_size(size):
   if (size == 0):
       return '0B'
   size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
   i = int(math.floor(math.log(size,1024)))
   p = math.pow(1024,i)
   s = round(size/p,2)
   return '%s %s' % (s,size_name[i])

print(convert_size(getsize('file_name.zip')))

3
чи справді варто було скопіювати відповідь із "sapam" .... ні .... просто коментуйте наступного разу.
Angry 84,

Іншу відповідь потрібно скопіювати десь ще, а потім знову скопіювати тут ... насправді ми заохочуємо оригінальні.
WesternGun
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.