Bibliothèque réutilisable pour obtenir une version lisible par l'homme de la taille du fichier?


238

Il existe différents extraits sur le Web qui vous donneraient une fonction pour renvoyer la taille lisible par l'homme à partir de la taille des octets:

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

Mais existe-t-il une bibliothèque Python qui fournit cela?


2
Je pense que cela relève de la "tâche trop petite pour exiger une bibliothèque". Si vous regardez la source de hurry.filesize, il n'y a qu'une seule fonction, avec une douzaine de lignes de code. Et même cela pourrait être compacté.
Ben Blank

8
L'avantage d'utiliser une bibliothèque est qu'elle est généralement testée (contient des tests qui peuvent être exécutés au cas où une modification introduirait un bogue). Si vous ajoutez les tests, ce ne sont plus des «douzaines de lignes de code» :-)
Sridhar Ratnakumar

La quantité de réinventer la roue dans la communauté python est folle et ridicule. Juste ls -h /path/to/file.ext fera le travail. Cela dit, la réponse acceptée fait du bon travail. Kudo.
Edward Aung

Réponses:


523

Résoudre le problème ci-dessus "d'une tâche trop petite pour nécessiter une bibliothèque" par une implémentation simple:

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)

Les soutiens:

  • tous les préfixes binaires actuellement connus
  • nombres négatifs et positifs
  • nombres supérieurs à 1000 Yobibytes
  • unités arbitraires (peut-être que vous aimez compter en Gibibits!)

Exemple:

>>> sizeof_fmt(168963795964)
'157.4GiB'

par Fred Cirera


4
Il devrait y avoir un espace entre le numéro et l'unité. Si vous générez du HTML ou du latex, ce devrait être un espace insécable.
josch

3
juste une pensée, mais pour tout suffixe (?) autre que B(c'est-à-dire pour les unités autres que les octets), vous voudriez que le facteur soit 1000.0plutôt que 1024.0non?
Anentropic

5
Si vous souhaitez augmenter la précision de la composante décimale, remplacez les 1lignes 4 et 6 par la précision souhaitée.
Matthew G

44
ce serait bien si toute cette itération sur cette «tâche trop petite» était capturée et encapsulée dans une bibliothèque avec des tests.
fasce.

6
@ MD004 C'est l'inverse. Les préfixes sont définis de telle sorte que 1 KB = 1000 B et 1 KiB = 1024 B.
augurar

116

Une bibliothèque qui a toutes les fonctionnalités que vous cherchez semble être humanize. humanize.naturalsize()semble faire tout ce que vous cherchez.


9
Quelques exemples en utilisant les données de l'OP: humanize.naturalsize(2048) # => '2.0 kB' ,humanize.naturalsize(2048, binary=True) # => '2.0 KiB' humanize.naturalsize(2048, gnu=True) # => '2.0K'
RubenLaguna

33

Voici ma version. Il n'utilise pas de boucle for. Il a une complexité constante, O ( 1 ), et est en théorie plus efficace que les réponses ici qui utilisent une boucle 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'

Pour rendre plus clair ce qui se passe, nous pouvons omettre le code pour le formatage des chaînes. Voici les lignes qui font réellement le travail:

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

2
pendant que vous parlez d'optimiser un code aussi court, pourquoi ne pas utiliser if / elif / else? La dernière vérification num == 1 est inutile, sauf si vous vous attendez à des tailles de fichier négatives. Sinon: beau travail, j'aime cette version.
Ted

2
Mon code pourrait sûrement être plus optimisé. Cependant, mon but était de démontrer que cette tâche pouvait être résolue avec une complexité constante.
joctee

37
Les réponses avec des boucles for sont également O (1), car les boucles for sont bornées - leur temps de calcul ne correspond pas à la taille de l'entrée (nous n'avons pas de préfixes SI illimités).
Thomas Minor

1
devrait probablement ajouter une virgule pour la mise en forme, 1000serait donc afficher comme 1,000 bytes.
iTayb

3
Notez que lorsque vous utilisez Python 3, zip retourne un itérateur, vous devez donc l'envelopper avec list (). unit_list = list(zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 1, 2, 2, 2]))
donarb

30

Les travaux suivants dans Python 3.6+, sont, à mon avis, la réponse la plus facile à comprendre ici, et vous permettent de personnaliser la quantité de décimales utilisée.

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

Bien que je sache que cette question est ancienne, j'ai récemment proposé une version qui évite les boucles, en utilisant log2pour déterminer l'ordre de taille qui se double d'un décalage et d'un index dans la liste des suffixes:

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

Pourrait bien être considéré comme non pythonique pour sa lisibilité, cependant :)


1
Bien que j'aime la chose log2, vous devez gérer la taille == 0!
Marti Nito du

Vous devez envelopper soit sizeou (1 << (order * 10)dans float()la dernière ligne (pour Python 2).
Harvey

FYI: certains peuvent avoir besoin import mathlà-haut.
monsto

@monsto true, ajouté :)
akaIDIOT

J'adore à quel point c'est compact! Merci pour le partage.
John Crawford

17

Il doit toujours y avoir un de ces gars. Aujourd'hui, c'est moi. Voici une solution à une ligne - ou deux lignes si vous comptez la signature de la fonction.

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
Pour info, la sortie sera toujours arrondie vers le bas.
wp-overwatch.com

1
ne serait-il pas préférable d'attribuer la liste par défaut pour les unités à l'intérieur de la méthode pour éviter d'utiliser une liste comme argument par défaut? (et en utilisant à la units=Noneplace)
Imanol

3
@ImanolEizaguirre Les meilleures pratiques indiquent que c'est une bonne idée de faire comme vous l'avez suggéré, donc vous n'introduisez pas par inadvertance de bogues dans un programme. Cependant, cette fonction telle qu'elle est écrite est sûre car la liste des unités n'est jamais manipulée. S'il était manipulé, les modifications seraient permanentes et tous les appels de fonction ultérieurs recevraient une version manipulée de la liste comme argument par défaut pour l'argument unités.
wp-overwatch.com

Pour Python 3, si vous voulez un point décimal, utilisez-le à la place: `` `def human_size (fsize, units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']): retourne "{: .2f} {}". Format (float (fsize), units [0]) si fsize <1024 else human_size (fsize / 1024, units [1:]) `` `
Omer

15

Si vous utilisez Django installé, vous pouvez également essayer le format de fichier :

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

=>

"1.0 GB"

1
Un inconvénient pour moi est qu'il utilise GB au lieu de GiB même s'il divise par 1024.
Pepedou

9

Une de ces bibliothèques est 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
Cependant, cette bibliothèque n'est pas très personnalisable. >>> from hurry.filesize import size >>> size (1031053) >>> size (3033053) '2M' Je m'attends à ce qu'il montre, par exemple, '2.4M' ou '2423K' .. au lieu du flagrantement approximé ' 2M '.
Sridhar Ratnakumar

Notez également qu'il est très facile de simplement extraire le code de hurry.filesize et de le placer directement dans votre propre code, si vous avez affaire à des systèmes de dépendance et autres. C'est à peu près aussi court que les extraits que les gens fournissent ici.
mlissner

@SridharRatnakumar, pour aborder le problème de sur-approximation de façon quelque peu intelligente, veuillez consulter mon hack mathématique . L'approche peut-elle être encore améliorée?
Acumenus

9

L'utilisation de puissances de 1000 ou de kibio serait plus conforme aux normes:

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 Ne faites jamais confiance à une bibliothèque qui imprime des milliers avec le suffixe K (majuscule) :)


P.S. Never trust a library that prints thousands with the K (uppercase) suffix :)Pourquoi pas? Le code pourrait être parfaitement sain et l'auteur n'a tout simplement pas considéré le boîtier pour kilo. Il semble assez idiot de rejeter automatiquement tout code basé sur votre règle ...
Douglas Gaskell

7

Cela fera ce dont vous avez besoin dans presque toutes les situations, est personnalisable avec des arguments facultatifs et, comme vous pouvez le voir, est à peu près auto-documenté:

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)

Exemple de sortie:

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

Personnalisations avancées:

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

Ce code est compatible avec Python 2 et Python 3. La conformité PEP8 est un exercice pour le lecteur. Rappelez-vous, c'est la sortie qui est jolie.

Mettre à jour:

Si vous avez besoin de milliers de virgules, appliquez simplement l'extension évidente:

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

Par exemple:

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


6

Sur la base de l'extrait fourni comme alternative à hurry.filesize (), voici un extrait qui donne des nombres de précision variables en fonction du préfixe utilisé. Ce n'est pas aussi concis que certains extraits, mais j'aime les résultats.

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

À partir de toutes les réponses précédentes, voici mon point de vue. C'est un objet qui stockera la taille du fichier en octets sous forme d'entier. Mais lorsque vous essayez d'imprimer l'objet, vous obtenez automatiquement une version lisible par l'homme.

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

J'aime la précision fixe de la version décimale de senderle , alors voici une sorte d'hybride avec la réponse de joctee ci-dessus (saviez-vous que vous pouviez prendre des journaux avec des bases non entières?):

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

Que diriez-vous d'une simple doublure:

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

Voici comment cela fonctionne sous le capot:

  1. Calcule le journal 2 (taille de fichier)
  2. Divise-le par 10 pour obtenir l'unité la plus proche. (par exemple, si la taille est de 5 000 octets, l'unité la plus proche l'est Kb, donc la réponse doit être X Kio)
  3. Retourne file_size/value_of_closest_unitavec l'unité.

Cependant, cela ne fonctionne pas si la taille du fichier est 0 ou négative (car le journal n'est pas défini pour les nombres 0 et -ve). Vous pouvez leur ajouter des chèques supplémentaires:

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

Exemples:

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

NOTE - Il y a une différence entre Kb et KiB. KB signifie 1000 octets, tandis que KiB signifie 1024 octets. KB, MB, GB sont tous des multiples de 1000, tandis que KiB, MiB, GiB etc. sont tous des multiples de 1024. Plus d'informations ici


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

Ce que vous êtes sur le point de trouver ci-dessous n'est en aucun cas la solution la plus performante ou la plus courte parmi celles déjà publiées. Au lieu de cela, il se concentre sur un problème particulier que la plupart des autres réponses manquent.

A savoir le cas où une entrée comme 999_995est donnée:

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

qui, étant tronqué à l'entier le plus proche et appliqué de nouveau à l'entrée, donne

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

Cela semble être exactement ce à quoi nous nous attendions jusqu'à ce que nous soyons tenus de contrôler la précision de la sortie . Et c'est là que les choses commencent à devenir un peu difficiles.

Avec une précision de 2 chiffres, nous obtenons:

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

au lieu de 1M.

Comment pouvons-nous contrer cela?

Bien sûr, nous pouvons le vérifier explicitement:

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

Mais pouvons-nous faire mieux? Pouvons-nous savoir de quelle façon les ordercoupes devraient être effectuées avant de faire la dernière étape?

Il s'avère que nous le pouvons.

En supposant une règle d'arrondi de 0,5 décimal, la ifcondition ci-dessus se traduit par:

entrez la description de l'image ici

résultant 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]}'

donnant

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

référer Sridhar Ratnakumarla réponse, mise à jour pour:

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)

et un exemple de sortie est:

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

Cette solution peut également vous intéresser, selon le fonctionnement de votre esprit:

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'
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.