Cython的意外输出和返回值

时间:2013-04-12 23:38:19

标签: python python-2.7 cython

首先,我在Python 2.7.4中使用Cython 0.18。我遇到了一个相当奇怪的错误,我不知道为什么。这是玩具代码:

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错误,还是我在这里错过了一些微不足道的东西?

1 个答案:

答案 0 :(得分:4)

问题在于所有这样的情况:

temp = seq1.lower()
seq1 = temp

temp = seq2.lower()

正如你在问题中指出的那样,你需要做这个舞蹈而不仅仅是seq1 = seq1.lower() - 是因为Caveats when using a Python string in a C context

但你正在做的事情是不正确的,它足以让Cython进入思考它是正确的,并编译垃圾。

让我们一步一步地走过去:

temp = seq1.lower()

这会从str中创建seq1,调用其lower(),并将结果存储在temp中。

seq1 = temp

这使seq1成为指向strtemp对象的内部缓冲区的指针。正如文档具体说的那样:

  

然后,您有责任在必要时保留参考资料。

temp = seq2.lower()

此yadda-yadda-yaddas,将结果存储在temp中。结果,它释放了temp的旧值。这是str唯一的参考。因此,GC可以自由地收集它,并立即这样做。这意味着seq1现在指向已释放对象的内部缓冲区。

前两次,你显然很幸运,并且缓冲区不会被重用。但最终,在while循环中,它失败了,缓冲区被重用,最后你得到一个指向其他字符串缓冲区的指针。


那么,你是如何解决这个问题的?

嗯,只要需要,你就可以保留所有这些中间参考。

但实际上,为什么您还需要seq1seq2char*值?你没有从中获得任何性能上的好处。事实上,你正在从中获得额外的性能成本。每次使用seq1作为str时,它都会从该缓冲区中创建一个新的str对象(并复制缓冲区),即使您已经拥有了一个非常好的对象如果你没有欺骗过Cython,那么我只是保留了。

因此,最简单的解决方法是将第一行替换为:

cpdef unsigned int func(char *sequence1, char *sequence2, bool case_sensitive=True):
    seq1, seq2 = str(sequence1), str(sequence2)

(你真的不需要那里的str调用;事实上你没有cdef这些变量应该足够了。但我认为这使得意图更清晰。)