web-dev-qa-db-fra.com

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

Il existe divers extraits sur le Web qui vous donneraient une fonction permettant de retourner une taille lisible par l'homme à partir d'une taille en octets:

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

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

180
Sridhar Ratnakumar

Aborder le problème ci-dessus "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 (vous aimez peut-être compter en gibibits!)

Exemple:

>>> sizeof_fmt(168963795964)
'157.4GiB'

par Fred Cirera

415
Sridhar Ratnakumar

Une bibliothèque qui possède toutes les fonctionnalités que vous semblez recherchées est humanize . humanize.naturalsize() semble faire tout ce que vous cherchez.

89
Pyrocater

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 de la chaîne. Voici les lignes qui font réellement le travail:

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

Bien que je sache que cette question est ancienne, j’ai récemment proposé une version qui évite les boucles, en utilisant log2 pour déterminer l’ordre de taille qui sert également de décalage et d’index dans la liste de 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 être considéré comme non rythmique pour sa lisibilité, cependant :)

22
akaIDIOT

Si vous utilisez Django installé, vous pouvez également essayer filesizeformat :

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

=>

"1.0 GB"
12
Jon Tirsen

Il doit toujours y avoir un de ces gars. Eh bien 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
9
Mr. Me

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'
9
Sridhar Ratnakumar

Utiliser des puissances de 1000 ou kibibytes serait plus pratique:

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)

P.S. Ne faites jamais confiance à une bibliothèque qui imprime des milliers de personnes avec le suffixe K (majuscule) :)

8

Cela fera ce dont vous avez besoin dans presque toutes les situations, il est personnalisable avec des arguments optionnels et, comme vous pouvez le voir, est pretty beaucoup 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 le output qui est joli.

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

En sortant de la solution Sridhar Ratnakumar, j'aime un peu mieux ça. Fonctionne en Python 3.6+

def human_readable_size(size, decimal_places):
    for unit in ['','KB','MB','GB','TB']:
        if size < 1024.0:
            break
        size /= 1024.0
    return f"{size:.{decimal_places}f}{unit}"
6
Mr. Me

En se basant sur 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 des 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)
6
markltbaker

En me basant sur 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)
4
xApple

J'aime la précision fixe de la version décimale de senderle , alors voici une sorte d'hybride de cela avec la réponse de joctee ci-dessus (saviez-vous que vous pouvez 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')
3
HST

Le projet HumanFriendly aide avec cela .

import humanfriendly
humanfriendly.format_size(1024)

Le code ci-dessus donnera 1 Ko comme réponse.
Exemples peuvent être trouvés ici .

3
arumuga abinesh

Que diriez-vous d'un simple 2 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 journal2(taille du fichier)
  2. Divise par 10 pour obtenir l'unité la plus proche. (Par exemple, si la taille est de 5000 octets, l'unité la plus proche est Kb, la réponse devrait donc être X KiB)
  3. Retourne file_size/value_of_closest_unit avec l'unité.

Cependant, cela ne fonctionne pas si la taille du fichier est 0 ou négatif (parce que le journal n'est pas défini pour les nombres 0 et -ve). Vous pouvez ajouter des contrôles supplémentaires pour eux:

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, alors que KiB signifie 1024 octets. KB, MB, GB sont tous des multiples de 1000, alors que KiB, MiB, GiB, etc. sont tous des multiples de 1024. Pour en savoir plus ici

2
jerrymouse

DiveIntoPython3 aussi parle de cette fonction.

2
Sridhar Ratnakumar

Vous devriez utiliser "humaniser".

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

Référence:

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

2
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
Matt Joiner

Django moderne a une balise de modèle autonome filesizeformat:

Formate la valeur comme une taille de fichier human-readable (c'est-à-dire '13 Ko ',' 4.1 MB ',' 102 octets ', etc.).

Par exemple:

{{ value|filesizeformat }}

Si la valeur est 123456789, la sortie serait 117,7 Mo.

Plus d'infos: https://docs.djangoproject.com/fr/1.10/ref/templates/builtins/#filesizeformatat

1
METAJIJI

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 qui manque à beaucoup d’autres réponses.

À savoir le cas où une entrée comme 999_995 est donnée:

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

qui, tronqué au nombre entier le plus proche et appliqué à 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 devions contrôler précision de la sortie . Et c'est à ce moment que les choses commencent à devenir un peu difficiles.

Avec la précision réglée à 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 manière la order doit être réduite avant de passer à l'étape finale?

Il s'avère que nous pouvons.

En supposant une règle d’arrondi de 0,5 décimale, la condition if ci-dessus se traduit par:

 enter image description here 

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
ayorgo

reportez-vous à la réponse de Sridhar Ratnakumar, 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 la sortie d'exemple 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
crifan

Cette solution pourrait é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'
0
Peter F