在 Python2 的 C API 中,有
long PyInt_AS_LONG()
函数,可以从 Python2 long
快速创建 C int
。
是否有一种快速方法可以使用 Python3 C API 检查
int
(实际上是任意精度整数)是否适合 C long
?
可以调用
PyLong_AsLong()
,但这似乎做了不必要的工作。应该有一个函数来获取用于存储数字的内存块的大小,但我在文档中找不到任何内容。
这必须用C完成,所以我不能直接使用Python算术比较。虽然原则上我可以调用 PyObject_RichCompare(),但它看起来有点矫枉过正。
PyLong_AsLong代码可以在[GitHub]找到:python/cpython - (v3.10.13) cpython/Objects/longobject.c#453并且(正如@jasonharper在评论中指出的那样)使用PyLong_AsLongAndOverflow(其中就在它上面)。
我创建了一个测试程序。
_mod00.c:
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#define MOD_NAME "_mod00"
static PyObject* PyTestAsLongAndOverflow(PyObject *self, PyObject *iObj)
{
int of;
if (!PyLong_CheckExact(iObj)) {
Py_RETURN_FALSE;
}
PyLong_AsLongAndOverflow(iObj, &of);
if (of) {
Py_RETURN_FALSE;
}
Py_RETURN_TRUE;
}
static PyObject* PyTestAsLong(PyObject *self, PyObject *iObj)
{
if (!PyLong_CheckExact(iObj)) {
Py_RETURN_FALSE;
}
PyLong_AsLong(iObj);
if (PyErr_Occurred()) {
PyErr_Clear();
Py_RETURN_FALSE;
}
Py_RETURN_TRUE;
}
static PyObject* PyTestParseTuple(PyObject *self, PyObject *args)
{
long l = 0;
if (!PyArg_ParseTuple(args, "l", &l)) {
PyErr_Clear();
Py_RETURN_FALSE;
}
Py_RETURN_TRUE;
}
static PyMethodDef moduleMethods[] = {
{"test_as_long_and_overflow", (PyCFunction)PyTestAsLongAndOverflow, METH_O, NULL},
{"test_as_long", (PyCFunction)PyTestAsLong, METH_O, NULL},
{"test_parse_tuple", (PyCFunction)PyTestParseTuple, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}, // Sentinel
};
static struct PyModuleDef moduleDef = {
PyModuleDef_HEAD_INIT, MOD_NAME, NULL, -1, moduleMethods
};
PyMODINIT_FUNC PyInit__mod00(void)
{
return PyModule_Create(&moduleDef);
}
code00.py:
#!/usr/bin/env python
import sys
import timeit
import _mod00 as mod
FUNCS = (
mod.test_as_long_and_overflow,
mod.test_as_long,
mod.test_parse_tuple,
)
def test_acc():
print("Test accuracy:")
values = (
None,
False,
3.141593,
"",
-1,
0,
1,
sys.maxsize,
sys.maxsize + 1,
-sys.maxsize,
-sys.maxsize - 1,
-sys.maxsize - 2,
)
for value in values:
print(" ({:s}) {:}{:s}:".format(value.__class__.__name__, value, " (hex: {:016X})".format(value) if isinstance(value, int) else ""))
for func in FUNCS:
print(" {:s}: {:}".format(func.__name__, func(value)))
def test_perf(count=10000000):
print("\nTest performance:")
values = (
sys.maxsize,
sys.maxsize + 1,
)
for value in values:
print(" ({:s}) {:}{:s}:".format(value.__class__.__name__, value, " (hex: {:016X})".format(value) if isinstance(value, int) else ""))
for func in FUNCS:
print(" {:s}: {:.3f}s".format(func.__name__, timeit.timeit(lambda: func(value), number=count)))
def main(*argv):
test_acc()
test_perf()
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
输出:
[cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q076889790]> . ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
[064bit prompt]> ls
code00.py _mod00.c
[064bit prompt]>
[064bit prompt]> gcc -fPIC -I/usr/include/python3.10 -shared -o _mod00.so _mod00.c
[064bit prompt]> ls
code00.py _mod00.c _mod00.so
[064bit prompt]>
[064bit prompt]> python ./code00.py
Python 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0] 064bit on linux
Test accuracy:
(NoneType) None:
test_as_long_and_overflow: False
test_as_long: False
test_parse_tuple: False
(bool) False (hex: 0000000000000000):
test_as_long_and_overflow: False
test_as_long: False
test_parse_tuple: True
(float) 3.141593:
test_as_long_and_overflow: False
test_as_long: False
test_parse_tuple: False
(str) :
test_as_long_and_overflow: False
test_as_long: False
test_parse_tuple: False
(int) -1 (hex: -000000000000001):
test_as_long_and_overflow: True
test_as_long: True
test_parse_tuple: True
(int) 0 (hex: 0000000000000000):
test_as_long_and_overflow: True
test_as_long: True
test_parse_tuple: True
(int) 1 (hex: 0000000000000001):
test_as_long_and_overflow: True
test_as_long: True
test_parse_tuple: True
(int) 9223372036854775807 (hex: 7FFFFFFFFFFFFFFF):
test_as_long_and_overflow: True
test_as_long: True
test_parse_tuple: True
(int) 9223372036854775808 (hex: 8000000000000000):
test_as_long_and_overflow: False
test_as_long: False
test_parse_tuple: False
(int) -9223372036854775807 (hex: -7FFFFFFFFFFFFFFF):
test_as_long_and_overflow: True
test_as_long: True
test_parse_tuple: True
(int) -9223372036854775808 (hex: -8000000000000000):
test_as_long_and_overflow: True
test_as_long: True
test_parse_tuple: True
(int) -9223372036854775809 (hex: -8000000000000001):
test_as_long_and_overflow: False
test_as_long: False
test_parse_tuple: False
Test performance:
(int) 9223372036854775807 (hex: 7FFFFFFFFFFFFFFF):
test_as_long_and_overflow: 0.819s
test_as_long: 0.828s
test_parse_tuple: 1.140s
(int) 9223372036854775808 (hex: 8000000000000000):
test_as_long_and_overflow: 0.813s
test_as_long: 1.128s
test_parse_tuple: 1.587s
Done.
注释:
我还通过 PyArg_ParseTuple 测试了转换(但由于运行时在场景后面执行元组操作,所以速度较慢)
你是对的,设置例外会增加一些开销
值可能因平台/架构而异:[SO]:Python 中 C 类型整数的最大值和最小值(@CristiFati 的答案)