Lepszy sposób na konwersję rozmiarów plików w Pythonie [zamknięty]


85

Używam biblioteki, która czyta plik i zwraca jego rozmiar w bajtach.

Ten rozmiar pliku jest następnie wyświetlany użytkownikowi końcowemu; Aby ułatwić im zrozumienie tego, wyraźnie konwertuję rozmiar pliku na MB, dzieląc go przez 1024.0 * 1024.0. Oczywiście to działa, ale zastanawiam się, czy istnieje lepszy sposób na zrobienie tego w Pythonie?

Mówiąc lepiej, mam na myśli funkcję standardowej biblioteki, która może manipulować rozmiarami w zależności od typu, który chcę. Tak jak jeśli podam MB, automatycznie dzieli to przez 1024.0 * 1024.0. Coś się dzieje na tych liniach.


5
Więc napisz jeden. Należy również zauważyć, że wiele systemów używa teraz MB do oznaczenia 10 ^ 6 zamiast 2 ^ 20.
tc.

6
@AA, @tc: Należy pamiętać, że normami SI i IEC są kB (Kilo) for 1.000 Bytei KiB (Kibi) for 1.024 Byte. Zobacz en.wikipedia.org/wiki/Kibibyte .
Bobby

2
@Bobby: kB faktycznie oznacza „kilobel”, równy 10000 dB. Nie ma jednostki SI dla bajtu. IIRC, IEC zaleca KiB, ale nie definiuje kB ani KB.
tc.

2
@tc. Przedrostek kilo jest zdefiniowany przez SI w znaczeniu 1000. KB zdefiniowane przez IEC itd., Aby użyć przedrostka SI zamiast 2 ^ 10.
ford

1
Chodzi mi o to, że przedrostki są ogólnie definiowane przez SI, ale skróty określające rozmiar danych to nie: physics.nist.gov/cuu/Units/prefixes.html . Są one zdefiniowane przez IEC: physics.nist.gov/cuu/Units/binary.html
ford

Odpowiedzi:


103

Jest hurry.filesize , który przyjmie rozmiar w bajtach i utworzy ładny ciąg znaków, jeśli tak.

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

Lub jeśli chcesz 1K == 1000 (co zakłada większość użytkowników):

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

Obsługuje również IEC (ale nie zostało to udokumentowane):

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

Ponieważ został napisany przez Awesome Martijna Faassena, kod jest mały, przejrzysty i rozszerzalny. Pisanie własnych systemów jest niezwykle łatwe.

Tutaj jest jeden:

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

Używane tak:

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

1
Hm, teraz potrzebuję jednego, aby przejść w drugą stronę. Od „1 kb” do 1024(int).
mlissner

2
Działa tylko w Pythonie 2
e-info128

2
Ten pakiet może być fajny, ale dziwna licencja i fakt, że nie ma kodu źródłowego dostępnego online, sprawiają, że bardzo chętnie go uniknę. Wydaje się, że obsługuje tylko Python2.
Almog Cohen

1
@AlmogCohen źródło jest online, dostępne bezpośrednio z PyPI (niektóre pakiety nie mają repozytorium Github, tylko strona PyPI), a licencja nie jest taka niejasna, ZPL to Licencja Publiczna Zope, która według mojej najlepszej wiedzy , Podobny do BSD. Zgadzam się, że sama licencja jest dziwna: nie ma standardowego pliku „LICENSE.txt” ani nagłówka każdego pliku źródłowego.
sleblanc

1
Aby uzyskać megabajt, wykonałem następujące równanie za pomocą operatora przesuwania bitowego: MBFACTOR = float(1 << 20); mb= int(size_in_bytes) / MBFACTOR @LennartRegebro
alper

151

Oto czego używam:

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

Uwaga: rozmiar należy przesłać w bajtach.


11
Jeśli wysyłasz rozmiar w bajtach, po prostu dodaj „B” jako pierwszy element size_name.
tuxGurl

Gdy masz 0 bajtów pliku, nie powiedzie się. log (0, 1024) nie jest zdefiniowany! Powinieneś sprawdzić wielkość liter 0 bajtów przed tą instrukcją i = int (math.floor (math.log (rozmiar, 1024))).
genclik27

genclik - masz rację. Właśnie przesłałem drobną edycję, która to naprawi i umożliwi konwersję z bajtów. Dzięki, Sapam, za oryginał
FarmerGedden

HI @WHK, ponieważ tuxGurl wspomniał o jego łatwej naprawie.
James

3
Właściwie nazwy rozmiaru musiałyby być („B”, „KiB”, „MiB”, „GiB”, „TiB”, „PiB”, „EiB”, „ZiB”, „YiB”). Więcej informacji można znaleźć na stronie en.wikipedia.org/wiki/Mebibyte .
Alex

38

Zamiast dzielnika rozmiaru 1024 * 1024możesz użyć << operatora przesunięcia bitowego , tj. 1<<20Aby uzyskać megabajty, 1<<30uzyskać gigabajty itp.

W najprostszym scenariuszu można mieć np stałym MBFACTOR = float(1<<20), które mogą być następnie wykorzystane w bajtach, a mianowicie: megas = size_in_bytes/MBFACTOR.

Megabajty to zwykle wszystko, czego potrzebujesz, lub w inny sposób można użyć czegoś takiego:

# 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
Czyż nie >>?
Tjorriemorrie 8-14

2
@Tjorriemorrie: to musi być lewe przesunięcie, prawe przesunięcie spowoduje zrzucenie jedynego bitu i spowoduje 0.
ccpizza

Świetna odpowiedź. Dziękuję Ci.
Borislav Aymaliev

Wiem, że to jest stare, ale czy byłoby to prawidłowe użycie? def convert_to_mb (data_b): print (data_b / (1 << 20))
roastbeeef

24

Oto kompaktowa funkcja do obliczania rozmiaru

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

Aby uzyskać bardziej szczegółowe dane wyjściowe i odwrotnie, odwiedź: http://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/


15

Poniżej znajdziesz szybki i stosunkowo łatwy do odczytania sposób drukowania rozmiarów plików w jednej linii kodu, jeśli już wiesz, czego chcesz. Te jednowierszowe łączą świetną odpowiedź @ccpizza powyżej z przydatnymi sztuczkami formatowania, które przeczytałem tutaj. Jak wydrukować liczbę przecinkami jako separatorami tysięcy? .

Bajty

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

Kilobity

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

Kilobajty

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

Megabity

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

Megabajtów

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

Gigabity

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

Gigabajty

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

Terabajty

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

Oczywiście zakładają, że wiesz z grubsza, z jakim rozmiarem będziesz mieć do czynienia na początku, który w moim przypadku (edytor wideo w telewizji South West London) wynosi MB, a czasami GB dla klipów wideo.


AKTUALIZACJA ZA POMOCĄ PATHLIB W odpowiedzi na komentarz Hildy'ego , oto moja sugestia dotycząca zwartej pary funkcji (utrzymywanie elementów „atomowych” zamiast ich scalania) przy użyciu tylko standardowej biblioteki Pythona:

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
To całkiem sprytny sposób na zrobienie tego. Zastanawiam się, czy mógłbyś umieścić je w funkcji, w której podajesz, czy chcesz kb. mb i tak dalej. Możesz nawet mieć polecenie wejściowe, które zapyta, które z nich chcesz, co byłoby całkiem wygodne, jeśli robisz to dużo.
Hildy,

Patrz wyżej, Hildy ... Możesz także dostosować wiersz słownika, taki jak @ lennart-regebro, opisany powyżej ... co może być przydatne do zarządzania pamięcią masową, np. „Partycja”, „Klaster”, „Dyski 4TB”, „DVD_RW”, „ Dysk Blu-Ray ”,„ pendrive'y 1GB ”lub cokolwiek innego.
Peter F

Właśnie dodałem również Kb (Kilobit), Mb (Megabit) i Gb (Gigabit) - użytkownicy często są zdezorientowani pod względem szybkości przesyłania danych w sieci lub plików, więc pomyślałem, że może to być przydatne.
Peter F

9

Na wszelki wypadek, gdyby ktoś szukał odwrotności tego problemu (tak jak na pewno), oto, co działa dla mnie:

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

Nie zajmujesz się przypadkami liczb dziesiętnych, takich jak 1,5 GB. Aby to naprawić wystarczy zmienić << 10się * 1024, << 20by * 1024**2i << 30do * 1024**3.
E235

3

Oto ona:

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

Oto moje dwa centy, które pozwalają na rzucanie w górę iw dół oraz dodaje dostosowywalnej precyzji:

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)

Dodaj TBitp., Jak chcesz.


Zagłosuję na to, ponieważ można to załatwić tylko za pomocą standardowej biblioteki Pythona
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)

jest to użyteczne w wielu ustawieniach. cieszę się, że natknąłem się na ten komentarz. wielkie dzięki.
Saurabh Jain

0

Oto wersja, która pasuje do wyniku 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

0

Chciałem dwukierunkowej konwersji i chciałem, aby obsługa formatu () w Pythonie 3 była jak najbardziej Pythonowa. Może wypróbować moduł biblioteki datasize? 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.'

-1

Oto moja realizacja:

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)

Drukuje do trzech miejsc po przecinku i usuwa końcowe zera i kropki. Parametr boolowskisi będzie przełączać użycie wielkości opartej na 10 i na 2.

To jest jego odpowiednik. Pozwala na pisanie czystych plików konfiguracyjnych, takich jak {'maximum_filesize': from_filesize('10M'). Zwraca liczbę całkowitą, która przybliża zamierzony rozmiar pliku. Nie używam przesuwania bitów, ponieważ wartość źródłowa jest liczbą zmiennoprzecinkową (zaakceptuje from_filesize('2.15M')to dobrze). Konwersja na liczbę całkowitą / dziesiętną działałaby, ale czyni kod bardziej skomplikowanym i już działa tak, jak jest.

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.