int()
需要考虑的转换类型比 float()
要多。当您将单个对象传递给 int()
且它不是整数时,会进行各种测试:
- 如果它已经是一个整数,则直接使用它
- 如果该对象实现了
__int__
方法, 则调用它并使用结果
- 如果该对象是继承自 C 的
int
子类,则到内部提取结构中的 C 整数值转换为 int()
对象。
- 如果该对象实现了
__trunc__
方法, 则调用它并使用结果
- 如果该对象是字符串,则将其转换为基数设置为 10 的整数。
当您传递基数参数时,这些测试都不会执行,代码直接跳转到使用所选基数将字符串转换为整数。这是因为没有其他可接受的类型,当有给定基数时。因此,当您传递基数时,从字符串创建整数会变得更快。
$ bin/python -m timeit "int('1')"
1000000 loops, best of 3: 0.469 usec per loop
$ bin/python -m timeit "int('1', 10)"
1000000 loops, best of 3: 0.277 usec per loop
$ bin/python -m timeit "float('1')"
1000000 loops, best of 3: 0.206 usec per loop
当你将一个字符串传递给
float()
时,首先要进行的测试是查看参数是否为字符串对象(而不是子类),此时它正在被解析。没有必要测试其他类型。
因此,
int('1')
调用比
int('1', 10)
或
float('1')
进行了更多的测试。这些测试中,测试1、2和3非常快;它们只是指针检查。但第四个测试使用了 C 中等效的
getattr(obj, '__trunc__')
,这是相对昂贵的。这必须测试实例和字符串的完整 MRO,并且没有缓存,最终会引发一个
AttributeError()
,格式化一个错误消息,没有人会看到这个消息。所有这些工作在这里都是无用的。
在Python 3中,
getattr()
调用已被替换为更快的代码。这是因为在Python 3中,不需要考虑旧式类,因此可以直接在实例的类型(类、
type(instance)
的结果)上查找属性,并且跨MRO的类属性查找此时已被缓存。不需要创建任何异常。
float()
对象实现了
__int__
方法,这就是为什么
int(float('1'))
更快的原因;在第4步中,你永远不会触发
__trunc__
属性测试,因为第2步已经产生了结果。
如果你想查看Python 2的C代码,请首先查看
int_new()
方法。解析参数后,代码基本上执行以下操作:
if (base == -909)
return PyNumber_Int(x);
if (PyString_Check(x)) {
return PyInt_FromString(string, NULL, base);
}
没有基础的情况下调用PyNumber_Int()
函数,该函数执行以下操作:
if (PyInt_CheckExact(o)) {
}
m = o->ob_type->tp_as_number;
if (m && m->nb_int) {
}
if (PyInt_Check(o)) {
}
trunc_func = PyObject_GetAttr(o, trunc_name);
if (trunc_func) {
}
if (PyString_Check(o))
return int_from_string(PyString_AS_STRING(o),
PyString_GET_SIZE(o));
在这里,int_from_string()
实际上是对 PyInt_FromString(string, length, 10)
的封装,所以使用基数 10 来解析字符串。
在 Python 3 中,intobject
被移除,只剩下被重命名为 int()
的 longobject
。同样地,unicode
已经替代了 str
。因此现在我们看到 long_new()
,并且使用 PyUnicode_Check()
而不是 PyString_Check()
来检测字符串。
if (obase == NULL)
return PyNumber_Long(x);
if (PyUnicode_Check(x))
return PyLong_FromUnicodeObject(x, (int)base);
因此,当没有设置基础时,我们需要查看{{link1:PyNumber_Long()
}},它会执行:
if (PyLong_CheckExact(o)) {
}
m = o->ob_type->tp_as_number;
if (m && m->nb_int) {
}
trunc_func = _PyObject_LookupSpecial(o, &PyId___trunc__);
if (trunc_func) {
}
if (PyUnicode_Check(o))
return PyLong_FromUnicodeObject(o, 10);
请注意
_PyObject_LookupSpecial()
调用,这是
特殊方法查找的实现;它最终使用
_PyType_Lookup()
,该函数使用缓存;由于没有
str.__trunc__
方法,该缓存将在第一次MRO扫描后永远返回空值。此方法也不会引发异常,它只会返回所请求的方法或空值。
float()
处理字符串的方式在Python 2和3之间没有变化,因此您只需要查看
Python 2 float_new()
函数,对于字符串来说非常简单:
if (PyString_CheckExact(x))
return PyFloat_FromString(x, NULL);
return PyNumber_Float(x);
对于字符串对象,我们直接转换为解析,否则使用
PyNumber_Float()
查找实际的
float
对象,或具有
__float__
方法的对象,或者字符串子类。
这揭示了一种可能的优化方式:如果int()
首先测试PyString_CheckExact()
是否符合所有其他类型测试,那么在处理字符串时它将与float()
一样快。 PyString_CheckExact()
排除了具有__int__
或__trunc__
方法的字符串子类,因此是一个很好的第一次测试。
为了回应其他答案将此归咎于基本解析(因此寻找
0b
、
0o
、
0
或
0x
前缀,不区分大小写),默认的
int()
调用使用单个字符串参数
确实会寻找一个基数,该基数硬编码为 10。在这种情况下传入带有前缀的字符串是错误的。
>>> int('0x1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '0x1'
只有在将第二个参数显式设置为
0
时,才会执行基本前缀解析:
>>> int('0x1', 0)
1
由于没有对
__trunc__
进行测试,因此
base=0
前缀解析情况的速度与显式设置
base
为任何其他支持的值一样快:
$ python2.7 -m timeit "int('1')"
1000000 loops, best of 3: 0.472 usec per loop
$ python2.7 -m timeit "int('1', 10)"
1000000 loops, best of 3: 0.268 usec per loop
$ python2.7 bin/python -m timeit "int('1', 0)"
1000000 loops, best of 3: 0.271 usec per loop
$ python2.7 bin/python -m timeit "int('0x1', 0)"
1000000 loops, best of 3: 0.261 usec per loop
jakob@devbox:~$ python -m timeit "int("1")" 10000000次循环,3次中的最佳结果:每个循环0.104微秒
jakob@devbox:~$ python -m timeit "float("1")" 10000000次循环,3次中的最佳结果:每个循环0.106微秒
- Jakob Bowyerfloatobject.c
和intobject.c
。寻找float_new
和int_new
函数。 - Martijn Pieters