可重用的库以获得人类可读的文件大小?


238

Web上有各种片段,这些片段将为您提供从字节大小返回人类可读大小的功能:

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

但是是否有提供此功能的Python库?


2
我认为这属于“任务太小,需要图书馆”的标题。如果您查看hurry.filesize的源代码,则只有一个函数,带有十几行代码。甚至可以将其压缩。
本·布兰克

8
使用库的好处是通常会对其进行测试(包含可以在用户的​​编辑引入错误的情况下运行的测试)。如果添加测试,则不再是“数十行代码” :-)
Sridhar Ratnakumar,2009年

在python社区重新发明轮子的数量是疯狂和荒谬的。只需ls -h /path/to/file.ext就可以了。话虽如此,被接受的答案做得很好。工藤
爱德华·昂

Answers:


522

通过简单的实现解决了上述“任务太少,需要库”的问题:

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)

支持:

  • 所有当前已知的二进制前缀
  • 负数和正数
  • 大于1000 Yobibytes的数字
  • 任意单位(也许您想算成千兆比特!)

例:

>>> sizeof_fmt(168963795964)
'157.4GiB'

弗雷德·西拉Fred Cirera)


4
数字和单位之间应该有一个空格。如果要输出html或乳胶,则应为非空格。
josch 2014年

3
只是一个想法,但是对于除B(即对于字节以外的单位)以外的任何(?)后缀,您希望该因子1000.0而不1024.0是否?
Anentropic 2014年

5
如果要提高小数部分的精度,请将1第4行和第6行的值更改为所需的精度。
马修·G

44
如果捕获了所有关于“任务太小”的迭代并将其封装到带有测试的库中,那肯定会很好。
fess。

6
@ MD004相反。定义前缀,使得1 KB = 1000 B,1 KiB =
1024B。– augurar

116

具有所有您正在寻找的功能的库是humanizehumanize.naturalsize()似乎可以满足您的所有需求。


9
使用OP中数据的一些示例:humanize.naturalsize(2048) # => '2.0 kB'humanize.naturalsize(2048, binary=True) # => '2.0 KiB' humanize.naturalsize(2048, gnu=True) # => '2.0K'
RubenLaguna

32

这是我的版本。它不使用for循环。它具有恒定的复杂度O(1),并且在理论上比这里使用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'

为了更清楚地说明发生了什么,我们可以省略字符串格式化的代码。这是实际完成工作的行:

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

2
当您谈论优化这样的短代码时,为什么不使用if / elif / else?除非您期望文件大小为负,否则最后检查num == 1是不必要的。否则:不错,我喜欢这个版本。
TED 2012年

2
我的代码肯定可以更优化。但是,我的观点是证明可以以恒定的复杂度解决此任务。
joctee 2012年

37
for循环的答案也是O(1),因为for循环是有界的-它们的计算时间不随输入大小而定(我们没有无限的SI前缀)。
Thomas Minor

1
可能应该为格式添加逗号,因此1000将显示为1,000 bytes
iTayb 2014年

3
请注意,在使用Python 3时,zip返回一个迭代器,因此您需要使用list()包装它。 unit_list = list(zip(['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'], [0, 0, 1, 2, 2, 2]))
donarb

30

在我看来,以下代码在Python 3.6+中有效,这是最容易理解的答案,它允许您自定义使用的小数位数。

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

虽然我知道这个问题很古老,但是我最近想出了一个避免循环的版本,log2用于确定大小顺序,该大小顺序作为偏移量加倍,并且是后缀列表的索引:

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

虽然很容易被认为是非Python的,但:)


1
当我喜欢log2时,您应该处理size == 0!
玛蒂·尼托

你需要自动换行size(1 << (order * 10)float()最后一行(对于Python 2)。
哈维

仅供参考:import math那里可能需要一些。
monsto

@monsto true,添加了:)
akaIDIOT

爱这是多么紧凑!感谢你的分享。
约翰·克劳福德

17

总是有一个这样的家伙。好吧,今天是我。这是一种单线解决方案-如果您计算功能签名,则为两行。

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
仅供参考,输出将始终四舍五入。
wp-overwatch.com

1
为避免在方法中使用列表作为默认参数,为方法内的单位分配默认列表会更好吗?(并units=None改为使用)
Imanol

3
@ImanolEizaguirre最佳实践将指出,按照您的建议进行操作是个好主意,因此您不会无意中将错误引入程序中。但是,编写此函数很安全,因为决不会操纵单位列表。如果对其进行了操作,则更改将是永久的,并且任何后续函数调用都将收到列表的经过处理的版本,作为unites参数的默认参数。
wp-overwatch.com

对于Python 3,如果需要小数点,请改用此格式:```def human_size(fsize,units = ['bytes','KB','MB','GB','TB','PB', 'EB']):返回“ {:.2f} {}”。format(float(fsize),units [0]),如果fsize <1024,否则为human_size(fsize / 1024,units [1:])```
Omer

15

如果您使用的是安装的Django,也可以尝试使用filesizeformat

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

=>

"1.0 GB"

1
一个缺点对我来说这是它采用的不是吉布GB,即使它是由1024分
Pepedou

9

这样的库之一是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
但是,该库不是非常可定制的。>>>从hurry.filesize导入大小>>>大小(1031053)>>>大小(3033053)'2M'我希望它显示例如'2.4M'或'2423K'..而不是公然近似的' 2M'。
Sridhar Ratnakumar,2009年

还要注意,如果要处理依赖系统等,只需从hurry.filesize中获取代码并将其直接放入自己的代码中是非常容易的。它与人们在此处提供的摘要一样短。
mlissner,2011年

@SridharRatnakumar,为以某种智能方式解决过逼近问题,请参阅我的数学技巧。可以进一步改进该方法吗?
Acumenus 2015年

9

使用1000或千字节的幂将更符合标准:

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永远不要相信带有K(大写)后缀的库,它可以打印成千上万个:)


P.S. Never trust a library that prints thousands with the K (uppercase) suffix :)为什么不?代码可能听起来很完美,而作者只是没有考虑公斤的大小写。根据您的规则自动关闭任何代码似乎很愚蠢……
Douglas Gaskell

7

这几乎可以在任何情况下满足您的需求,并且可以使用可选参数进行自定义,并且您可以看到,它几乎可以自我记录:

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)

输出示例:

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

高级定制:

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

此代码与Python 2和Python 3兼容。PEP8合规性是读者的一项练习。记住,这是很漂亮的输出

更新:

如果您需要数千个逗号,只需应用明显的扩展名即可:

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

例如:

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


6

整理作为hurry.filesize()替代方案提供的代码段,以下是一个代码段,该代码段根据使用的前缀给出不同的精度数字。它不如某些摘要那么简洁,但是我喜欢结果。

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

从以前的所有答案中得出,这是我的看法。这是一个对象,它将文件大小(以字节为单位)存储为整数。但是,当您尝试打印对象时,会自动获得人类可读的版本。

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

我喜欢senderle十进制版本的固定精度,因此这是上面joctee答案的一种混合形式(您是否知道可以使用非整数基数的日志?):

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

一个简单的2班轮怎么样:

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

下面是它的工作原理:

  1. 计算日志2(文件大小)
  2. 将其除以10得到最接近的单位。(例如,如果大小为5000字节,则最接近的单位为Kb,因此答案应为X KiB)
  3. file_size/value_of_closest_unit与单位一起返回。

但是,如果文件大小为0或负数,则它不起作用(因为未定义0和-ve数字的日志)。您可以为其添加额外的支票:

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

例子:

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

注意 -Kb和KiB之间有区别。KB表示1000字节,而KiB表示1024字节。KB,MB,GB是1000的倍数,而昆明植物研究所,MIB,吉布等都是1024的倍数更多在这里


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

在下面已发布的解决方案中,您绝不会发现性能最高或最短的解决方案。相反,它专注于许多其他答案都错过的一个特定问题

999_995给出类似输入的情况:

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

将其截断为最接近的整数并应用于输入

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

在我们需要控制之前,这似乎正是我们所期望的 输出精度。这是事情开始变得有点困难的时候。

将精度设置为2位数,我们得到:

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

代替1M

我们如何应对呢?

当然,我们可以明确地检查它:

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

但是我们可以做得更好吗?我们可以知道哪种方式order在进行最后一步之前,我们可以应该采用切割吗?

事实证明我们可以。

假设使用十进制四舍五入规则,则上述if条件转换为:

在此处输入图片说明

导致

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

给予

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

参考Sridhar Ratnakumar的答案,更新为:

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)

示例输出为:

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

根据您的思维方式,此解决方案也可能对您有吸引力:

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'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.