web-dev-qa-db-fra.com

Meilleure façon de convertir les tailles de fichiers en Python

J'utilise une bibliothèque qui lit un fichier et renvoie sa taille en octets.

Cette taille de fichier est ensuite affichée à l'utilisateur final; pour leur faciliter la compréhension, je convertis explicitement la taille du fichier en MB en le divisant par 1024.0 * 1024.0. Bien sûr, cela fonctionne, mais je me demande s’il existe un meilleur moyen de faire cela en Python.

Par meilleur, j'entends peut-être une fonction stdlib qui peut manipuler des tailles en fonction du type que je veux. Comme si je spécifiais MB, il le divise automatiquement par 1024.0 * 1024.0. Quelque chose sur ces lignes.

39
user225312

Il y a hurry.filesize qui prendra la taille en octets et fera une chaîne Nice si elle est.

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

Ou si vous voulez 1K == 1000 (ce que supposent la plupart des utilisateurs):

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

Il prend également en charge IEC (mais cela n’a pas été documenté):

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

Parce qu'il est écrit par le génial Martijn Faassen, le code est petit, clair et extensible. Écrire vos propres systèmes est extrêmement simple.

En voici un:

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

Utilisé comme tel:

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

Voici ce que j'utilise: 

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: la taille doit être envoyée en octets.

83
James Sapam

Au lieu d’un diviseur de taille de 1024 * 1024, vous pouvez utiliser l’opérateur <<de décalage binaire , c’est-à-dire 1<<20 pour obtenir des mégaoctets, 1<<30 pour obtenir des gigaoctets, etc.

J'ai défini une constante MBFACTOR = float(1<<20) qui peut ensuite être utilisée avec des octets, c'est-à-dire: megas = size_in_bytes/MBFACTOR

18
ccpizza

Voici la fonction compacte pour calculer la taille

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

Pour une sortie plus détaillée et un fonctionnement inversé, veuillez consulter: http://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/

14
Pavan Gupta

Juste au cas où quelqu'un chercherait l'inverse de ce problème (comme je l'avais déjà fait), voici ce qui fonctionne pour moi:

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
6
Romeo Mihalcea

Voir ci-dessous pour un moyen rapide et relativement facile à lire d’imprimer les tailles de fichier sur une seule ligne de code si vous savez déjà ce que vous voulez. Ces one-liners combinent l'excellente réponse de @ccpizza ci-dessus avec quelques astuces de formatage pratiques que j'ai lues ici Comment imprimer des nombres avec des virgules sous forme de milliers de séparateurs? .

Octets

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

Mégabits

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

Mégaoctets

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

Gigabits

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

Gigaoctets

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

Téraoctets

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

Évidemment, ils supposent que vous savez à peu près à quelle taille vous allez avoir affaire au début, ce qui dans mon cas (monteur vidéo chez South West London TV) est MB et parfois GB pour les clips vidéo.


En réponse au commentaire de Hildy, voici ma suggestion pour une fonction compacte (3 lignes) utilisant uniquement la bibliothèque standard Python:

from os.path import getsize

def file_size(filepath, unit = "MB"):
    bit_shift = {"B":0, "kb":7, "KB":10, "mb":17, "MB":20, "gb":27, "GB":30, "TB":40}
    return '{:,.0f}'.format(getsize(filepath)/float(1<<bit_shift[unit]))+" "+unit

# Tests and test results
>>> file_size("d:\\media\\bags of fun.avi")
'38 MB'
>>> file_size("d:\\media\\bags of fun.avi","KB")
'38,763 KB'
>>> file_size("d:\\media\\bags of fun.avi","kb")
'310,104 kb'
4
Peter F

Voici mes deux sous, ce qui permet d'effectuer des castings de haut en bas et ajoute une précision personnalisable:

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)

Ajoutez TB, etc., comme vous le souhaitez.

2
WesternGun

Voici une version qui correspond à la sortie 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
0
Keith

Voici ma mise en œuvre:

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)

Il imprimera jusqu'à trois décimales et supprimera les zéros et les points. Le paramètre booléen si permet de basculer l'utilisation de la taille 10 ou de la taille 2.

C'est sa contrepartie. Cela permet d'écrire des fichiers de configuration propres comme {'maximum_filesize': from_filesize('10M'). Il retourne un entier qui se rapproche de la taille de fichier prévue. Je n'utilise pas de décalage de bits car la valeur source est un nombre à virgule flottante (elle acceptera bien from_filesize('2.15M') bien). La convertir en un nombre entier/décimal fonctionnerait, mais compliquerait le code et il fonctionne déjà tel quel.

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