int()
必须考虑比必须转换更多的可能类型float()
。当您将单个对象传递给int()
并且它还不是整数时,会测试各种内容:
- 如果已经是整数,直接使用
- 如果对象实现了
__int__
方法,调用它并使用结果
- 如果对象是 的 C 派生子类
int
,则进入并将结构中的 C 整数值转换为int()
对象。
- 如果对象实现了
__trunc__
方法,调用它并使用结果
- 如果对象是字符串,则将其转换为基数设置为 10 的整数。
当您传入基本参数时,这些测试都不会执行,然后代码会直接跳转到将字符串转换为 int,并使用选定的基数。那是因为没有其他可接受的类型,而不是在给定基数的情况下。
结果,当你传入一个基数时,突然从一个字符串创建一个整数要快得多:
$ 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()
,进行的第一个测试是查看参数是否是字符串对象(而不是子类),此时它正在被解析。无需测试其他类型。
所以调用比orint('1')
进行了更多的测试。在这些测试中,测试 1、2 和 3 非常快。它们只是指针检查。但是第四个测试使用的是 C 等价的,相对昂贵。这必须测试实例和字符串的完整 MRO,并且没有缓存,最后它会引发一个,格式化一个没有人会看到的错误消息。所有在这里都毫无用处的工作。int('1', 10)
float('1')
getattr(obj, '__trunc__')
AttributeError()
在 Python 3 中,该getattr()
调用已被替换为更快的代码。这是因为在 Python 3 中,不需要考虑旧式类,因此可以直接在实例的类型(类, 的结果type(instance)
)上查找属性,并且跨 MRO 的类属性查找缓存在这点。不需要创建任何异常。
float()
对象实现__int__
方法,这就是为什么int(float('1'))
更快;您从未__trunc__
在步骤 4 中进行属性测试,因为步骤 2 产生了结果。
如果您想查看 C 代码,对于 Python 2,请先查看int_new()
方法。解析参数后,代码本质上是这样做的:
if (base == -909) // no base argument given, the default is -909
return PyNumber_Int(x); // parse an integer from x, an arbitrary type.
if (PyString_Check(x)) {
// do some error handling; there is a base, so parse the string with the base
return PyInt_FromString(string, NULL, base);
}
无基本情况调用PyNumber_Int()
函数,它执行以下操作:
if (PyInt_CheckExact(o)) {
// 1. it's an integer already
// ...
}
m = o->ob_type->tp_as_number;
if (m && m->nb_int) { /* This should include subclasses of int */
// 2. it has an __int__ method, return the result
// ...
}
if (PyInt_Check(o)) { /* An int subclass without nb_int */
// 3. it's an int subclass, extract the value
// ...
}
trunc_func = PyObject_GetAttr(o, trunc_name);
if (trunc_func) {
// 4. it has a __trunc__ method, call it and process the result
// ...
}
if (PyString_Check(o))
// 5. it's a string, lets parse!
return int_from_string(PyString_AS_STRING(o),
PyString_GET_SIZE(o));
whereint_from_string()
本质上是 的包装器PyInt_FromString(string, length, 10)
,因此以 10 为基数解析字符串。
在 Python 3 中,intobject
被删除,只留下longobject
,在 Python 端重命名为int()
。同样,unicode
已取代str
. 所以现在我们看一下long_new()
,对字符串的测试是用PyUnicode_Check()
代替的PyString_Check()
:
if (obase == NULL)
return PyNumber_Long(x);
// bounds checks on the obase argument, storing a conversion in base
if (PyUnicode_Check(x))
return PyLong_FromUnicodeObject(x, (int)base);
因此,当没有设置基数时,我们需要查看PyNumber_Long()
,它执行:
if (PyLong_CheckExact(o)) {
// 1. it's an integer already
// ...
}
m = o->ob_type->tp_as_number;
if (m && m->nb_int) { /* This should include subclasses of int */
// 2. it has an __int__ method
// ...
}
trunc_func = _PyObject_LookupSpecial(o, &PyId___trunc__);
if (trunc_func) {
// 3. it has a __trunc__ method
// ...
}
if (PyUnicode_Check(o))
// 5. it's a string
return PyLong_FromUnicodeObject(o, 10);
注意_PyObject_LookupSpecial()
调用,这是特殊的方法查找实现;它最终使用_PyType_Lookup()
,它使用缓存;因为没有任何str.__trunc__
方法可以让缓存在第一次 MRO 扫描后永远返回 null。此方法也从不引发异常,它只返回请求的方法或 null。
处理字符串的方式float()
在 Python 2 和 3 之间没有变化,因此您只需要查看Python 2float_new()
函数,它对于字符串来说非常简单:
// test for subclass and retrieve the single x argument
/* If it's a string, but not a string subclass, use
PyFloat_FromString. */
if (PyString_CheckExact(x))
return PyFloat_FromString(x, NULL);
return PyNumber_Float(x);
所以对于字符串对象,我们直接跳转到解析,否则用于PyNumber_Float()
查找实际float
对象,或带有__float__
方法的东西,或字符串子类。
这确实揭示了一个可能的优化:如果int()
要在所有其他类型测试之前先进行测试,它将与字符串PyString_CheckExact()
一样快。排除具有or方法的字符串子类,因此是一个很好的第一个测试。float()
PyString_CheckExact()
__int__
__trunc__
为了解决其他将其归咎于基本解析的答案(因此查找a 0b
、或前缀0o
,不区分大小写),带有单个字符串参数的默认调用确实会查找 base,base 被硬编码为 10。传递是错误的在这种情况下,在带有前缀的字符串中:0
0x
int()
>>> 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