По-добър начин за конвертиране на размери на файлове в Python [затворено]

Използвам библиотека, която чете файл и връща размера му в байтове.

След това този размер на файла се показва на крайния потребител; за да им е по-лесно да го разберат, изрично конвертирам размера на файла в MB, като го разделя на 1024.0 * 1024.0. Разбира се, това работи, но се чудя има ли по-добър начин да направя това в Python?

Под по-добро имам предвид може би функция stdlib, която може да манипулира размери според типа, който искам. Например, ако задам MB, той автоматично го разделя на 1024.0 * 1024.0. Нещо в тези редове.


person user225312    schedule 04.03.2011    source източник
comment
Така че напиши едно. Също така имайте предвид, че много системи сега използват MB за означаване на 10^6 вместо 2^20.   -  person tc.    schedule 04.03.2011
comment
@A A, @tc: Моля, имайте предвид, че нормата SI и IEC е kB (Kilo) for 1.000 Byte и KiB (Kibi) for 1.024 Byte. Вижте en.wikipedia.org/wiki/Kibibyte .   -  person Bobby    schedule 04.03.2011
comment
@Bobby: kB всъщност означава килобел, равно на 10000 dB. Няма единица SI за байт. IIRC, IEC препоръчва KiB, но не дефинира kB или KB.   -  person tc.    schedule 12.03.2011
comment
@tc. Префиксът kilo се определя от SI, за да означава 1000. IEC дефинира kB и т.н., за да използва префикса SI вместо 2^10.   -  person ford    schedule 12.02.2013
comment
@fizzisist: цитирам? IEC е установил KiB/MiB/и т.н., но доколкото ми е известно, няма международен стандарт, определящ kB/MB/и т.н. освен SI, където kB/MB означава килобел/мегабел (точно както dB означава децибел). Във всеки случай би било неразумно, тъй като MB/GB отдавна е двусмислено.   -  person tc.    schedule 18.02.2013
comment
Имам предвид, че префиксите се дефинират като цяло от SI, но съкращенията за размера на данните не са: физика .nist.gov/cuu/Units/prefixes.html. Те са определени от IEC: physics.nist.gov/cuu/Units/binary.html   -  person ford    schedule 19.02.2013
comment
@tc. SI ясно посочва, че SI префиксите се отнасят стриктно за степени на 10 и не трябва да се използват за степени на 2. Например, 1 килобит не трябва да се използва за представяне на 1024 бита   -  person endolith    schedule 18.01.2017


Отговори (12)


Има 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'

Тъй като е написан от Страхотния Мартин Фаасен, кодът е малък, ясен и разширим. Писането на ваши собствени системи е изключително лесно.

Ето един:

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'
person Lennart Regebro    schedule 04.03.2011
comment
Перфектно! Повече от желанието да го накарам да работи за моя случай, исках да знам, че има нещо подобно. - person user225312; 04.03.2011
comment
Хм, сега ми трябва един, за да отида в другата посока. От 1 kb до 1024 (int). - person mlissner; 09.07.2018
comment
Работи само в python 2 - person e-info128; 18.02.2019
comment
Този пакет може да е страхотен, но странният лиценз и фактът, че няма онлайн достъпен изходен код, го правят нещо, което с удоволствие бих избегнал. И също така изглежда, че поддържа само python2. - person Almog Cohen; 03.04.2019
comment
@AlmogCohen източникът е онлайн, достъпен направо от PyPI (някои пакети нямат Github хранилище, а само PyPI страница) и лицензът не е толкова неясен, ZPL е Zope Public License, което е, доколкото ми е известно , подобен на BSD. Съгласен съм, че самото лицензиране е странно: няма стандартен файл „LICENSE.txt“, нито има преамбюл в горната част на всеки файл източник. - person sleblanc; 20.05.2019
comment
Възможно ли е да получите всички реализации в MB с помощта на hurry.filesize? @Ленарт Регебро - person alper; 02.03.2020
comment
@alper Да. Можете да настроите каквато искате система. Ако искате САМО MB, нямате нужда от hurry.filesize, тогава просто го разделете на мегабайт. - person Lennart Regebro; 09.03.2020
comment
За да получа мегабайт, направих следното уравнение, използвайки оператор за побитово изместване: MBFACTOR = float(1 << 20); mb= int(size_in_bytes) / MBFACTOR @LennartRegebro - person alper; 09.03.2020

Ето какво използвам:

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: размерът трябва да бъде изпратен в байтове.

person James    schedule 11.02.2013
comment
Ако изпращате размер в байтове, просто добавете B като първи елемент на size_name. - person tuxGurl; 25.03.2013
comment
Когато имате файл с размер 0 байта, той се проваля. log(0, 1024) не е дефиниран! Трябва да проверите регистъра на буквите 0 байта преди този израз i = int(math.floor(math.log(size,1024))). - person genclik27; 07.05.2014
comment
genclik - прав си. Току-що изпратих незначителна редакция, която ще поправи това и ще активира преобразуването от байтове. Благодаря, Sapam, за оригинала - person FarmerGedden; 22.08.2014
comment
ЗДРАВЕЙ @WHK, тъй като tuxGurl спомена, че това е лесно решение. - person James; 25.01.2016
comment
Всъщност имената на размерите трябва да бъдат (B, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB). Вижте en.wikipedia.org/wiki/Mebibyte за повече информация. - person Alex; 06.12.2019
comment
използвайки това решение - person Ajay Kumar; 06.01.2020

Вместо делител на размера на 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
person ccpizza    schedule 16.10.2012
comment
Не е ли >>? - person Tjorriemorrie; 08.08.2014
comment
@Tjorriemorrie: трябва да е ляво преместване, дясното преместване ще изпусне единствения бит и ще доведе до 0. - person ccpizza; 06.04.2018
comment
Брилянтен отговор. Благодаря ти. - person Borislav Aymaliev; 08.08.2018
comment
знам, че това е старо, но дали това е правилна употреба? def convert_to_mb(data_b): print(data_b/(1 ‹‹ 20)) - person roastbeeef; 27.03.2019

Ето компактната функция за изчисляване на размера

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/

person Pavan Gupta    schedule 14.08.2015
comment
Операторът while трябва да се промени на while size >= 1024 and index < len(suffixes):, в противен случай функцията ще върне 1024.0KB вместо 1.0MB например. - person AnythingIsFine; 14.05.2021

Ето някои лесни за копиране единични подложки, които да използвате, ако вече знаете какъв размер на единица искате. Ако търсите по-обща функция с няколко хубави опции, вижте моята актуализация от ФЕВРУАРИ 2021 г. по-нататък...

Байтове

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")

АКТУАЛИЗАЦИЯ ФЕВРУАРИ 2021 Г. Ето моите актуализирани и усъвършенствани функции за а) получаване на размера на файл/папка, б) конвертиране в желани единици:

from pathlib import Path

def get_path_size(path = Path('.'), recursive=False):
    """
    Gets file size, or total directory size

    Parameters
    ----------
    path: str | pathlib.Path
        File path or directory/folder path

    recursive: bool
        True -> use .rglob i.e. include nested files and directories
        False -> use .glob i.e. only process current directory/folder

    Returns
    -------
    int:
        File size or recursive directory size in bytes
        Use cleverutils.format_bytes to convert to other units e.g. MB
    """
    path = Path(path)
    if path.is_file():
        size = path.stat().st_size
    elif path.is_dir():
        path_glob = path.rglob('*.*') if recursive else path.glob('*.*')
        size = sum(file.stat().st_size for file in path_glob)
    return size


def format_bytes(bytes, unit, SI=False):
    """
    Converts bytes to common units such as kb, kib, KB, mb, mib, MB

    Parameters
    ---------
    bytes: int
        Number of bytes to be converted

    unit: str
        Desired unit of measure for output


    SI: bool
        True -> Use SI standard e.g. KB = 1000 bytes
        False -> Use JEDEC standard e.g. KB = 1024 bytes

    Returns
    -------
    str:
        E.g. "7 MiB" where MiB is the original unit abbreviation supplied
    """
    if unit.lower() in "b bit bits".split():
        return f"{bytes*8} {unit}"
    unitN = unit[0].upper()+unit[1:].replace("s","")  # Normalised
    reference = {"Kb Kib Kibibit Kilobit": (7, 1),
                 "KB KiB Kibibyte Kilobyte": (10, 1),
                 "Mb Mib Mebibit Megabit": (17, 2),
                 "MB MiB Mebibyte Megabyte": (20, 2),
                 "Gb Gib Gibibit Gigabit": (27, 3),
                 "GB GiB Gibibyte Gigabyte": (30, 3),
                 "Tb Tib Tebibit Terabit": (37, 4),
                 "TB TiB Tebibyte Terabyte": (40, 4),
                 "Pb Pib Pebibit Petabit": (47, 5),
                 "PB PiB Pebibyte Petabyte": (50, 5),
                 "Eb Eib Exbibit Exabit": (57, 6),
                 "EB EiB Exbibyte Exabyte": (60, 6),
                 "Zb Zib Zebibit Zettabit": (67, 7),
                 "ZB ZiB Zebibyte Zettabyte": (70, 7),
                 "Yb Yib Yobibit Yottabit": (77, 8),
                 "YB YiB Yobibyte Yottabyte": (80, 8),
                 }
    key_list = '\n'.join(["     b Bit"] + [x for x in reference.keys()]) +"\n"
    if unitN not in key_list:
        raise IndexError(f"\n\nConversion unit must be one of:\n\n{key_list}")
    units, divisors = [(k,v) for k,v in reference.items() if unitN in k][0]
    if SI:
        divisor = 1000**divisors[1]/8 if "bit" in units else 1000**divisors[1]
    else:
        divisor = float(1 << divisors[0])
    value = bytes / divisor
    if value != 1 and len(unitN) > 3:
            unitN += "s" # Create plural unit of measure
    return "{:,.0f}".format(value) + " " + unitN


# Tests 
>>> assert format_bytes(1,"b") == '8 b'
>>> assert format_bytes(1,"bits") == '8 bits'
>>> assert format_bytes(1024, "kilobyte") == "1 Kilobyte"
>>> assert format_bytes(1024, "kB") == "1 KB"
>>> assert format_bytes(7141000, "mb") == '54 Mb'
>>> assert format_bytes(7141000, "mib") == '54 Mib'
>>> assert format_bytes(7141000, "Mb") == '54 Mb'
>>> assert format_bytes(7141000, "MB") == '7 MB'
>>> assert format_bytes(7141000, "mebibytes") == '7 Mebibytes'
>>> assert format_bytes(7141000, "gb") == '0 Gb'
>>> assert format_bytes(1000000, "kB") == '977 KB'
>>> assert format_bytes(1000000, "kB", SI=True) == '1,000 KB'
>>> assert format_bytes(1000000, "kb") == '7,812 Kb'
>>> assert format_bytes(1000000, "kb", SI=True) == '8,000 Kb'
>>> assert format_bytes(125000, "kb") == '977 Kb'
>>> assert format_bytes(125000, "kb", SI=True) == '1,000 Kb'
>>> assert format_bytes(125*1024, "kb") == '1,000 Kb'
>>> assert format_bytes(125*1024, "kb", SI=True) == '1,024 Kb'
person Peter F    schedule 07.10.2018
comment
Това е доста умен начин да го направите. Чудя се дали бихте могли да ги поставите във функция, където предавате дали искате kb. mb и така нататък. Можете дори да имате команда за въвеждане, която пита коя искате, което би било доста удобно, ако правите това често. - person Hildy; 07.10.2018
comment
Вижте по-горе, Hildy... Можете също така да персонализирате реда на речника като @lennart-regebro, описан по-горе... което може да бъде полезно за управление на съхранението, напр. Дял, клъстер, 4TB дискове, DVD_RW, Blu-Ray диск, 1GB памет или каквото и да е друго. - person Peter F; 07.10.2018
comment
Също така току-що добавих Kb (килобит), Mb (мегабит) и Gb (гигабит) - потребителите често се объркват по отношение на скоростта на мрежата или прехвърлянето на файлове, така че реших, че може да е удобно. - person Peter F; 07.10.2018

Само в случай, че някой търси обратната страна на този проблем (както аз със сигурност направих), ето какво работи за мен:

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
person Romeo Mihalcea    schedule 31.10.2016
comment
Вие не обработвате случая на десетични числа като 1,5 GB. За да го коригирате, просто сменете << 10 на * 1024, << 20 на * 1024**2 и << 30 на * 1024**3. - person E235; 13.03.2019

Ето го:

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

Изход

>>> convert_bytes(1024)
'1.0 KB'
>>> convert_bytes(102400)
'100.0 KB'
person rhoitjadhav    schedule 04.12.2019
comment
Този MiB, не MB и така нататък ... - person Bouncner; 26.04.2021

Ето моите два цента, които позволяват замятане нагоре и надолу и добавят персонализирана прецизност:

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 и т.н., както желаете.

person WesternGun    schedule 06.11.2017
comment
Ще гласувам за това, защото може да се разработи само със стандартната библиотека на Python - person Ciasto piekarz; 07.08.2018

Ето версия, която съответства на резултата от 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
person Keith    schedule 01.12.2018

Исках двупосочно преобразуване и исках да използвам поддръжката на Python 3 format(), за да бъда най-питоничен. Може би опитайте библиотечен модул за размер на данни? https://pypi.org/project/datasize/

$ pip install -qqq datasize
$ python
...
>>> from datasize import DataSize
>>> 'My new {:GB} SSD really only stores {:.2GiB} of data.'.format(DataSize('750GB'),DataSize(DataSize('750GB') * 0.8))
'My new 750GB SSD really only stores 558.79GiB of data.'
person Jeremy    schedule 30.11.2020

Ето моята реализация:

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)
person sleblanc    schedule 20.05.2019

person    schedule
comment
това може да се използва в толкова много настройки. радвам се, че попаднах на този коментар. Благодаря много. - person Saurabh Jain; 13.05.2020
comment
да, това е доста сладко и не изисква външни библиотеки - person chowpay; 27.01.2021