Python 中的小陷阱
欢迎来到 2019 🎈,新年第一天我们来聊下,Python 中那些看似平常却容易掉进去的陷阱。
陷阱很多,所以本文会长期更新的。
1 float 转 str 类型
最近在写开源项目 cn2an(它是一个快速转化「中文数字」和「阿拉伯数字」的工具包)时,
碰到了这样一个问题,单元测试中的self.assertEqual(str(0.00005), "0.00005")
居然出错了,难道是 str(0.00005) 和 “0.00005” 不相等?
我怀着诡异的心情分别打印了这两个参数。
1 | str(0.00005) |
原来是浮点数的小数点后,零的数量大于等于 4 时,python 会自动把它转化成科学计数表示,这样导致了我使用 str() 函数进行转换时,不能得到想要的结果。
我又测试了一些其他情况,发现当小数点后的零的数量小于 4 时,不会有这个问题。
1 | str(0.0005) |
那如何解决它呢?我先是网上检索了一波,发现给出的方案并不能 work。
1 | import numpy as np |
而且这个方案还要引入一个叫numpy
的第三方包,这对完全不依赖 numpy 中任何方法的代码来说,代价太大了!
所以不管能不能 work,我都不会采用这个方案的。
既然找不到办法就只能自己动手解决了。我尝试直接把科学计数法的还原回去,发现可以成功转化。下面是代码👇:
1 | def convert_number_to_string(number_data): |
搞定!如果你有更好的方法,欢迎发邮件和我交流。
2 float 类型有效精度
不知道你有没有碰到过,计算结果与期望不一致的情况,比如下面这两种:
1 | "{:.30f}".format(1/3) |
是不是看起来有点奇怪,似乎前面的数字还对,后面的就出错了。这是因为 python 的 float 类型的绝对有效精度只有 15 位。
NOTE: 下面是选读内容,可跳过。
为什么只有 15 位绝对有效精度呢?
这个就要说浮点数在机器中的表示方式了。python 的 float 类型相当于其他语言的 double 类型,在 64 位的机器中占 8 个字节,可以用 64 位二进制描述。
它的组成如下:
float 类型(64位) = 符号(1位) + 指数(11位)+ 尾数(52位)
- 符号:表示浮点数的正负;
- 指数:表示指数的有效数字,可以界定出浮点数的取值范围;
- 尾数:表示数字的有效数字,可以界定出浮点数的有效精度。
你可以很容易的看出,二进制尾数的所能表示的十进制数字的上限,就是浮点数的有效精度上限。
1 | 2**52 |
通过计算得出,尾数能够表示的最大十进制数字的长度为 16 位,这意味着最多能有 16 位有效精度,但绝对能保证的有效精度只有 15 位。
在正式动手解决这个问题之前,我先仔细想了一下。
首先这种问题应该不会只有我碰到,另外也隐隐感觉到,这种问题官方应该会给出方案的。于是便找到了decimal
这个库,能够完美解决这个问题。
Decimal numbers can be represented exactly.
Unlike hardware based binary floating point, the decimal module has a user alterable precision (defaulting to 28 places) which can be as large as needed for a given problem.
decimal 可以准确表示十进制数字,并且用户可以随意更改的精度大小。
1 | from decimal import Decimal |
3 隐藏在 bool 函数中的细节
在 python 的 bool 函数有一个特性,任何字符串都会被转化成 True
。
1 | bool("False") |
这会带来什么问题呢?在使用命令行解析参数模块argparse
时就会出现意想不到错误。
看下面这样一个例子:
1 | # test.py |
接着我传递一个False
参数做下测试。
1 | python test.py --is_debug False |
而程序解析到的参数居然是True
!
如何解决呢?只需要自定义一个 bool 函数就可以了。
(如果你其他地方有用到默认的 bool 函数,这里最好换个名字,比如叫 new_bool )
1 | # test.py |
好了,我们再来做一下测试。
1 | python test.py --is_debug False |
4 strip() 在 Windows 中的用法
通常情况下,我们想去掉一串文字末尾的换行符,如文件中读取多行文本。
1 | with open("test.txt", "r") as f: |
在 Mac 和 Ubuntu 下这样写是没问题的,它们的换行符都是\n
。
但 Windows 中的换行符是 \r\n
!所以恰当的写法应该是 line.strip()
,因为 strip() 方法的默认功能是删除”\r”、”\t”、”\n”和” “。