Python Cython的意外输出和返回值
首先,我将Cython0.18与Python2.7.4结合使用。我遇到了一个相当奇怪的错误,我不知道为什么。以下是玩具代码:Python Cython的意外输出和返回值,python,python-2.7,cython,Python,Python 2.7,Cython,首先,我将Cython0.18与Python2.7.4结合使用。我遇到了一个相当奇怪的错误,我不知道为什么。以下是玩具代码: from cpython cimport bool cpdef unsigned int func(char *seq1, char *seq2, bool case_sensitive=True): print 'seq1', seq1, len(seq1) print 'seq2', seq2, len(seq2) p
from cpython cimport bool
cpdef unsigned int func(char *seq1, char *seq2, bool case_sensitive=True):
print 'seq1', seq1, len(seq1)
print 'seq2', seq2, len(seq2)
print
#take care of case sensitivity
if not case_sensitive:
#this is kinda hacky, but I've gotta assign the lowercased string to a Python object before assigning it back to char *
#see http://docs.cython.org/src/userguide/language_basics.html#caveats-when-using-a-python-string-in-a-c-context
temp = seq1.lower()
seq1 = temp
temp = seq2.lower()
seq2 = temp
print 'seq1', seq1, len(seq1)
print 'seq2', seq2, len(seq2)
print
#trim common characters at the beginning of the words
while len(seq1) > 0 and len(seq2) > 0 and seq1[0] == seq2[0]:
temp = seq1[1:]
seq1 = temp
temp = seq2[1:]
seq2 = temp
print 'seq1', seq1, len(seq1)
print 'seq2', seq2, len(seq2)
print
#handle degenerate cases
if not seq1:
return len(seq2)
if not seq2:
return len(seq1)
下面是一个示例调用:
>>> from func import func
>>> print func('TUESDAYs', 'tuesday', False)
现在,我希望看到的是:
seq1 TUESDAYs 8
seq2 tuesday 7
seq1 tuesdays 8
seq2 tuesday 7
seq1 s 1
seq2 0
1
但我实际上看到的是:
seq1 TUESDAYs 8
seq2 tuesday 7
seq1 tuesdays 8
seq2 tuesday 7
seq1 stdout 6
seq2 tuesday 7
0
这到底是怎么回事?首先,
stdout
为什么要输出?为什么我没有得到我应该得到的输出?这是一个Cython bug,还是我只是遗漏了一些琐碎的东西?在所有情况下,问题都是这样的:
temp = seq1.lower()
seq1 = temp
temp = seq2.lower()
你需要跳这种舞,而不是像你在问题中指出的那样只跳seq1=seq1.lower()
但是你所做的是不正确的,这足以诱使Cython认为它是正确的,并编译垃圾
让我们一行一行地看一下:
temp = seq1.lower()
这将从seq1
中创建一个str
,调用其lower()
,并将结果存储在temp
中
seq1 = temp
这使得seq1
成为指向temp
中str
对象的内部缓冲区的指针。正如文件明确指出的:
因此,您有责任在必要时保持参考p
这个yadda-yadda-yadda是,并将结果存储在temp
中。因此,它释放了temp
的旧值。这是你唯一引用的str
。因此,GC可以自由地收集它,并且可以立即收集。这意味着seq1
现在指向已释放对象的内部缓冲区
前两次,你显然很幸运,缓冲区没有得到重用。但是最终,在while
循环中,它失败了,缓冲区被重用,最后您得到了指向其他字符串缓冲区的指针
那么,你如何解决这个问题呢
好吧,你可以保留所有这些中间引用,只要它们需要
但实际上,为什么您需要seq1
和seq2
成为char*
值呢?您不会从中获得任何性能优势。事实上,您从中获得了额外的性能成本。每次您将seq1
用作str
,它都会从该缓冲区中创建一个新的str
对象(并复制缓冲区),即使您已经有了一个非常好的对象,如果您没有欺骗Cython,也可以保留它
因此,最简单的修复方法是将第一行替换为:
cpdef unsigned int func(char *sequence1, char *sequence2, bool case_sensitive=True):
seq1, seq2 = str(sequence1), str(sequence2)
(你其实不需要那里的str
调用;事实上你没有cdef
变量就足够了。但我认为这让意图更清楚了。)我注意到你从一开始就将seq1
和seq2
用作str
对象,我打赌你是否添加了显式转换(将参数更改为s1
和s2
,然后在顶部写入str1,str2=str(s1),str(s2)
),问题就消失了。(至少值得测试。)这是与char*
和Pythonstr
对象交互所必需的解决方法。我在代码中有一条注释,描述了我为什么这样做。你将seq1和seq2转换为str的想法很有趣。我会试试。马上回来……是的,我找到了这一部分并删除了注释。但这正是第二次会议的灵感所在d注释。在内部使用char*并没有带来任何性能好处。事实上,我认为您正在使它变慢。对于每个中间步骤,您创建一个Pythonstr
对象,执行str
操作,存储str
结果,然后使seq1
指向该str
的缓冲区。然后你用seq1
构建了一个新的str
,如果你没有欺骗Cython的话,你可以直接引用旧的。事实上……这也是问题的根源。正如你链接的文档所说,“那么你有责任尽可能长时间地保留参考p。”让我写一个答案。使用Apple Python 2.7.2和Cython 0.17.1,我得到了完全相同的输出,至少在第一次运行时是这样。当然,我期望得到相同的垃圾,但不是完全相同的垃圾。我开始查看生成的C代码,试图找出确切的原因,但是……这不值得。使用三种不同的Python 3.x在不同版本中,我始终将b'tuday'
作为字符串(或者更确切地说,作为字节
,因此它打印为seq1b“b'tuday''10
),而不是stdout
…如图所示。
cpdef unsigned int func(char *sequence1, char *sequence2, bool case_sensitive=True):
seq1, seq2 = str(sequence1), str(sequence2)