Mejor forma de convertir tamaños de archivos en Python

84

Estoy usando una biblioteca que lee un archivo y devuelve su tamaño en bytes.

Este tamaño de archivo se muestra al usuario final; para que sea más fácil para ellos entenderlo, estoy convirtiendo explícitamente el tamaño del archivo MBdividiéndolo por 1024.0 * 1024.0. Por supuesto, esto funciona, pero me pregunto si hay una mejor manera de hacer esto en Python.

Por mejor, me refiero a quizás una función stdlib que puede manipular tamaños de acuerdo con el tipo que quiero. Como si lo especifique MB, automáticamente lo divide por 1024.0 * 1024.0. Algo en estas líneas.

user225312
fuente
4
Así que escribe uno. También tenga en cuenta que muchos sistemas ahora usan MB para significar 10 ^ 6 en lugar de 2 ^ 20.
tc.
5
@AA, @tc: tenga en cuenta que la norma SI e IEC es kB (Kilo) for 1.000 Bytey KiB (Kibi) for 1.024 Byte. Consulte en.wikipedia.org/wiki/Kibibyte .
Bobby
2
@Bobby: kB en realidad significa "kilobel", igual a 10000 dB. No hay una unidad SI para byte. IIRC, la IEC recomienda KiB pero no define kB o KB.
tc.
2
@tc. El prefijo kilo está definido por SI para significar 1000. El IEC definió kB, etc. para usar el prefijo SI en lugar de 2 ^ 10.
Ford
1
Quiero decir que los prefijos se definen generalmente por SI, pero las abreviaturas para el tamaño de los datos no son: physics.nist.gov/cuu/Units/prefixes.html . Estos están definidos por IEC: physics.nist.gov/cuu/Units/binary.html
ford

Respuestas:

100

Hay hurry.filesize que tomará el tamaño en bytes y hará una buena cadena si lo hace.

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

O si desea 1K == 1000 (que es lo que asumen la mayoría de los usuarios):

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

También tiene soporte IEC (pero eso no fue documentado):

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

Debido a que está escrito por Awesome Martijn Faassen, el código es pequeño, claro y extensible. Escribir sus propios sistemas es muy fácil.

Aqui hay uno:

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

Usado así:

>>> from hurry.filesize import size
>>> size(11000, system=mysystem)
'10 Bunches'
>>> size(198283722, system=mysystem)
'189 Heaps'
Lennart Regebro
fuente
1
Hm, ahora necesito que uno vaya por el otro lado. De "1 kb" a 1024(un int).
mlissner
2
Funciona solo en Python 2
e-info128
2
Este paquete puede ser genial, pero la licencia extraña y el hecho de que no hay un código fuente disponible en línea lo convierten en algo que estaría muy feliz de evitar. Y también parece ser compatible solo con python2.
Almog Cohen
1
@AlmogCohen la fuente está en línea, disponible directamente desde PyPI (algunos paquetes no tienen un repositorio de Github, solo una página de PyPI) y la licencia no es tan oscura, ZPL es la Licencia Pública de Zope, que es, a mi leal saber y entender , Tipo BSD. Estoy de acuerdo en que la licencia en sí es extraña: no hay un archivo 'LICENSE.txt' estándar, ni un preámbulo en la parte superior de cada archivo fuente.
sleblanc
1
Con el fin de conseguir megabytes Yo siguiente ecuación usando bit a bit operador de desplazamiento: MBFACTOR = float(1 << 20); mb= int(size_in_bytes) / MBFACTOR @LennartRegebro
Alper
147

Esto es lo que uso:

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: el tamaño debe enviarse en Bytes.

James
fuente
11
Si está enviando el tamaño en bytes, simplemente agregue "B" como el primer elemento de size_name.
tuxGurl
Cuando tiene 0 bytes de archivo, falla. log (0, 1024) no está definido. Debe marcar el caso de 0 bytes antes de esta declaración i = int (math.floor (math.log (tamaño, 1024))).
genclik27
genclik - tienes razón. Acabo de enviar una edición menor que solucionará esto y habilitará la conversión de bytes. Gracias, Sapam, por el original
FarmerGedden
Hola @WHK ya que tuxGurl mencionó que es una solución fácil.
James
3
En realidad, los nombres de tamaño deberían ser ("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"). Consulte en.wikipedia.org/wiki/Mebibyte para obtener más información.
Alex
36

En lugar de un divisor de tamaño 1024 * 1024, puede usar el << operador de desplazamiento bit a bit , es decir, 1<<20para obtener megabytes, 1<<30para obtener gigabytes, etc.

En el escenario más simple que puede tener por ejemplo una constante MBFACTOR = float(1<<20)que puede ser utilizado con bytes, es decir: megas = size_in_bytes/MBFACTOR.

Los megabytes son generalmente todo lo que necesita, o de lo contrario se puede usar algo como esto:

# 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
ccpizza
fuente
10
¿No es así >>?
Tjorriemorrie
2
@Tjorriemorrie: debe ser un desplazamiento a la izquierda, el desplazamiento a la derecha reducirá el único bit y dará como resultado 0.
ccpizza
Brillante respuesta. Gracias.
Borislav Aymaliev
Sé que esto es antiguo, pero ¿sería un uso correcto? def convert_to_mb (data_b): print (data_b / (1 << 20))
roastbeeef
22

Aquí está la función compacta para calcular el tamaño

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

Para obtener una salida más detallada y viceversa, consulte: http://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/

Pavan Gupta
fuente
12

Consulte a continuación una forma rápida y relativamente fácil de leer para imprimir tamaños de archivo en una sola línea de código si ya sabe lo que desea. Estas frases sencillas combinan la gran respuesta de @ccpizza anterior con algunos trucos de formato útiles que leí aquí ¿Cómo imprimir números con comas como separadores de miles? .

Bytes

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

Kilobits

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

Kilobytes

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

Megabits

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

Megabytes

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

Gigabits

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

Gigabytes

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

Terabytes

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

Obviamente, asumen que sabes aproximadamente con qué tamaño vas a tratar desde el principio, que en mi caso (editor de video en South West London TV) es MB y ocasionalmente GB para videos.


ACTUALIZAR USANDO PATHLIB En respuesta al comentario de Hildy, aquí está mi sugerencia para un par compacto de funciones (manteniendo las cosas 'atómicas' en lugar de fusionarlas) usando solo la biblioteca estándar de 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'
Peter F
fuente
1
Esa es una forma bastante inteligente de hacer eso. Me pregunto si podría ponerlos en una función en la que pase si quiere kb. mb's y así sucesivamente. Incluso podría tener un comando de entrada que le pregunte cuál desea, lo cual sería bastante conveniente si lo hace mucho.
Hildy
Vea arriba, Hildy ... También puede personalizar la línea del diccionario como @ lennart-regebro descrita anteriormente ... que podría ser útil para la administración del almacenamiento, por ejemplo, "Partición", "Clúster", "Discos de 4TB", "DVD_RW", " Disco Blu-Ray "," tarjetas de memoria de 1GB "o lo que sea.
Peter F
También acabo de agregar Kb (Kilobit), Mb (Megabit) y Gb (Gigabit); los usuarios a menudo los confunden en términos de red o velocidades de transferencia de archivos, por lo que pensé que podría ser útil.
Peter F
9

En caso de que alguien esté buscando el reverso de este problema (como yo lo hice), esto es lo que funciona para mí:

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
Romeo Mihalcea
fuente
No está manejando el caso de números decimales como 1.5GB. Para solucionarlo, simplemente cambie << 10a * 1024, << 20a * 1024**2y << 30a * 1024**3.
E235
3

Aquí está:

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
Rhoitjadhav
fuente
2

Aquí mis dos centavos, que permite lanzar hacia arriba y hacia abajo, y agrega precisión personalizable:

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)

Agregue TB, etc., como desee.

WesternGun
fuente
Votaré
1

Aquí hay una versión que coincide con la salida de 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
Keith
fuente
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)
Kamran Kausar
fuente
esto se puede utilizar en muchos entornos. Me alegro de haber encontrado este comentario. muchas gracias.
Saurabh Jain
0

Aquí está mi implementación:

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)

Imprimirá hasta tres decimales y eliminará los ceros y puntos finales. El parámetro booleano sialternará el uso de una magnitud de tamaño basada en 10 frente a 2.

Esta es su contraparte. Permite escribir archivos de configuración limpios como {'maximum_filesize': from_filesize('10M'). Devuelve un número entero que se aproxima al previsto. No estoy usando el cambio de bits porque el valor de origen es un número de punto flotante (lo aceptará from_filesize('2.15M')bien). Convertirlo en un entero / decimal funcionaría, pero hace que el código sea más complicado y ya funciona como está.

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)
sleblanc
fuente
-1

Aquí hay otra versión de la implementación inversa de @ romeo que maneja una sola cadena de entrada.

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)
Aaron Duke
fuente
-1

Similar a la respuesta de Aaron Duke pero más "pitónico";)

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)
nesh
fuente
-1

Soy nuevo en la programación. Se me ocurrió la siguiente función que convierte un tamaño de archivo determinado en un formato legible.

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]
usuario8637373
fuente
-2

Esto funciona correctamente para todos los tamaños de archivo:

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')))
Роман Арсеньев
fuente
3
¿Realmente valió la pena copiar la respuesta de "sapam" .... no .... solo comenta la próxima vez?
Angry 84
Otra respuesta debe copiarse en otro lugar y luego copiarse aquí nuevamente ... de hecho, recomendamos las originales.
WesternGun