¿Biblioteca reutilizable para obtener una versión legible para humanos del tamaño del archivo?

238

Hay varios fragmentos en la web que le darían una función para devolver el tamaño legible por humanos del tamaño de bytes:

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

Pero, ¿hay una biblioteca de Python que proporcione esto?

Sridhar Ratnakumar
fuente
2
Creo que esto cae bajo el título de "una tarea demasiado pequeña para requerir una biblioteca". Si observa la fuente de hurry.filesize, solo hay una función, con una docena de líneas de código. E incluso eso podría ser compactado.
Ben Blank
8
La ventaja de usar una biblioteca es que generalmente se prueba (contiene pruebas que se pueden ejecutar en caso de que la edición de uno introduzca un error). Si agrega las pruebas, entonces ya no son 'docenas de líneas de código' :-)
Sridhar Ratnakumar
La cantidad de reinvención de la rueda en la comunidad de Python es una locura y un ridículo. Solo ls -h /path/to/file.ext hará el trabajo. Dicho esto, la respuesta aceptada está haciendo un buen trabajo. Kudo
Edward Aung el

Respuestas:

523

Abordar el problema anterior "una tarea demasiado pequeña para requerir una biblioteca" mediante una implementación sencilla:

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)

Apoya:

  • todos los prefijos binarios conocidos actualmente
  • números negativos y positivos
  • números mayores de 1000 Yobibytes
  • unidades arbitrarias (¡tal vez te guste contar en Gibibits!)

Ejemplo:

>>> sizeof_fmt(168963795964)
'157.4GiB'

por Fred Cirera

Sridhar Ratnakumar
fuente
44
Debe haber un espacio entre el número y la unidad. Si está generando html o latex, debería ser un espacio sin interrupciones.
josch
3
solo un pensamiento, pero para cualquier sufijo (?) que no sea B(es decir, para unidades que no sean bytes), ¿desearía que el factor sea 1000.0más que 1024.0no?
Anentropic
55
Si desea aumentar la precisión del componente decimal, cambie las 1líneas 4 y 6 a la precisión que desee.
Matthew G
44
seguro que sería bueno si toda esta iteración en esta "tarea demasiado pequeña" fuera capturada y encapsulada en una biblioteca con pruebas.
Fess.
66
@ MD004 Es al revés. Los prefijos se definen de modo que 1 KB = 1000 B y 1 KiB = 1024 B.
augurar
116

Una biblioteca que tiene toda la funcionalidad que parece que estás buscando es humanize. humanize.naturalsize()parece hacer todo lo que estás buscando.

Pyrocater
fuente
99
Algunos ejemplos usando los datos de la OP: humanize.naturalsize(2048) # => '2.0 kB' ,humanize.naturalsize(2048, binary=True) # => '2.0 KiB' humanize.naturalsize(2048, gnu=True) # => '2.0K'
RubenLaguna
33

Aquí está mi versión. No usa un bucle for. Tiene una complejidad constante, O ( 1 ), y en teoría es más eficiente que las respuestas aquí que usan un bucle 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'

Para que quede más claro lo que está sucediendo, podemos omitir el código para el formato de cadena. Aquí están las líneas que realmente hacen el trabajo:

exponent = int(log(num, 1024))
quotient = num / 1024**exponent
unit_list[exponent]
joctee
fuente
2
Mientras habla de optimizar un código tan corto, ¿por qué no usar if / elif / else? La última verificación num == 1 es innecesaria a menos que espere tamaños de archivo negativos. De lo contrario: buen trabajo, me gusta esta versión.
Ted
2
Mi código seguramente podría estar más optimizado. Sin embargo, mi punto era demostrar que esta tarea podría resolverse con una complejidad constante.
Joctee
37
Las respuestas con bucles for también son O (1), porque los bucles for están acotados: su tiempo de cálculo no se escala con el tamaño de la entrada (no tenemos prefijos SI ilimitados).
Thomas Minor
1
probablemente debería agregar una coma para el formato, por 1000lo que se mostrará como 1,000 bytes.
iTayb
3
Tenga en cuenta que cuando usa Python 3, zip devuelve un iterador, por lo que debe ajustarlo con list (). unit_list = list(zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 1, 2, 2, 2]))
donarb
30

Lo siguiente funciona en Python 3.6+, es, en mi opinión, la respuesta más fácil de entender aquí, y le permite personalizar la cantidad de lugares decimales utilizados.

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}"
wp-overwatch.com
fuente
26

Si bien sé que esta pregunta es antigua, recientemente se me ocurrió una versión que evita los bucles y la uso log2para determinar el orden de tamaño que funciona como un cambio y un índice en la lista de sufijos:

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

Sin embargo, podría considerarse poco propónico por su legibilidad :)

akaIDIOT
fuente
1
Si bien me gusta lo de log2, ¡debes manejar size == 0!
Marti Nito
Debe ajustar sizeo (1 << (order * 10)en float()la última línea (para python 2).
Harvey
FYI: algunos pueden necesitar import mathallá arriba.
Monsto
@monsto true, añadido :)
akaIDIOT
Me encanta lo compacto que es esto! Gracias por compartir.
John Crawford
17

Siempre tiene que haber uno de esos tipos. Pues hoy soy yo. Aquí hay una solución de una línea, o dos líneas si cuenta la firma de la función.

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
wp-overwatch.com
fuente
1
Para su información, la salida siempre se redondeará hacia abajo.
wp-overwatch.com
1
¿No sería mejor asignar la lista predeterminada para las unidades dentro del método para evitar usar una lista como argumento predeterminado? (y usando en su units=Nonelugar)
Imanol
3
@ImanolEizaguirre Las mejores prácticas afirmarían que es una buena idea hacer lo que sugirió, por lo que no introduce errores inadvertidamente en un programa. Sin embargo, esta función tal como está escrita es segura porque la lista de unidades nunca se manipula. Si se manipulara, los cambios serían permanentes y cualquier llamada de función posterior recibiría una versión manipulada de la lista como argumento predeterminado para el argumento de las unidades.
wp-overwatch.com
Para Python 3, si desea un punto decimal, use esto en su lugar: `` `def human_size (fsize, units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']): devuelve "{: .2f} {}". Format (float (fsize), units [0]) if fsize <1024 else human_size (fsize / 1024, units [1:]) `` `
Omer
15

Si está utilizando Django instalado, también puede probar el formato de archivo :

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

=>

"1.0 GB"
Jon Tirsen
fuente
1
Una desventaja de esto para mí es que usa GB en lugar de GiB a pesar de que se divide por 1024.
Pepedou
9

Una de esas bibliotecas es date prisa .

>>> from hurry.filesize import alternative
>>> size(1, system=alternative)
'1 byte'
>>> size(10, system=alternative)
'10 bytes'
>>> size(1024, system=alternative)
'1 KB'
Sridhar Ratnakumar
fuente
3
Sin embargo, esta biblioteca no es muy personalizable. >>> de hurry.filesize tamaño de importación >>> tamaño (1031053) >>> tamaño (3033053) '2M' Espero que muestre, por ejemplo, '2.4M' o '2423K' ... en lugar del descaradamente aproximado ' 2M '.
Sridhar Ratnakumar
Tenga en cuenta también que es muy fácil simplemente tomar el código de hurry.filesize y ponerlo directamente en su propio código, si se trata de sistemas de dependencia y similares. Es casi tan corto como los fragmentos que la gente proporciona aquí.
mlissner
@SridharRatnakumar, para abordar el problema de sobre-aproximación de manera algo inteligente, vea mi truco matemático . ¿Se puede mejorar aún más el enfoque?
Acumenus
9

Usar potencias de 1000 o kibibytes sería más amigable con los estándares:

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)

PD: Nunca confíes en una biblioteca que imprima miles con el sufijo K (mayúscula) :)

Giancarlo Sportelli
fuente
P.S. Never trust a library that prints thousands with the K (uppercase) suffix :)Por qué no? El código podría ser perfectamente sólido y el autor simplemente no consideró la carcasa por kilo. Parece bastante tonto descartar automáticamente cualquier código basado en su regla ...
Douglas Gaskell
7

Esto hará lo que necesita en casi cualquier situación, es personalizable con argumentos opcionales y, como puede ver, es bastante más auto-documentado:

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)

Salida de ejemplo:

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

Personalizaciones avanzadas:

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

Este código es compatible con Python 2 y Python 3. El cumplimiento de PEP8 es un ejercicio para el lector. Recuerda, es el salida lo que es bonito.

Actualizar:

Si necesita miles de comas, simplemente aplique la extensión obvia:

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

Por ejemplo:

>>> pretty_units(987654321098765432109876543210)
'816,968.5 YiB'
gojomo
fuente
7

Deberías usar "humanizar".

>>> humanize.naturalsize(1000000)
'1.0 MB'
>>> humanize.naturalsize(1000000, binary=True)
'976.6 KiB'
>>> humanize.naturalsize(1000000, gnu=True)
'976.6K'

Referencia:

https://pypi.org/project/humanize/

Saeed Zahedian Abroodi
fuente
6

Riffing en el fragmento proporcionado como una alternativa a hurry.filesize (), aquí hay un fragmento que proporciona números de precisión variables en función del prefijo utilizado. No es tan conciso como algunos fragmentos, pero me gustan los resultados.

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)
markltbaker
fuente
6

El proyecto HumanFriendly ayuda con esto .

import humanfriendly
humanfriendly.format_size(1024)

El código anterior le dará 1 KB como respuesta.
Los ejemplos se pueden encontrar aquí .

arumuga abinesh
fuente
4

A partir de todas las respuestas anteriores, aquí está mi opinión al respecto. Es un objeto que almacenará el tamaño del archivo en bytes como un entero. Pero cuando intenta imprimir el objeto, obtiene automáticamente una versión legible para humanos.

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)
xApple
fuente
3

Me gusta la precisión fija de la versión decimal de senderle , así que aquí hay una especie de híbrido de eso con la respuesta de joctee anterior (¿sabías que podrías tomar registros con bases no enteras?):

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')
HST
fuente
2

DiveIntoPython3 también habla sobre esta función.

Sridhar Ratnakumar
fuente
2

¿Qué tal un simple 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])

Así es como funciona debajo del capó:

  1. Calcula el registro 2 (tamaño de archivo)
  2. Lo divide por 10 para obtener la unidad más cercana. (por ejemplo, si el tamaño es de 5000 bytes, la unidad más cercana es Kb, por lo que la respuesta debería ser X KiB)
  3. Devuelve file_size/value_of_closest_unitjunto con la unidad.

Sin embargo, no funciona si el tamaño del archivo es 0 o negativo (porque el registro no está definido para los números 0 y -ve). Puede agregar controles adicionales para ellos:

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

Ejemplos:

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

NOTA - Hay una diferencia entre Kb y KiB. KB significa 1000 bytes, mientras que KiB significa 1024 bytes. KB, MB, GB son múltiplos de 1000, mientras que KiB, MiB, GiB, etc. son múltiplos de 1024. Más información aquí.

jerrymouse
fuente
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
Matt Joiner
fuente
1

Lo que está a punto de encontrar a continuación no es, de ninguna manera, la solución más eficiente o más corta entre las que ya se publicaron. En cambio, se enfoca en un tema particular que muchas de las otras respuestas pierden.

A saber, el caso cuando 999_995se da una entrada como :

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

que, al truncarse al entero más cercano y aplicarse nuevamente a la entrada, da

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

Esto parece ser exactamente lo que esperaríamos hasta que se nos requiera controlar la precisión de salida . Y aquí es cuando las cosas comienzan a ponerse un poco difíciles.

Con la precisión establecida en 2 dígitos obtenemos:

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

en lugar de 1M.

¿Cómo podemos contrarrestar eso?

Por supuesto, podemos verificarlo explícitamente:

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

¿Pero podemos hacerlo mejor? ¿Podemos saber de qué maneraorder debe cortar antes de dar el paso final?

Resulta que podemos.

Suponiendo una regla de redondeo decimal de 0.5, la ifcondición anterior se traduce en:

ingrese la descripción de la imagen aquí

Resultando en

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'
ayorgo
fuente
0

Sridhar Ratnakumarrespuesta de referido , actualizada 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)

y el resultado de ejemplo es:

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
crifan
fuente
0

Esta solución también puede atraerlo, dependiendo de cómo funcione su 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'
Peter F
fuente