Python 格式大小应用程序(将 B 转换为 KB、MB、GB、TB)



我正在尝试编写一个应用程序来将字节转换为 kb 到 mb 到 gb 到 tb。 这是我到目前为止所拥有的:

def size_format(b):
if b < 1000:
return '%i' % b + 'B'
elif 1000 <= b < 1000000:
return '%.1f' % float(b/1000) + 'KB'
elif 1000000 <= b < 1000000000:
return '%.1f' % float(b/1000000) + 'MB'
elif 1000000000 <= b < 1000000000000:
return '%.1f' % float(b/1000000000) + 'GB'
elif 1000000000000 <= b:
return '%.1f' % float(b/1000000000000) + 'TB'

问题是,当我尝试该应用程序时,我会在十进制归零后得到所有内容。 例size_format(623)产量 '623B' 但随着size_format(6200), 而不是获得"6.2kb" 我得到"6.0kb"。 知道为什么吗?

Bryan_Rch答案的修复版本:

def format_bytes(size):
# 2**10 = 1024
power = 2**10
n = 0
power_labels = {0 : '', 1: 'kilo', 2: 'mega', 3: 'giga', 4: 'tera'}
while size > power:
size /= power
n += 1
return size, power_labels[n]+'bytes'
def humanbytes(B):
"""Return the given bytes as a human friendly KB, MB, GB, or TB string."""
B = float(B)
KB = float(1024)
MB = float(KB ** 2) # 1,048,576
GB = float(KB ** 3) # 1,073,741,824
TB = float(KB ** 4) # 1,099,511,627,776
if B < KB:
return '{0} {1}'.format(B,'Bytes' if 0 == B > 1 else 'Byte')
elif KB <= B < MB:
return '{0:.2f} KB'.format(B / KB)
elif MB <= B < GB:
return '{0:.2f} MB'.format(B / MB)
elif GB <= B < TB:
return '{0:.2f} GB'.format(B / GB)
elif TB <= B:
return '{0:.2f} TB'.format(B / TB)

tests = [1, 1024, 500000, 1048576, 50000000, 1073741824, 5000000000, 1099511627776, 5000000000000]
for t in tests: print("{0} == {1}".format(t,humanbytes(t)))

输出:

1 == 1.0 Byte
1024 == 1.00 KB
500000 == 488.28 KB
1048576 == 1.00 MB
50000000 == 47.68 MB
1073741824 == 1.00 GB
5000000000 == 4.66 GB
1099511627776 == 1.00 TB
5000000000000 == 4.55 TB

对于未来的我来说,它也在Perl中:

sub humanbytes {
my $B = shift;
my $KB = 1024;
my $MB = $KB ** 2; # 1,048,576
my $GB = $KB ** 3; # 1,073,741,824
my $TB = $KB ** 4; # 1,099,511,627,776
if ($B < $KB) {
return "$B " . (($B == 0 || $B > 1) ? 'Bytes' : 'Byte');
} elsif ($B >= $KB && $B < $MB) {
return sprintf('%0.02f',$B/$KB) . ' KB';
} elsif ($B >= $MB && $B < $GB) {
return sprintf('%0.02f',$B/$MB) . ' MB';
} elsif ($B >= $GB && $B < $TB) {
return sprintf('%0.02f',$B/$GB) . ' GB';
} elsif ($B >= $TB) {
return sprintf('%0.02f',$B/$TB) . ' TB';
}
}

警告:其他答案可能包含错误。在此之前发布的那些无法处理接近下一个单元边界的文件大小。

划分字节以获得人类可读的答案似乎很容易,对吧?错!

许多答案都是不正确的,并且包含导致错误输出的浮点舍入错误,例如"1024 KiB"而不是"1 MiB"。不过,他们不应该为此感到难过,因为即使是Android的操作系统程序员过去也有一个错误,成千上万的程序员眼睛也没有注意到世界上最受欢迎的StackOverflow答案中的错误,尽管多年来人们使用旧的Java答案。

那么问题出在哪里呢?嗯,这是由于浮点舍入的工作方式。诸如"1023.95"之类的浮点数在被告知将自身格式化为单十进制数时实际上会四舍五入为"1024.0"。大多数程序员不会考虑这个错误,但它完全打破了"人类可读字节"的格式。所以他们的代码认为"哦,1023.95,没关系,我们已经找到了正确的单位,因为数字小于 1024",但他们没有意识到它会四舍五入为"1024.0",这应该被格式化为 NEXT 大小单位。

此外,许多其他答案都使用非常慢的代码和一堆数学函数,如pow/log,这可能看起来"整洁",但完全破坏了性能。大多数其他答案都使用疯狂的if/else嵌套或其他性能杀手,例如临时列表,实时字符串连接/创建等。简而言之,他们浪费CPU周期来做毫无意义的繁重工作。

它们中的大多数还忘记包含较大的单位,因此仅支持最常见文件大小的一小部分。给定更大的数字,这样的代码将输出类似"1239213919393491123.1 Gigabytes"的内容,这很愚蠢。他们中的一些人甚至不会这样做,如果输入数字大于他们实现的最大单位,就会中断。

此外,它们几乎没有一个处理负输入,例如"负2兆字节",并完全中断此类输入。

他们还对非常个人的选择进行硬编码,例如精度(小数位数)和单位类型(公制或二进制)。这意味着他们的代码几乎不可重用。

所以......好吧,我们遇到了当前答案不正确的情况......那么,为什么不把每件事都做好呢?这是我的函数,它侧重于性能和可配置性。您可以在 0-3 位小数之间进行选择,以及是需要度量(1000 的幂)还是二进制(1024 的幂)表示形式。它包含一些代码注释和使用示例,以帮助人们理解它为什么这样做以及它通过这种方式避免了哪些错误。如果删除所有注释,它将使行号减少很多,但我建议在复制粘贴时保留注释,以便您将来再次理解代码。;-)

from typing import List, Union
class HumanBytes:
METRIC_LABELS: List[str] = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
BINARY_LABELS: List[str] = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
PRECISION_OFFSETS: List[float] = [0.5, 0.05, 0.005, 0.0005] # PREDEFINED FOR SPEED.
PRECISION_FORMATS: List[str] = ["{}{:.0f} {}", "{}{:.1f} {}", "{}{:.2f} {}", "{}{:.3f} {}"] # PREDEFINED FOR SPEED.
@staticmethod
def format(num: Union[int, float], metric: bool=False, precision: int=1) -> str:
"""
Human-readable formatting of bytes, using binary (powers of 1024)
or metric (powers of 1000) representation.
"""
assert isinstance(num, (int, float)), "num must be an int or float"
assert isinstance(metric, bool), "metric must be a bool"
assert isinstance(precision, int) and precision >= 0 and precision <= 3, "precision must be an int (range 0-3)"
unit_labels = HumanBytes.METRIC_LABELS if metric else HumanBytes.BINARY_LABELS
last_label = unit_labels[-1]
unit_step = 1000 if metric else 1024
unit_step_thresh = unit_step - HumanBytes.PRECISION_OFFSETS[precision]
is_negative = num < 0
if is_negative: # Faster than ternary assignment or always running abs().
num = abs(num)
for unit in unit_labels:
if num < unit_step_thresh:
# VERY IMPORTANT:
# Only accepts the CURRENT unit if we're BELOW the threshold where
# float rounding behavior would place us into the NEXT unit: F.ex.
# when rounding a float to 1 decimal, any number ">= 1023.95" will
# be rounded to "1024.0". Obviously we don't want ugly output such
# as "1024.0 KiB", since the proper term for that is "1.0 MiB".
break
if unit != last_label:
# We only shrink the number if we HAVEN'T reached the last unit.
# NOTE: These looped divisions accumulate floating point rounding
# errors, but each new division pushes the rounding errors further
# and further down in the decimals, so it doesn't matter at all.
num /= unit_step
return HumanBytes.PRECISION_FORMATS[precision].format("-" if is_negative else "", num, unit)
print(HumanBytes.format(2251799813685247)) # 2 pebibytes
print(HumanBytes.format(2000000000000000, True)) # 2 petabytes
print(HumanBytes.format(1099511627776)) # 1 tebibyte
print(HumanBytes.format(1000000000000, True)) # 1 terabyte
print(HumanBytes.format(1000000000, True)) # 1 gigabyte
print(HumanBytes.format(4318498233, precision=3)) # 4.022 gibibytes
print(HumanBytes.format(4318498233, True, 3)) # 4.318 gigabytes
print(HumanBytes.format(-4318498233, precision=2)) # -4.02 gibibytes

顺便说一下,硬编码PRECISION_OFFSETS是以这种方式创建的,以实现最佳性能。我们可以使用公式unit_step_thresh = unit_step - (0.5/(10**precision))以编程方式计算偏移量,以支持任意精度。但是用大量的 4+ 尾随十进制数字格式化文件大小真的没有意义。这就是为什么我的函数完全支持人们使用的内容:0、1、2 或 3 位小数。因此,我们避免了一堆pow和除法数学。这个决定是使此功能快速完成的众多小细节选择之一。性能选择的另一个示例是决定使用基于字符串的if unit != last_label检查来检测列表的末尾,而不是按索引迭代并查看我们是否已到达最终的 List 索引。通过range()或通过enumerate()生成元组比仅对存储在_LABELS列表中的 Python 不可变字符串对象进行地址比较要,而这正是此代码所做的!

当然,把那么多工作放在性能上有点过分,但我讨厌"编写草率的代码,只有在项目中数千个缓慢的函数使整个项目变得缓慢之后才进行优化"的态度。大多数程序员赖以生存的"过早优化"报价完全被误解并被用作马虎的借口。:-P

我把这个代码放在公共领域。随意在您的项目中使用它,包括免费软件和商业软件。我实际上建议你把它放在一个.py模块中,把它从一个"类命名空间"改为一个普通的模块。我只使用了一个类来保持 StackOverflow 的代码整洁,如果您不想使用模块,可以轻松粘贴到自包含的 python 脚本中。

享受并玩得开心! :-)

对我来说是个好主意:

def convert_bytes(num):
"""
this function will convert bytes to MB.... GB... etc
"""
step_unit = 1000.0 #1024 bad the size
for x in ['bytes', 'KB', 'MB', 'GB', 'TB']:
if num < step_unit:
return "%3.1f %s" % (num, x)
num /= step_unit

另一个人类字节版本,没有循环/如果。否则,在 python3 语法中。

从@whereisalext的答案中窃取的测试数字。

请注意,它仍然是一个草图,例如,如果数字足够大,它将追溯。

import math as m

MULTIPLES = ["B", "k{}B", "M{}B", "G{}B", "T{}B", "P{}B", "E{}B", "Z{}B", "Y{}B"]

def humanbytes(i, binary=False, precision=2):
base = 1024 if binary else 1000
multiple = m.trunc(m.log2(i) / m.log2(base))
value = i / m.pow(base, multiple)
suffix = MULTIPLES[multiple].format("i" if binary else "")
return f"{value:.{precision}f} {suffix}"

if __name__ == "__main__":
sizes = [
1, 1024, 500000, 1048576, 50000000, 1073741824, 5000000000,
1099511627776, 5000000000000]
for i in sizes:
print(f"{i} == {humanbytes(i)}, {humanbytes(i, binary=True)}")

结果:

1 == 1.00 B, 1.00 B
1024 == 1.02 kB, 1.00 kiB
500000 == 500.00 kB, 488.28 kiB
1048576 == 1.05 MB, 1.00 MiB
50000000 == 50.00 MB, 47.68 MiB
1073741824 == 1.07 GB, 1.00 GiB
5000000000 == 5.00 GB, 4.66 GiB
1099511627776 == 1.10 TB, 1.00 TiB
5000000000000 == 5.00 TB, 4.55 TiB

更新:

正如注释中指出的那样(正如最初指出的那样:"请注意,它仍然是一个草图"),这段代码很慢而且有问题。请看@mitch-麦克马伯斯的回答。

更新2:我也在撒谎说没有if

现在有一个方便的 DataSize 包:

pip install datasize
import datasize
import sys
a = [i for i in range(1000000)]
s = sys.getsizeof(a)
print(f"{datasize.DataSize(s):MiB}")

输出:

8.2945556640625MiB

我有相当可读的功能将字节转换为更大的单位:

def bytes_2_human_readable(number_of_bytes):
if number_of_bytes < 0:
raise ValueError("!!! number_of_bytes can't be smaller than 0 !!!")
step_to_greater_unit = 1024.
number_of_bytes = float(number_of_bytes)
unit = 'bytes'
if (number_of_bytes / step_to_greater_unit) >= 1:
number_of_bytes /= step_to_greater_unit
unit = 'KB'
if (number_of_bytes / step_to_greater_unit) >= 1:
number_of_bytes /= step_to_greater_unit
unit = 'MB'
if (number_of_bytes / step_to_greater_unit) >= 1:
number_of_bytes /= step_to_greater_unit
unit = 'GB'
if (number_of_bytes / step_to_greater_unit) >= 1:
number_of_bytes /= step_to_greater_unit
unit = 'TB'
precision = 1
number_of_bytes = round(number_of_bytes, precision)
return str(number_of_bytes) + ' ' + unit

使用对数可能是最简洁的方法:

from math import floor, log
def format_bytes(size):
power = 0 if size <= 0 else floor(log(size, 1024))
return f"{round(size / 1024 ** power, 2)} {['B', 'KB', 'MB', 'GB', 'TB'][int(power)]}"

您可以更改除法的行为,而不是修改代码:

from __future__ import division

这为Python 2.x使用的"经典"风格提供了"真正的"划分。有关更多详细信息,请参阅 PEP 238 - 更改除法运算符。

这现在是Python 3.x中的默认行为。

一个非常简单的解决方案是:

SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
def get_readable_file_size(size_in_bytes):
index = 0
while size_in_bytes >= 1024:
size_in_bytes /= 1024
index += 1
try:
return f'{size_in_bytes} {SIZE_UNITS[index]}'
except IndexError:
return 'File too large'

当你除以值时,你使用的是整数除法,因为这两个值都是整数。您需要先将其中一个转换为浮点数:

return '%.1f' % float(b)/1000 + 'KB'

甚至只是

return '%.1f' % b/1000.0 + 'KB'

这是一个紧凑的版本,可以将B(字节)转换为任何更高阶的MB,GB,而无需在python中使用大量if...else。我使用逐位来处理这个问题。此外,如果您触发函数中的参数return_output为 True,它还允许返回浮点输出:

import math
def bytes_conversion(number, return_float=False):
def _conversion(number, return_float=False):
length_number = int(math.log10(number))
if return_float:
length_number = int(math.log10(number))
return length_number // 3, '%.2f' % (int(number)/(1 << (length_number//3) *10))
return length_number // 3, int(number) >> (length_number//3) * 10
unit_dict = {
0: "B",  1: "kB",
2: "MB", 3: "GB",
4: "TB", 5: "PB",
6: "EB"
}
if return_float:
num_length, number = _conversion(number, return_float=return_float)
else:
num_length, number = _conversion(number)
return "%s %s" % (number, unit_dict[num_length])
#Example usage:
#print(bytes_conversion(491266116, return_float=True))

这只是我在StackOverflow中的一些帖子。如果我有任何错误或违规行为,请告诉我。

我的观点中改进了@whereisalext答案,有一个更通用的函数,不需要在将添加更多单位后添加更多语句:

AVAILABLE_UNITS = ['bytes', 'KB', 'MB', 'GB', 'TB']
def get_amount_and_unit(byte_amount):
for index, unit in enumerate(AVAILABLE_UNITS):
lower_threshold = 0 if index == 0 else 1024 ** (index - 1)
upper_threshold = 1024 ** index
if lower_threshold <= byte_amount < upper_threshold:
if lower_threshold == 0:
return byte_amount, unit
else:
return byte_amount / lower_threshold, AVAILABLE_UNITS[index - 1]
# Default to the maximum
max_index = len(AVAILABLE_UNITS) - 1
return byte_amount / (1024 ** max_index), AVAILABLE_UNITS[max_index]

请注意,这@whereisalext算法略有不同:

  • 这将返回一个元组,其中包含第一个索引处的转换量和第二个索引处的单位
  • 这不会尝试区分单字节和多字节(因此 1 字节是此方法的输出)

我认为这是一个简短而简洁的内容。这个想法是基于我多年前写的一些图形缩放代码。代码片段round(log2(size)*4)/40在这里发挥了魔力,以 2**10 的幂计算边界。"正确"的实现是:trunc(log2(size)/10,但是当大小接近新边界时,您会得到奇怪的行为。例如,datasize(2**20-1)将返回(1024.00,'KiB')。通过使用round并缩放log2结果,您可以在接近新边界时获得很好的切入。

from math import log2
def datasize(size):
"""
Calculate the size of a code in B/KB/MB.../
Return a tuple of (value, unit)
"""
assert size>0, "Size must be a positive number"
units = ("B", "KiB", "MiB", "GiB", "TiB", "PiB",  "EiB", "ZiB", "YiB") 
scaling = round(log2(size)*4)//40
scaling = min(len(units)-1, scaling)
return  size/(2**(10*scaling)), units[scaling]
for size in [2**10-1, 2**10-10, 2**10-100, 2**20-10000, 2**20-2**18, 2**20, 2**82-2**72, 2**80-2**76]:
print(size, "bytes= %.3f %s" % datasize(size))
1023 bytes= 0.999 KiB
1014 bytes= 0.990 KiB
924 bytes= 924.000 B
1038576 bytes= 0.990 MiB
786432 bytes= 768.000 KiB
1048576 bytes= 1.000 MiB
4830980911975647053611008 bytes= 3.996 YiB
1133367955888714851287040 bytes= 0.938 YiB

让我补充一下我的,其中没有变量在循环中更新或类似的容易出错的行为。实现的逻辑很简单。它只在Python 3中测试过。

def format_bytes(size: int) -> str:
power_labels = {40: "TB", 30: "GB", 20: "MB", 10: "KB"}
for power, label in power_labels.items():
if size >= 2 ** power:
approx_size = size // 2 ** power
return f"{approx_size} {label}"
return f"{size} bytes"

例如,在 KB/MB 边界对其进行了测试:

1024
  • *1024-1 返回"1023 KB">
  • 1024
  • *1024 返回"1 MB">
  • 1024*1024+1 返回"1 MB">

如果您想要浮点数而不是舍入整数,您可以轻松更改approx_size

此功能已存在于matplotlib中。

>>> from matplotlib.ticker import EngFormatter
>>> fmt = EngFormatter('B')
>>> fmt(123456)
'123.456 kB'

在除法之前做 float(b),例如做float(b)/1000而不是float(b/1000),因为b和 1000 都是整数,b/1000仍然是没有小数部分的整数。

这是将字节转换为千,兆,太。

#From bytes to kilo, mega, tera
def  get_(size):
#2**10 = 1024
power = 2**10
n = 1
Dic_powerN = {1:'kilobytes', 2:'megabytes', 3:'gigabytes', 4:'Terabytes'}
if size <= power**2 :
size /=  power
return size, Dic_powerN[n]
else: 
while size   >  power :
n  += 1
size /=  power**n
return size, Dic_powerN[n]

没有小数位的输出:

>>> format_file_size(12345678)
'11 MiB, 792 KiB, 334 bytes'
format_file_size(
def format_file_size(fsize):
result = []
units = {s: u for s, u in zip(reversed([2 ** n for n in range(0, 40, 10)]), ['GiB', 'MiB', 'KiB', 'bytes'])}
for s, u in units.items():
t = fsize // s
if t > 0:
result.append('{} {}'.format(t, u))
fsize = fsize % s
return ', '.join(result) or '0 bytes'

我知道这里已经有很多答案和解释,但我尝试了这种基于类的方法,它对我非常有效。它可能看起来很大,但只要看看我如何使用属性和方法

class StorageUnits:
b, Kb, Kib, Mb, Mib, Gb, Gib, Tb, Tib, Pb, Pib, Eb, Eib, Zb, Zib, Yb, Yib, B, KB, KiB, MB, MiB, GB, GiB, TB,
TiB, PB, PiB, EB, EiB, ZB, ZiB, YB, YiB = [0]*34

class DigitalStorageConverter:
def __init__(self):
self.storage = StorageUnits()
self.bit_conversion_value_table = {
'b': 1, 'Kb': 1000, 'Mb': 1000**2, 'Gb': 1000**3, 'Tb': 1000**4, 'Pb': 1000**5, 'Eb': 1000**6,
'Zb': 1000**7, 'Yb': 1000**8, 'Kib': 1024, 'Mib': 1024**2, 'Gib': 1024**3, 'Tib': 1024**4, 'Pib': 1024**5,
'Eib': 1024**6, 'Zib': 1024**7, 'Yib': 1024**8,
'B': 8, 'KB': 8*1000, 'MB': 8*(1000**2), 'GB': 8*(1000**3), 'TB': 8*(1000**4), 'PB': 8*(1000**5),
'EB': 8*(1000**6), 'ZB': 8*(1000**7), 'YB': 8*(1000**8), 'KiB': 8*1024, 'MiB': 8*(1024**2),
'GiB': 8*(1024**3), 'TiB': 8*(1024**4), 'PiB': 8*(1024**5), 'EiB': 8*(1024**6), 'ZiB': 8*(1024**7),
'YiB': 8*(1024**8)
}
"Values of all the units in bits"
self.name_conversion_table = {
'bit': 'b', 'kilobit': 'Kb', 'megabit': 'Mb', 'gigabit': 'Gb', 'terabit': 'Tb', 'petabit': 'Pb',
'exabit': 'Eb', 'zettabit': 'Zb', 'yottabit': 'Yb', 'kibibit': 'Kib', 'mebibit': 'Mib', 'Gibibit': 'Gib',
'tebibit': 'Tib', 'pebibit': 'Pb', 'exbibit': 'Eib', 'zebibit': 'Zib', 'yobibit': 'Yib',
'byte': 'B', 'kilobyte': 'KB', 'megabyte': 'MB', 'gigabyte': 'GB', 'terabyte': 'TB', 'petabyte': 'PB',
'exabyte': 'EB', 'zettabyte': 'ZB', 'yottabyte': 'YB', 'kibibyte': 'KiB', 'mebibyte': 'MiB',
'gibibyte': 'GiB', 'tebibyte': 'TiB', 'pebibyte': 'PiB', 'exbibyte': 'EiB', 'zebibyte': 'ZiB',
'yobibyte': 'YiB'
}
self.storage_units = [u for u in list(StorageUnits.__dict__.keys()) if not u.startswith('__')]
def get_conversion(self, value: float, from_type: str) -> StorageUnits:
if from_type in list(self.name_conversion_table.values()):
from_type_bit_value = self.bit_conversion_value_table[from_type]
elif from_type in list(self.name_conversion_table.keys()):
from_type = self.name_conversion_table[from_type]
from_type_bit_value = self.bit_conversion_value_table[from_type]
else:
raise KeyError(f'Invalid storage unit type "{from_type}"')
value = value * from_type_bit_value
for i in self.storage_units:
self.storage.__setattr__(i, value / self.bit_conversion_value_table[i])
return self.storage

if __name__ == '__main__':
c = DigitalStorageConverter()
s = c.get_conversion(5000, 'KiB')
print(s.KB, s.MB, s.TB)   # , ..., ..., etc till whatever you may want

如果数字太大,该程序将以指数形式为您提供答案。

注意如果发现存储值的名称不正确,请更正

def resize(size: int | float, from_: str = "KB", to_: str = "B"):
sizes = ("PB", "TB", "GB", "MB", "KB", "B")
unit = sizes.index(to_.upper()) - sizes.index(from_.upper())
return size // (1024 ** abs(unit)) if unit < 0 else size ** (1024 * abs(unit))

最新更新