Libreria riutilizzabile per ottenere una versione leggibile dall'uomo delle dimensioni del file?


238

Esistono vari frammenti sul Web che ti darebbero una funzione per restituire la dimensione leggibile umana dalla dimensione dei byte:

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

Ma c'è una libreria Python che fornisce questo?


2
Penso che questo rientri nella rubrica "un compito troppo piccolo per richiedere una biblioteca". Se guardi alla fonte per hurry.filesize, c'è solo una singola funzione, con una dozzina di righe di codice. E anche quello potrebbe essere compattato.
Ben Blank,

8
Il vantaggio dell'utilizzo di una libreria è che di solito viene testato (contiene test che possono essere eseguiti nel caso in cui la modifica introduca un bug). Se aggiungi i test, allora non sono più "una dozzina di righe di codice" :-)
Sridhar Ratnakumar,

La quantità di reinventare la ruota nella comunità di Python è folle e ridicola. Solo ls -h /path/to/file.ext farà il lavoro. Detto questo, la risposta accettata sta facendo un buon lavoro. Kudo.
Edward Aung,

Risposte:


523

Affrontare il problema "un'attività troppo piccola per richiedere una libreria" sopra descritta con un'implementazione semplice:

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)

supporti:

  • tutti i prefissi binari attualmente noti
  • numeri negativi e positivi
  • numeri maggiori di 1000 Yobibyte
  • unità arbitrarie (forse ti piace contare in Gibibits!)

Esempio:

>>> sizeof_fmt(168963795964)
'157.4GiB'

di Fred Cirera


4
Dovrebbe esserci uno spazio tra il numero e l'unità. Se stai producendo html o latex dovrebbe essere uno spazio non-breaking.
Jose

3
solo un pensiero, ma per qualsiasi (?) suffisso diverso da B(cioè per unità diverse dai byte) vorresti che il fattore fosse 1000.0piuttosto che 1024.0no?
Anentropico,

5
Se si desidera aumentare la precisione del componente decimale, modificare le 1righe 4 e 6 con la precisione desiderata.
Matthew G,

44
sicuramente sarebbe bello se tutta questa iterazione su questo "compito troppo piccolo" fosse catturata e incapsulata in una libreria con test.
Fess.

6
@ MD004 È il contrario. I prefissi sono definiti in modo tale che 1 KB = 1000 B e 1 KiB = 1024 B.
Augurar

116

Una libreria che ha tutte le funzionalità che sembra stia cercando è humanize. humanize.naturalsize()sembra fare tutto quello che cerchi.


9
Alcuni esempi che utilizzano i dati dall'OP: humanize.naturalsize(2048) # => '2.0 kB' ,humanize.naturalsize(2048, binary=True) # => '2.0 KiB' humanize.naturalsize(2048, gnu=True) # => '2.0K'
RubenLaguna

33

Ecco la mia versione. Non utilizza un for-loop. Ha una complessità costante, O ( 1 ), ed è in teoria più efficiente delle risposte qui che usano un for-loop.

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'

Per chiarire cosa sta succedendo, possiamo omettere il codice per la formattazione della stringa. Ecco le linee che effettivamente fanno il lavoro:

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

2
mentre parli di come ottimizzare un codice così breve, perché non usare if / elif / else? L'ultimo controllo num == 1 non è necessario a meno che non ti aspetti file di dimensioni negative. Altrimenti: bel lavoro, mi piace questa versione.
Ted

2
Il mio codice potrebbe sicuramente essere più ottimizzato. Tuttavia, il mio punto era dimostrare che questo compito poteva essere risolto con una complessità costante.
giovedì

37
Le risposte con per i cicli sono anche O (1), perché i cicli per sono limitati - il loro tempo di calcolo non si ridimensiona con la dimensione dell'input (non abbiamo prefissi SI illimitati).
Thomas Minor,

1
probabilmente dovrebbe aggiungere una virgola per la formattazione, quindi 1000mostrerebbe come 1,000 bytes.
iTayb

3
Nota che quando usi Python 3, zip restituisce un iteratore, quindi devi racchiuderlo con list (). unit_list = list(zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 1, 2, 2, 2]))
donarb,

30

I seguenti lavori in Python 3.6+, sono, a mio avviso, la risposta più semplice da comprendere qui, e consentono di personalizzare la quantità di cifre decimali utilizzate.

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

Mentre so che questa domanda è antica, di recente ho trovato una versione che evita i loop, usando log2per determinare l'ordine delle dimensioni che funge anche da spostamento e un indice nell'elenco dei suffissi:

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

Potrebbe ben essere considerato non pitonico per la sua leggibilità, però :)


1
Mentre mi piace la cosa log2, dovresti gestire size == 0!
Marti Nito,

È necessario per avvolgere sia sizeo (1 << (order * 10)nel float()nell'ultima riga (per Python 2).
Harvey,

Cordiali saluti: alcuni potrebbero aver bisogno di import mathlassù.
Monsto

@monsto true, aggiunto :)
akaIDIOT

Adoro quanto sia compatto! Grazie per aver condiviso.
John Crawford,

17

Dev'essere sempre uno di quei ragazzi. Bene oggi sono io. Ecco una soluzione a una riga o due righe se conti la firma della funzione.

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
Cordiali saluti, l'uscita sarà sempre arrotondata per difetto.
wp-overwatch.com,

1
non sarebbe meglio assegnare l'elenco predefinito per le unità all'interno del metodo per evitare di usare un elenco come argomento predefinito? (e usando units=Noneinvece)
Imanol il

3
@ImanolEizaguirre Le migliori pratiche affermerebbero che è una buona idea fare come hai suggerito, quindi non introdurre inavvertitamente bug in un programma. Tuttavia, questa funzione così come è scritta è sicura perché l'elenco delle unità non viene mai manipolato. Se fosse manipolato, le modifiche sarebbero permanenti e qualsiasi chiamata di funzione successiva avrebbe ricevuto una versione manipolata dell'elenco come argomento predefinito per l'argomento unità.
wp-overwatch.com

Per Python 3, se si desidera un punto decimale, utilizzare invece questo: `` def human_size (fsize, units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']): restituisce "{: .2f} {}". Format (float (fsize), unità [0]) se fsize <1024 else human_size (fsize / 1024, unità [1:]) ``
Omer,

15

Se stai usando Django installato, puoi anche provare formato file :

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

=>

"1.0 GB"

1
Un aspetto negativo di questo per me è che usa GB invece di GiB anche se si sta dividendo per 1024.
Pepedou,

9

Una di queste librerie è 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
Tuttavia, questa libreria non è molto personalizzabile. >>> from hurry.filesize import size >>> size (1031053) >>> size (3033053) '2M' Mi aspetto che mostri, ad esempio, '2.4M' o '2423K' .. invece del palesemente approssimativo ' 2M'.
Sridhar Ratnakumar,

Nota anche che è molto semplice prendere il codice da hurry.filesize e inserirlo direttamente nel tuo codice, se hai a che fare con sistemi di dipendenza e simili. È breve quanto i frammenti che le persone forniscono qui.
mlissner,

@SridharRatnakumar, per affrontare il problema dell'eccessiva approssimazione in modo un po 'intelligente, vedi il mio trucco matematico . L'approccio può essere ulteriormente migliorato?
Acumenus,

9

L'uso di potenze di 1000 o kibibyte sarebbe più standard-friendly:

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 Non fidarti mai di una libreria che ne stampa migliaia con il suffisso K (maiuscolo) :)


P.S. Never trust a library that prints thousands with the K (uppercase) suffix :)Perchè no? Il codice potrebbe essere perfettamente valido e l'autore non ha preso in considerazione l'involucro del chilo. Sembra abbastanza ingannevole eliminare automaticamente qualsiasi codice in base alla tua regola ...
Douglas Gaskell,

7

Questo farà ciò di cui hai bisogno in quasi ogni situazione, è personalizzabile con argomenti opzionali e, come puoi vedere, è praticamente autocompattante:

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)

Esempio di output:

>>> 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'

Personalizzazioni avanzate:

>>> 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'

Questo codice è compatibile con Python 2 e Python 3. La conformità PEP8 è un esercizio per il lettore. Ricorda, è l' output che è carino.

Aggiornare:

Se hai bisogno di migliaia di virgole, applica l'estensione ovvia:

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

Per esempio:

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


6

Riffing sullo snippet fornito in alternativa a hurry.filesize (), ecco uno snippet che fornisce numeri di precisione variabili in base al prefisso utilizzato. Non è così conciso come alcuni frammenti, ma mi piacciono i risultati.

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

Attingendo a tutte le risposte precedenti, ecco la mia opinione su di esso. È un oggetto che memorizzerà la dimensione del file in byte come numero intero. Ma quando provi a stampare l'oggetto, ottieni automaticamente una versione leggibile dall'uomo.

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

Mi piace la precisione fissa della versione decimale di Senderle , quindi ecco una sorta di ibrido di quello con la risposta di joctee sopra (sapevi che potresti prendere i log con basi non intere?):

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

Che ne dici di un semplice 2 liner:

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

Ecco come funziona sotto il cofano:

  1. Calcola registro 2 (dimensione file)
  2. Lo divide per 10 per ottenere l'unità più vicina. (ad es. se la dimensione è di 5000 byte, l'unità più vicina è Kb, quindi la risposta dovrebbe essere X KiB)
  3. Ritorna file_size/value_of_closest_unitinsieme all'unità.

Tuttavia, non funziona se la dimensione del file è 0 o negativa (perché il registro non è definito per i numeri 0 e -ve). È possibile aggiungere ulteriori controlli per loro:

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

Esempi:

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

NOTA : esiste una differenza tra Kb e KiB. KB significa 1000 byte, mentre KiB significa 1024 byte. KB, MB, GB sono tutti multipli di 1000, mentre KiB, MiB, GiB ecc. Sono tutti multipli di 1024. Maggiori informazioni qui


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

Quello che stai per trovare di seguito non è affatto la soluzione più performante o più breve tra quelle già pubblicate. Invece, si concentra su un problema particolare che molte delle altre risposte mancano.

Vale a dire il caso in cui 999_995viene fornito un input simile :

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

che, troncato al numero intero più vicino e applicato nuovamente all'input, dà

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

Questo sembra essere esattamente ciò che ci aspetteremmo fino a quando non saremo tenuti a controllare la precisione dell'output . E questo è quando le cose iniziano a diventare un po 'difficili.

Con la precisione impostata su 2 cifre otteniamo:

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

invece di 1M.

Come possiamo contrastarlo?

Certo, possiamo verificarlo esplicitamente:

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

Ma possiamo fare di meglio? Possiamo sapere in che modo ordertagliare prima di fare il passo finale?

Si scopre che possiamo.

Supponendo una regola di arrotondamento decimale 0,5, la ifcondizione precedente si traduce in:

inserisci qui la descrizione dell'immagine

con il risultato di

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]}'

dando

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

fare riferimento Sridhar Ratnakumaralla risposta, aggiornata a:

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)

e l'output di esempio è:

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

Questa soluzione potrebbe piacere anche a te, a seconda di come funziona la tua mente:

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'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.