Sridhar Ratnakumar
Sridhar Ratnakumar

Reputation: 85402

Get a human-readable version of a file size

A function to return a human-readable size from the bytes size:

>>> human_readable(2048)
'2 kilobytes'

How can I do this?

Upvotes: 366

Views: 183119

Answers (30)

user22978908
user22978908

Reputation: 1

Use

from naturalsize import nsize
nsize(500) #insert your file size here

from the naturalsize module.

Upvotes: -1

Vinícius Queiroz
Vinícius Queiroz

Reputation: 1228

In case someone is wondering, to convert Sridhar Ratnakumar's answer back to bytes you could do the following:

import math

def format_back_to_bytes(value):
    for power, unit in enumerate(["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]):
        if value[-3:-1] == unit:
            return round(float(value[:-3])*math.pow(2, 10*power))

Usage:

>>> format_back_to_bytes('212.4GiB')
228062763418

Upvotes: 2

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48630

Here is an alternate version of joctee's implementation updated to use Python 3 syntax and handle negatives as exceptional cases.

I added types and docs, as well as raising an error upon receiving a negative number as input.

#!/usr/bin/env python3
from math import log
from typing import List, Tuple

def format_size(size: int) -> str:
    """
    Convert a file size to a human-friendly format.

    Parameters:
    size (int): The file size in bytes.

    Returns:
    str: The human-friendly format of the file size.

    Raises:
    ValueError: If size is negative.
    """
    units: List[Tuple[str, int]] = [
        ("bytes", 0),
        ("kB", 0),
        ("MB", 1),
        ("GB", 2),
        ("TB", 2),
        ("PB", 2)
    ]
    
    if size < 0:
        raise ValueError("Negative size not allowed")
    if size == 0:
        return "0 bytes"
    if size == 1:
        return "1 byte"
    
    exponent = min(int(log(size, 1024)), len(units) - 1)
    quotient = float(size) / 1024**exponent
    unit, precision = units[exponent]
    return f"{quotient:.{precision}f} {unit}"

Here is a more terse, iterative version:

def format_size(size: int) -> str:
    if size < 0:
        raise ValueError("Negative size not allowed")
    for unit in ["bytes", "KB", "MB", "GB", "TB", "PB"]:
        if size < 1024.0:
            return f"{size:.2f} {unit}"
        size /= 1024.0

Usage

Here is some example of usage for the format_size function:

#!/usr/bin/env python3
from file_size_formatter import format_size

# Constants for file size units
KILOBYTE = 1024                 # 1,024 bytes
MEGABYTE = KILOBYTE * KILOBYTE  # 1,048,576 bytes
GIGABYTE = KILOBYTE * MEGABYTE  # 1,073,741,824 bytes
TERABYTE = KILOBYTE * GIGABYTE  # 1,099,511,627,776 bytes
PETABYTE = KILOBYTE * TERABYTE  # 1,125,899,906,842,624 bytes

def main():
    # Examples of usage:
    print(format_size(0))         # Output: '0 bytes'
    print(format_size(1))         # Output: '1 byte'
    print(format_size(KILOBYTE))  # Output: '1 kB'
    print(format_size(MEGABYTE))  # Output: '1.0 MB'
    print(format_size(GIGABYTE))  # Output: '1.00 GB'
    print(format_size(TERABYTE))  # Output: '1.00 TB'
    print(format_size(PETABYTE))  # Output: '1.00 PB'

    # Example with negative input
    try:
        print(format_size(-1024))  # This will raise a ValueError
    except ValueError as e:
        print(e)  # Output: 'Negative size not allowed'

if __name__ == "__main__":
    main()

PyTest

Here is a test suite that verifies the output of the format_size function:

#!/usr/bin/env python3
import pytest
from file_size_formatter import format_size

# Constants for file size units
KILOBYTE = 1024                 # 1,024 bytes
MEGABYTE = KILOBYTE * KILOBYTE  # 1,048,576 bytes
GIGABYTE = KILOBYTE * MEGABYTE  # 1,073,741,824 bytes
TERABYTE = KILOBYTE * GIGABYTE  # 1,099,511,627,776 bytes
PETABYTE = KILOBYTE * TERABYTE  # 1,125,899,906,842,624 bytes

def test_format_size():
    assert format_size(0) == "0 bytes"
    assert format_size(1) == "1 byte"
    assert format_size(KILOBYTE) == "1 kB"
    assert format_size(MEGABYTE) == "1.0 MB"
    assert format_size(GIGABYTE) == "1.00 GB"
    assert format_size(TERABYTE) == "1.00 TB"
    assert format_size(PETABYTE) == "1.00 PB"

def test_format_size_negative():
    with pytest.raises(ValueError, match="Negative size not allowed"):
        format_size(-1024)

Upvotes: 1

crifan
crifan

Reputation: 14328

Referencing Sridhar Ratnakumar's answer, updated to:

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)

and example output is:

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

Upvotes: -1

arumuga abinesh
arumuga abinesh

Reputation: 111

The HumanFriendly project helps with this.

import humanfriendly
humanfriendly.format_size(1024)

The above code will give 1KB as the answer.
Examples can be found here.

Upvotes: 7

akaIDIOT
akaIDIOT

Reputation: 9231

I recently came up with a version that avoids loops, using log2 to determine the size order which doubles as a shift and an index into the suffix list:

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 maximum 3 decimals, and should never resort
    # to exponent values)
    return '{:.4g} {}'.format(size / (1 << (order * 10)), _suffixes[order])

It could well be considered unpythonic for its readability, though.

Upvotes: 32

Jon Tirsen
Jon Tirsen

Reputation: 4990

If you're using Django, you can also try filesizeformat:

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

=>

"1.0 GB"

Upvotes: 26

Pyrocater
Pyrocater

Reputation: 2202

A library that has all the functionality that it seems you're looking for is humanize. humanize.naturalsize() seems to do everything you're looking for.

Example code (Python 3.10)

import humanize

disk_sizes_list = [1, 100, 999, 1000,1024, 2000,2048, 3000, 9999, 10000, 2048000000, 9990000000, 9000000000000000000000]
for size in disk_sizes_list:
    natural_size = humanize.naturalsize(size)
    binary_size = humanize.naturalsize(size, binary=True)
    print(f" {natural_size} \t| {binary_size}\t|{size}")

Output

 1 Byte     | 1 Byte      |1
 100 Bytes  | 100 Bytes   |100
 999 Bytes  | 999 Bytes   |999
 1.0 kB     | 1000 Bytes  |1000
 1.0 kB     | 1.0 KiB     |1024
 2.0 kB     | 2.0 KiB     |2000
 2.0 kB     | 2.0 KiB     |2048
 3.0 kB     | 2.9 KiB     |3000
 10.0 kB    | 9.8 KiB     |9999
 10.0 kB    | 9.8 KiB     |10000
 2.0 GB     | 1.9 GiB     |2048000000
 10.0 GB    | 9.3 GiB     |9990000000
 9.0 ZB     | 7.6 ZiB     |9000000000000000000000

Upvotes: 209

joctee
joctee

Reputation: 2479

Here's my version. It does not use a for loop. It has constant complexity, O(1), and is in theory more efficient than the answers here that use a for loop.

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'

To make it more clear what is going on, we can omit the code for the string formatting. Here are the lines that actually do the work:

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

Upvotes: 39

Sridhar Ratnakumar
Sridhar Ratnakumar

Reputation: 85402

One such library is 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'

Upvotes: 10

Odin Kroeger
Odin Kroeger

Reputation: 51

Here is a class-based variant of Fred Cirera's answer, for those of us who prefer strong typing:

class Unit(int):
    multiplier = 1000
    precision = 2
    prefixes = ('', 'k')
    unit = '?'

    def __str__(self):
        number = self
        for prefix in self.prefixes:
            if number < self.multiplier or prefix == self.prefixes[-1]:
                break
            number /= float(self.multiplier)
        if prefix:
            return f'{number:.{self.precision}f}{prefix}{self.unit}'
        return f'{int(number)}{self.unit}'


class Filesize(Unit):
    multiplier = 1024
    prefixes = ('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi')
    unit = 'B'

class Distance(Unit):
    unit = 'm'


# Prints 123m
print(Distance(123))

# Prints 123456.8km
print(Distance(123456789))

Upvotes: 0

roundabout
roundabout

Reputation: 323

Here you have it:

def humanSize(value, decimals=2, scale=1024, units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")):
    for unit in units:
        if value < scale:
            break
        value /= scale
    if int(value) == value:
        # do not return decimals, if the value is already round
        return int(value), unit
    return round(value * 10**decimals) / 10**decimals, unit

Most correct format:

f"{humanSize(os.path.getsize(path))[0]}\u202f{humanSize(os.path.getsize(path))[1]}"

Examples:

>>> humanSize(42)
(42, 'B')
>>> humanSize(9137017301)
(8.51, 'GiB')
>>> humanSize(4096)
(4, 'kiB')
>>> humanSize(1267650600228229401496703205376)
(1, 'QiB')
>>> humanSize(543864)
(531.12, 'kiB')
>>> humanSize(500000000000)
(465.66, 'GiB')

Upvotes: 1

Sridhar Ratnakumar
Sridhar Ratnakumar

Reputation: 85402

Addressing the above "too small a task to require a library" issue by a straightforward implementation (using f-strings, so Python 3.6+):

def sizeof_fmt(num, suffix="B"):
    for unit in ("", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"):
        if abs(num) < 1024.0:
            return f"{num:3.1f}{unit}{suffix}"
        num /= 1024.0
    return f"{num:.1f}Yi{suffix}"

Supports:

  • all currently known binary prefixes
  • negative and positive numbers
  • numbers larger than 1000 Yobibytes
  • arbitrary units (maybe you like to count in Gibibits!)

Example:

>>> sizeof_fmt(168963795964)
'157.4GiB'

by Fred Cirera

Upvotes: 790

lxkarthi
lxkarthi

Reputation: 356

Here is an oneliner lambda without any imports to convert to human readable filesize. Pass the value in bytes.

to_human = lambda v : str(v >> ((max(v.bit_length()-1, 0)//10)*10)) +["", "K", "M", "G", "T", "P", "E"][max(v.bit_length()-1, 0)//10]
>>> to_human(1024)
'1K'
>>> to_human(1024*1024*3)
'3M'

Upvotes: 3

jak bin
jak bin

Reputation: 502

To get the file size in a human readable form, I created this function:

import os

def get_size(path):
    size = os.path.getsize(path)
    if size < 1024:
        return f"{size} bytes"
    elif size < pow(1024,2):
        return f"{round(size/1024, 2)} KB"
    elif size < pow(1024,3):
        return f"{round(size/(pow(1024,2)), 2)} MB"
    elif size < pow(1024,4):
        return f"{round(size/(pow(1024,3)), 2)} GB"
>>> get_size("a.txt")
1.4KB

Upvotes: 3

Terry Davis
Terry Davis

Reputation: 620

Here's something I wrote for a different question...

Much like xApple's answer, this object will always print in a human-readable format. The difference is that it's also a proper int, so you can do math with it! It passes the format specifier straight through to the number format and tacks on the suffix, so it's pretty much guaranteed that the requested length will be exceeded by two or three characters. I've never had a use for this code, so I haven't bothered to fix it!


class ByteSize(int):

    _KB = 1024
    _suffixes = 'B', 'KB', 'MB', 'GB', 'PB'

    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        self.bytes = self.B = int(self)
        self.kilobytes = self.KB = self / self._KB**1
        self.megabytes = self.MB = self / self._KB**2
        self.gigabytes = self.GB = self / self._KB**3
        self.petabytes = self.PB = self / self._KB**4
        *suffixes, last = self._suffixes
        suffix = next((
            suffix
            for suffix in suffixes
            if 1 < getattr(self, suffix) < self._KB
        ), last)
        self.readable = suffix, getattr(self, suffix)

        super().__init__()

    def __str__(self):
        return self.__format__('.2f')

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, super().__repr__())

    def __format__(self, format_spec):
        suffix, val = self.readable
        return '{val:{fmt}} {suf}'.format(val=val, fmt=format_spec, suf=suffix)

    def __sub__(self, other):
        return self.__class__(super().__sub__(other))

    def __add__(self, other):
        return self.__class__(super().__add__(other))
    
    def __mul__(self, other):
        return self.__class__(super().__mul__(other))

    def __rsub__(self, other):
        return self.__class__(super().__sub__(other))

    def __radd__(self, other):
        return self.__class__(super().__add__(other))
    
    def __rmul__(self, other):
        return self.__class__(super().__rmul__(other))   

Usage:

>>> size = 6239397620
>>> print(size)
5.81 GB
>>> size.GB
5.810891855508089
>>> size.gigabytes
5.810891855508089
>>> size.PB
0.005674699077644618
>>> size.MB
5950.353260040283
>>> size
ByteSize(6239397620)

Upvotes: 0

Zombo
Zombo

Reputation: 1

Here is an option using while:

def number_format(n):
   n2, n3 = n, 0
   while n2 >= 1e3:
      n2 /= 1e3
      n3 += 1
   return '%.3f' % n2 + ('', ' k', ' M', ' G')[n3]

s = number_format(9012345678)
print(s == '9.012 G')

https://docs.python.org/reference/compound_stmts.html#while

Upvotes: -1

cmcginty
cmcginty

Reputation: 117028

This feature if available in Boltons which is a very handy library to have for most projects.

>>> bytes2human(128991)
'126K'
>>> bytes2human(100001221)
'95M'
>>> bytes2human(0, 2)
'0.00B'

Upvotes: 0

hostingutilities.com
hostingutilities.com

Reputation: 9529

There's always got to be one of those guys. Well today it's me. Here's a one-liner -- or two lines if you count the function signature.

def human_size(bytes, units=[' bytes','KB','MB','GB','TB', 'PB', 'EB']):
    """ Returns a human readable string representation 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

If you need sizes bigger than an Exabyte, it's a little bit more gnarly:

def human_size(bytes, units=[' bytes','KB','MB','GB','TB', 'PB', 'EB']):
    return str(bytes) + units[0] if bytes < 1024 else human_size(bytes>>10, units[1:]) if units[1:] else f'{bytes>>10}ZB'

Upvotes: 47

hostingutilities.com
hostingutilities.com

Reputation: 9529

The following works in Python 3.6+, is, in my opinion, the easiest to understand answer on here, and lets you customize the amount of decimal places used.

def human_readable_size(size, decimal_places=2):
    for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']:
        if size < 1024.0 or unit == 'PiB':
            break
        size /= 1024.0
    return f"{size:.{decimal_places}f} {unit}"

Upvotes: 53

Peter F
Peter F

Reputation: 823

This solution might also appeal to you, depending on how your mind works:

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'

Upvotes: -2

ayorgo
ayorgo

Reputation: 3902

What you're about to find below is by no means the most performant or shortest solution among the ones already posted. Instead, it focuses on one particular issue that many of the other answers miss.

Namely the case when input like 999_995 is given:

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

which, being truncated to the nearest integer and applied back to the input gives

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

This seems to be exactly what we'd expect until we're required to control output precision. And this is when things start to get a bit difficult.

With the precision set to 2 digits we get:

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

instead of 1M.

How can we counter that?

Of course, we can check for it explicitly:

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

But can we do better? Can we get to know which way the order should be cut before we do the final step?

It turns out we can.

Assuming 0.5 decimal rounding rule, the above if condition translates into:

enter image description here

resulting in

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

giving

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

Upvotes: 1

Saeed Zahedian Abroodi
Saeed Zahedian Abroodi

Reputation: 1202

You should use "humanize".

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

Reference:

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

Upvotes: 15

jerrymouse
jerrymouse

Reputation: 17812

How about a 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])

Here is how it works under the hood:

  1. Calculates log2(filesize)
  2. Divides it by 10 to get the closest unit. (eg if size is 5000 bytes, the closest unit is Kb, so the answer should be X KiB)
  3. Returns file_size/value_of_closest_unit along with unit.

It however doesn't work if filesize is 0 or negative (because log is undefined for 0 and -ve numbers). You can add extra checks for them:

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

Examples:

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

NOTE - There is a difference between Kb and KiB. KB means 1000 bytes, whereas KiB means 1024 bytes. KB,MB,GB are all multiples of 1000, whereas KiB, MiB, GiB etc are all multiples of 1024. More about it here

Upvotes: 1

METAJIJI
METAJIJI

Reputation: 391

Modern Django have self template tag filesizeformat:

Formats the value like a human-readable file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc.).

For example:

{{ value|filesizeformat }}

If value is 123456789, the output would be 117.7 MB.

More info: https://docs.djangoproject.com/en/1.10/ref/templates/builtins/#filesizeformat

Upvotes: 4

HST
HST

Reputation: 67

I like the fixed precision of senderle's decimal version, so here's a sort of hybrid of that with joctee's answer above (did you know you could take logs with non-integer bases?):

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

Upvotes: 2

gojomo
gojomo

Reputation: 54203

This will do what you need in almost any situation, is customizable with optional arguments, and as you can see, is pretty much self-documenting:

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)

Example output:

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

Advanced customizations:

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

This code is both Python 2 and Python 3 compatible. PEP8 compliance is an exercise for the reader. Remember, it's the output that's pretty.

Update:

If you need thousands commas, just apply the obvious extension:

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

For example:

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

Upvotes: 8

Giancarlo Sportelli
Giancarlo Sportelli

Reputation: 1297

Using either powers of 1000 or kibibytes would be more standard-friendly:

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. Never trust a library that prints thousands with the K (uppercase) suffix :)

Upvotes: 8

xApple
xApple

Reputation: 6466

Drawing from all the previous answers, here is my take on it. It's an object which will store the file size in bytes as an integer. But when you try to print the object, you automatically get a human readable version.

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)

Upvotes: 4

markltbaker
markltbaker

Reputation: 481

Riffing on the snippet provided as an alternative to hurry.filesize(), here is a snippet that gives varying precision numbers based on the prefix used. It isn't as terse as some snippets, but I like the results.

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)

Upvotes: 7

Related Questions