我需要编写一些高性能的unicode处理逻辑,python2和python3的Unicode对象之间的差异很大。
我才刚刚开始弄清楚如何实现这一点,以下代码片段给了我麻烦:
from six.text_type import unicode
from cpython.version cimport PY_MAJOR_VERSION
cdef extern from "Python.h":
int PyUnicode_KIND ( object o )
def unicode_size ( unicode u ):
if PY_MAJOR_VERSION == 2:
return sizeof ( Py_UNICODE )
else:
return PyUnicode_KIND ( u )
此代码在python 2和3中执行并运行。但是,python2的编译器正在给我"警告C4013:' PyUnicode_KIND'不确定的;假设extern返回int"
通常情况下,我可以通过给c编译器一个extern声明函数来抑制这种警告,因为我知道如果我真的试图链接它,链接器就会发出一个发脾气。
但是,我无法弄清楚如何在cython中创建外部声明,以便编译器满意。
我对使用命令行参数关闭警告不感兴趣,我试图让编辑变得简单直接,而且其中一个"警告是错误"狂热分子。这个逻辑非常简单,可以放在一个pyx文件中。另外,流行的观点是生成的C代码应该在Python 2和Python 3中编译,所以我试图继续使用它。
为了让事情变得更加荒谬,当我查看生成的C代码时,会出现__Pyx_PyUnicode_KIND(),它完全符合我的要求,但如果我尝试从我的cython代码中调用它,那就说它没有&# 39; t存在。如果以上是不可能的,有没有办法可以访问这些可移植性宏?
也许我没有强调任何事情。显然过早优化是魔鬼,但到目前为止我的有限经验是,为了获得最大性能,我需要访问python对象后面的指针或安全包装器绝对会扼杀性能。还有另一种方法可以处理unicode输入并生成另一个unicode输出而无需使用特定于版本的C apis吗?
----------更新----------
感谢@ead,我能够开发出符合我所有标准的解决方案。对于其他想要在Cython中的缓冲区中累积UCS4字符然后在完成时将其转换为unicode对象的人,这就是我在.pyx文件中内联解决问题的方法:
cdef extern from *:
"""
// This is C code that will be passed through to xmlwalk.c as-is:
PyObject* PyUnicode_FromUCS4 ( Py_UCS4* s, Py_ssize_t size )
{
#if PY_VERSION_HEX >= 0x03030000
return PyUnicode_FromKindAndData ( PyUnicode_4BYTE_KIND, s, size );
#elif Py_UNICODE_SIZE == 4
return PyUnicode_FromUnicode ( s, size );
#elif Py_UNICODE_SIZE == 2
// WARNING: this version of the code rewrites s in-place as UTF-16.
// `s` no longer contains valid UCS4 code points upon return.
Py_UNICODE* dst = (Py_UNICODE*)s;
Py_ssize_t dst_size = 0;
Py_ssize_t i;
for ( i = 0; i < size; i++ )
{
Py_UCS4 c = s[i];
//printf ( "src[%i]=%i (0x%x)\\n", i, (int)c, (int)c );
if ( c < 0x10000 )
{
// assert ( c < 0xD800 || c > 0xDFFF ); // disabled for performance reasons
dst[dst_size++] = (Py_UNICODE)c;
//printf ( "dst[%i]=%i (0x%x)\\n", dst_size-1, (int)dst[dst_size-1], (int)dst[dst_size-1] );
}
else
{
dst[dst_size++] = 0xD800 | (c >> 10);
//printf ( "dst[%i]=%i (0x%x)\\n", dst_size-1, (int)dst[dst_size-1], (int)dst[dst_size-1] );
dst[dst_size++] = 0xDC00 | (c & 0x3FF);
//printf ( "dst[%i]=%i (0x%x)\\n", dst_size-1, (int)dst[dst_size-1], (int)dst[dst_size-1] );
}
}
return PyUnicode_FromUnicode ( dst, dst_size );
#else
assert(0); // could not determine correct unicode type
#endif
}
"""
PyObject* PyUnicode_FromUCS4 ( Py_UCS4* s, Py_ssize_t size )
答案 0 :(得分:0)
第一件事:你应该关心警告。
您必须知道,比较PY_MAJOR_VERSION == 2
是在运行时完成的,并且不是预处理程序指令,因此符号PyUnicode_KIND
(毕竟,在ANSI C中(C89,C90)不需要原型,编译器推断原型,因此扩展得到编译)也可能在Python2的扩展中找到它的方式。它在没有优化的构建中不会发生,因为优化器可以在编译期间看到PY_MAJOR_VERSION
的值并优化错误的分支,从而也可以引用PyUnicode_KIND
。 / p>
但是,如果您构建调试,而没有(-O0
)上的优化,则构建将失败。至少在Linux上 - 它将被构建 - 在共享对象中允许默认的未定义符号,但在导入期间它将失败,因为加载器将找不到符号PyUnicode_KIND
。不确定在Windows上会发生什么......
我不认为__Pyx_PyUnicode_KIND
应该在Cython中使用(有时候可能很方便) - 文档中没有提到__Pyx_XXXX
- 函数,所以它们可能并不是一个稳定的API,而只是实现细节。但是,您可能会滥用此功能来解析name collisions:
cdef extern from *:
#put "__Pyx_PyUnicode_KIND" into the C-code, whenever my_PyUnicode_KIND is used:
int my_PyUnicode_KIND "__Pyx_PyUnicode_KIND" (object u)
def unicode_size(u):
my_PyUnicode_KIND(u)
现在,每次使用my_PyUnicode_KIND
时,Cython都会将__Pyx_PyUnicode_KIND
放入C代码中。
但如上所述,__Pyx_XXXX
函数可能只是实现细节,因此不是一个稳定的API。更稳定的方法如下:
您实际尝试实现的是遵循使用预处理器的C代码:
#include <Python.h>
//you might want to use CYTHON_INLINE instead of inline
static inline int unicode_size(PyObject *o){
//actually PyUnicode_KIND is defined since CPython3.3:
#if PY_VERSION_HEX > 0x03030000 && defined(PyUnicode_KIND)
return PyUnicode_KIND(o);
#else
return sizeof(Py_UNICODE);
#endif
}
对于Python2,预处理器将选择正确的分支,符号PyUnicode_KIND
将永远不会出现在生成的目标文件中。
由于Cython的conditional statements工作方式略有不同(首先PY_MAJOR_VERSION
&amp; Co不是预定义的编译时名称,其次它不会转换为C预处理器,而只是正确的分支被翻译为C),这是实现目标的一种方式:
像往常一样将其导入Cython模块:
cdef export from "unicodesize.h":
int unicode_size(object o)
根据您的构建方式,您可能需要将正确的文件夹添加到设置文件中的包含路径。
替代方案是include C-code verbatim。