用Python转换文件大小的更好方式

148

我正在使用一个库读取文件并返回它的字节数。

然后将该文件大小显示给最终用户; 为了让他们更容易理解,我明确地通过将其除以1024.0 * 1024.0来将文件大小转换为MB。当然这样可以实现,但我想知道在Python中是否有更好的方法?

更好的方法是指可能有一个标准库函数可以根据所需类型操作大小。比如,如果我指定MB,它会自动将其除以1024.0 * 1024.0。就像这样的某些东西。


6
请写一篇翻译。另外请注意,现在许多系统使用MB表示10的六次方而不是2的20次方。请写下一篇翻译,同时需要注意现在很多系统使用MB来表示1百万,而非2的20次方。 - tc.
8
请记住,SI和IEC标准中,“kB(千字节)表示1,000字节”,“KiB(基比字节)表示1,024字节”。参见http://en.wikipedia.org/wiki/Kibibyte。 - Bobby
2
@Bobby:kB实际上是“千贝尔”,相当于10000 dB。字节没有国际单位制。据我所知,IEC建议使用KiB,但未定义kB或KB。 - tc.
2
@tc。SI定义前缀kilo表示1000。IEC定义了kB等使用SI前缀而不是2^10。 - ford
显示剩余3条评论
12个回答

257

这是我使用的内容:

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:大小应以字节为单位发送。


12
如果您正在发送以字节为单位的大小,则只需将“B”添加为size_name的第一个元素即可。 - tuxGurl
genclik - 你说得对。我刚刚提交了一个小修改,将修复这个问题,并使字节转换成为可能。感谢原作者Sapam。 - FarmerGedden
8
实际上,大小的名称应该为(“B”,“KiB”,“MiB”,“GiB”,“TiB”,“PiB”,“EiB”,“ZiB”,“YiB”)。更多信息请参见 https://en.wikipedia.org/wiki/Mebibyte 。 - Alex
使用此解决方案 - Ajay Kumar
做得很漂亮。非常好用。 - soBusted
显示剩余3条评论

147

有一个hurry.filesize库,可以将字节数转换为易读的字符串。

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

如果你想让 1K 等于 1000(这是大多数用户的假设):

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

它也支持IEC(但这没有记录):

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

因为它是由强大的Martijn Faassen编写的,所以代码量小、清晰易懂且可扩展。编写自己的系统非常容易。

这是其中之一:

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

使用方法如下:

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

2
嗯,现在我需要反过来。从“1 kb”到1024(一个整数)。 - mlissner
2
仅适用于Python 2。 - e-info128
7
这个软件包可能很不错,但奇怪的许可证和没有在线可用的源代码让我很愿意避免使用它。而且它似乎只支持Python2。 - Almog Cohen
2
@AlmogCohen 源代码在线,可直接从PyPI获取(有些软件包没有Github存储库,只有一个PyPI页面),而且许可证并不那么模糊,ZPL是Zope公共许可证,最好的知识是类似于BSD。我同意许可本身很奇怪:没有标准的'LICENSE.txt'文件,也没有每个源文件顶部的序言。 - sleblanc
1
为了获得兆字节,我使用位移运算符执行以下方程式:MBFACTOR = float(1 << 20); mb= int(size_in_bytes) / MBFACTOR @LennartRegebro - alper
显示剩余5条评论

60

您可以使用位移运算符bitwise shifting operator<<,代替大小除数1024 * 1024。例如,1<<20可以得到兆字节,1<<30可以得到千兆字节。

在最简单的情况下,您可以定义一个常量MBFACTOR = float(1<<20),然后将其用于字节,即:megas = size_in_bytes/MBFACTOR

通常只需要使用兆字节,或者可以使用类似以下的内容:

# bytes pretty-printing
UNITS_MAPPING = [
    (1<<50, ' PB'),
    (1<<40, ' TB'),
    (1<<30, ' GB'),
    (1<<20, ' MB'),
    (1<<10, ' KB'),
    (1, (' byte', ' bytes')),
]


def pretty_size(bytes, units=UNITS_MAPPING):
    """Get human-readable file sizes.
    simplified version of https://pypi.python.org/pypi/hurry.filesize/
    """
    for factor, suffix in units:
        if bytes >= factor:
            break
    amount = int(bytes / factor)

    if isinstance(suffix, tuple):
        singular, multiple = suffix
        if amount == 1:
            suffix = singular
        else:
            suffix = multiple
    return str(amount) + suffix

print(pretty_size(1))
print(pretty_size(42))
print(pretty_size(4096))
print(pretty_size(238048577))
print(pretty_size(334073741824))
print(pretty_size(96995116277763))
print(pretty_size(3125899904842624))

## [Out] ###########################
1 byte
42 bytes
4 KB
227 MB
311 GB
88 TB
2 PB

11
不是 >> 吗? - Tjorriemorrie
2
@Tjorriemorrie:它必须是左移,右移将会移除唯一的位并导致为 0 - ccpizza
精彩的回答。谢谢你。 - Borislav Aymaliev
我知道这是老代码,但这个用法正确吗?def convert_to_mb(data_b): print(data_b/(1 << 20)) - roastbeeef
@roastbeef 是的,那是正确的。我喜欢这个答案,因为我只需要将字节转换成兆字节。 - Matthias

46

以下是一些易于复制的单行代码,如果您已经知道要使用的单位大小,请使用它们。 如果您正在寻找更通用的功能并具有一些不错的选项,请参阅我在2021年2月更新的内容...

字节

print(f"{os.path.getsize(filepath):,} B") 

千比特

print(f"{os.path.getsize(filepath)/(1<<7):,.0f} kb")

千字节

print(f"{os.path.getsize(filepath)/(1<<10):,.0f} KB")

兆位

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

兆字节

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

千兆位

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

千兆字节

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

千兆字节

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

2021年2月更新: 以下是我更新和完善的函数,用于a)获取文件/文件夹大小,b)将其转换为所需的单位:

from pathlib import Path

def get_path_size(path = Path('.'), recursive=False):
    """
    Gets file size, or total directory size

    Parameters
    ----------
    path: str | pathlib.Path
        File path or directory/folder path

    recursive: bool
        True -> use .rglob i.e. include nested files and directories
        False -> use .glob i.e. only process current directory/folder

    Returns
    -------
    int:
        File size or recursive directory size in bytes
        Use cleverutils.format_bytes to convert to other units e.g. MB
    """
    path = Path(path)
    if path.is_file():
        size = path.stat().st_size
    elif path.is_dir():
        path_glob = path.rglob('*.*') if recursive else path.glob('*.*')
        size = sum(file.stat().st_size for file in path_glob)
    return size


def format_bytes(bytes, unit, SI=False):
    """
    Converts bytes to common units such as kb, kib, KB, mb, mib, MB

    Parameters
    ---------
    bytes: int
        Number of bytes to be converted

    unit: str
        Desired unit of measure for output


    SI: bool
        True -> Use SI standard e.g. KB = 1000 bytes
        False -> Use JEDEC standard e.g. KB = 1024 bytes

    Returns
    -------
    str:
        E.g. "7 MiB" where MiB is the original unit abbreviation supplied
    """
    if unit.lower() in "b bit bits".split():
        return f"{bytes*8} {unit}"
    unitN = unit[0].upper()+unit[1:].replace("s","")  # Normalised
    reference = {"Kb Kib Kibibit Kilobit": (7, 1),
                 "KB KiB Kibibyte Kilobyte": (10, 1),
                 "Mb Mib Mebibit Megabit": (17, 2),
                 "MB MiB Mebibyte Megabyte": (20, 2),
                 "Gb Gib Gibibit Gigabit": (27, 3),
                 "GB GiB Gibibyte Gigabyte": (30, 3),
                 "Tb Tib Tebibit Terabit": (37, 4),
                 "TB TiB Tebibyte Terabyte": (40, 4),
                 "Pb Pib Pebibit Petabit": (47, 5),
                 "PB PiB Pebibyte Petabyte": (50, 5),
                 "Eb Eib Exbibit Exabit": (57, 6),
                 "EB EiB Exbibyte Exabyte": (60, 6),
                 "Zb Zib Zebibit Zettabit": (67, 7),
                 "ZB ZiB Zebibyte Zettabyte": (70, 7),
                 "Yb Yib Yobibit Yottabit": (77, 8),
                 "YB YiB Yobibyte Yottabyte": (80, 8),
                 }
    key_list = '\n'.join(["     b Bit"] + [x for x in reference.keys()]) +"\n"
    if unitN not in key_list:
        raise IndexError(f"\n\nConversion unit must be one of:\n\n{key_list}")
    units, divisors = [(k,v) for k,v in reference.items() if unitN in k][0]
    if SI:
        divisor = 1000**divisors[1]/8 if "bit" in units else 1000**divisors[1]
    else:
        divisor = float(1 << divisors[0])
    value = bytes / divisor
    return f"{value:,.0f} {unitN}{(value != 1 and len(unitN) > 3)*'s'}"


# Tests 
>>> assert format_bytes(1,"b") == '8 b'
>>> assert format_bytes(1,"bits") == '8 bits'
>>> assert format_bytes(1024, "kilobyte") == "1 Kilobyte"
>>> assert format_bytes(1024, "kB") == "1 KB"
>>> assert format_bytes(7141000, "mb") == '54 Mb'
>>> assert format_bytes(7141000, "mib") == '54 Mib'
>>> assert format_bytes(7141000, "Mb") == '54 Mb'
>>> assert format_bytes(7141000, "MB") == '7 MB'
>>> assert format_bytes(7141000, "mebibytes") == '7 Mebibytes'
>>> assert format_bytes(7141000, "gb") == '0 Gb'
>>> assert format_bytes(1000000, "kB") == '977 KB'
>>> assert format_bytes(1000000, "kB", SI=True) == '1,000 KB'
>>> assert format_bytes(1000000, "kb") == '7,812 Kb'
>>> assert format_bytes(1000000, "kb", SI=True) == '8,000 Kb'
>>> assert format_bytes(125000, "kb") == '977 Kb'
>>> assert format_bytes(125000, "kb", SI=True) == '1,000 Kb'
>>> assert format_bytes(125*1024, "kb") == '1,000 Kb'
>>> assert format_bytes(125*1024, "kb", SI=True) == '1,024 Kb'

2022年10月更新

我对最近的一条评论回答得太长了,所以在这里对1<<20魔法的进一步解释!我还注意到以上示例中不需要使用float,因此我已将其删除。

如在上面的另一个回复中所述,“<<”是一种“按位运算符”。它将左侧转换为二进制,并将二进制数字向左移动20个位置(在本例中)。当我们以十进制正常计数时,数字总数决定我们是否已达到十位、百位、千位、百万等。在二进制中也是类似的,只是数字的位数决定我们是否在谈论位、字节、千字节、兆字节等。所以…… 1<<20实际上相当于(二进制)1后面跟着20个(二进制)零,或者如果您记得如何从二进制转换为十进制:2的20次方(2**20),即1048576。在上面的代码片段中,os.path.getsize返回一个以字节为单位的值,而1048576字节严格来说是Mebibyte(MiB),随意地说是Megabyte(MB)。


1
这是一个相当巧妙的方法。我想知道你是否可以将它们放入一个函数中,其中你可以传递kb、mb等参数。如果你经常这样做,你甚至可以有一个输入命令来询问你需要哪种,这将非常方便。 - Hildy
看上面,Hildy...你也可以像@lennart-regebro上面概述的那样自定义字典行...这对于存储管理非常有用,例如“分区”,“集群”,“4TB磁盘”,“DVD_RW”,“蓝光光盘”,“1GB内存棒”或其他任何内容。 - Peter F
我还添加了Kb(千比特)、Mb(兆比特)和Gb(吉比特)——用户在网络或文件传输速度方面经常混淆它们,所以认为这可能会很方便。 - Peter F
我喜欢一行代码,考虑使用f-strings进行压缩,例如:f'{os.path.getsize(filepath)/float(1<<20):.0f} MB' - pan0ramic
非常感谢 @pan0ramic! - Peter F
显示剩余2条评论

29

这是一个用于计算大小的简洁函数

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

如果需要更详细的输出或相反的操作,请参考:http://code.activestate.com/recipes/578019-bytes-to-human-human-to-bytes-converter/


while语句应该改为 while size >= 1024 and index < len(suffixes):,否则函数会返回1024.0KB而不是1.0MB - AnythingIsFine

22

就是这个:

def convert_bytes(size):
    for x in ['bytes', 'KB', 'MB', 'GB', 'TB']:
        if size < 1024.0:
            return "%3.1f %s" % (size, x)
        size /= 1024.0

    return size

输出

>>> convert_bytes(1024)
'1.0 KB'
>>> convert_bytes(102400)
'100.0 KB'

4
那里的MiB,不是MB等等... - Bouncner

12

如果有人在寻找这个问题的反向解决办法(就像我一样),这是对我有效的方法:

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

1
你没有处理像1.5GB这样的十进制数的情况。要修复它,只需将<< 10更改为* 1024,将<< 20更改为* 1024 ** 2,将<< 30更改为* 1024 ** 3 - E235

7
UNITS = {1000: ['KB', 'MB', 'GB'],
            1024: ['KiB', 'MiB', 'GiB']}

def approximate_size(size, flag_1024_or_1000=True):
    mult = 1024 if flag_1024_or_1000 else 1000
    for unit in UNITS[mult]:
        size = size / mult
        if size < mult:
            return '{0:.3f} {1}'.format(size, unit)

approximate_size(2123, False)

这个在很多场合都可以使用。很高兴我看到了这条评论。非常感谢。 - Saurabh Jain
是的,这相当不错,也不需要外部库。 - chowpay

2

以下是我个人的建议,它允许上下取整,并增加了可自定义的精度:

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)

根据您的需求添加TB等内容。

我会为此点赞,因为它可以仅使用Python标准库来解决。 - Ciasto piekarz

2

我希望进行双向转换,并且想要使用Python 3的format()支持,以实现最Pythonic的效果。也许可以尝试使用datasize库模块?https://pypi.org/project/datasize/

$ pip install -qqq datasize
$ python
...
>>> from datasize import DataSize
>>> 'My new {:GB} SSD really only stores {:.2GiB} of data.'.format(DataSize('750GB'),DataSize(DataSize('750GB') * 0.8))
'My new 750GB SSD really only stores 558.79GiB of data.'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接