Python 将contextlib与cython一起使用

Python 将contextlib与cython一起使用,python,opengl,cython,Python,Opengl,Cython,作为将游戏引擎代码转换为cython的一部分,我正在移植顶点缓冲区对象(Vbo)类。我使用这个Vbo类将3D模型数据发送到GPU。代码(vbo.pyx)当前如下所示: cimport gl from enum import Enum import contextlib class VboTarget(Enum): ARRAY = gl.GL_ARRAY_BUFFER INDEX = gl.GL_ELEMENT_ARRAY_BUFFER cdef class Vbo:

作为将游戏引擎代码转换为cython的一部分,我正在移植顶点缓冲区对象(Vbo)类。我使用这个Vbo类将3D模型数据发送到GPU。代码(
vbo.pyx
)当前如下所示:

cimport gl
from enum import Enum
import contextlib

class VboTarget(Enum):
    ARRAY = gl.GL_ARRAY_BUFFER
    INDEX = gl.GL_ELEMENT_ARRAY_BUFFER

cdef class Vbo:
    cdef readonly gl.GLuint id_
    cdef readonly double[:] data
    cdef readonly int target

    def __init__(self, data=None, target=VboTarget.ARRAY):
        gl.glewInit()
        gl.glGenBuffers(1, &self.id_)
        self.target = target.value
        if data is not None:
            self.data = data

    @contextlib.contextmanager
    def bind(self):
        gl.glBindBuffer(self.target, self.id_)
        try:
            yield
        finally:
            gl.glBindBuffer(self.target, 0)

    def set_data(self, new_data):
        self.data = new_data

    def update(self):#perform gpu update
        with self.bind():
            gl.glBufferData(self.target, self.data.nbytes, &self.data[0], gl.GL_DYNAMIC_DRAW)
我想使用
contextlib
,因为这样可以确保到GPU的缓冲区绑定和解除绑定将干净地自动发生。cython代码编译无误;但是,当我将此cython模块导入python代码时,会收到以下错误消息:

Traceback (most recent call last):
  File "main.py", line 2, in <module>
    import vbo
  File "vbo.pyx", line 21, in init vbo (vbo.c:15766)
    @contextlib.contextmanager
  File "C:\Python27\lib\contextlib.py", line 82, in contextmanager
    @wraps(func)
  File "C:\Python27\lib\functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'method_descriptor' object has no attribute '__module__'

在尝试了简化版本的代码后,它似乎对我来说仍然有效(Cython 0.25.1,Python 3.6.1):

我不认为您的更复杂示例的任何更改实际上会影响这一点,但我没有
gl.pxd
,因此很难进行测试。也许值得确保你的Cython版本是最新的(如果你还没有的话)

编辑:我认为重要的区别可能是Python2.7与Python3.6。Python3.6和Python2.7。因此,我不认为这是Cython行为的改变,因此可能不是真正的bug


如评论中所述,您可以使用带有
\uuuuuu enter\uuuuuu
\uuuu exit\uuuuuu
的非
cdef类来获得相同的行为:

cdef class Vbo:

    def __init__(self):
        pass

    def bind(self):
        class C:
            def __enter__(self2):
                # note that I can access "self" from the enclosing function
                # provided I rename the parameter passed to __enter__
                self.do_something() # gl.BindBuffer(self.target, self.id_) for you

            def __exit__(self2, exc_type, exc_val, exc_tb):
                print("Done") # gl.glBindBuffer(self.target, 0)
        return C()

    def do_something(self):
        print("something")


总之,我无法重现您的问题,但这里有一个替代方案…

在尝试了简化版的代码后,它看起来对我来说仍然有效(Cython 0.25.1,Python 3.6.1):

我不认为您的更复杂示例的任何更改实际上会影响这一点,但我没有
gl.pxd
,因此很难进行测试。也许值得确保你的Cython版本是最新的(如果你还没有的话)

编辑:我认为重要的区别可能是Python2.7与Python3.6。Python3.6和Python2.7。因此,我不认为这是Cython行为的改变,因此可能不是真正的bug


如评论中所述,您可以使用带有
\uuuuuu enter\uuuuuu
\uuuu exit\uuuuuu
的非
cdef类来获得相同的行为:

cdef class Vbo:

    def __init__(self):
        pass

    def bind(self):
        class C:
            def __enter__(self2):
                # note that I can access "self" from the enclosing function
                # provided I rename the parameter passed to __enter__
                self.do_something() # gl.BindBuffer(self.target, self.id_) for you

            def __exit__(self2, exc_type, exc_val, exc_tb):
                print("Done") # gl.glBindBuffer(self.target, 0)
        return C()

    def do_something(self):
        print("something")


总之,我无法重现您的问题,但这里有一个替代方案…

对于最新的Cython master(Cython版本0.26b0),这看起来实际上不是问题。只要在源文件顶部应用
binding=True
提示,@DavidW的答案中描述的contextlib代码的简化版本就可以完美地工作。可以找到有关Cython问题的讨论。

对于最新的Cython master(Cython版本0.26b0),现在看来这实际上不是问题。只要在源文件顶部应用
binding=True
提示,@DavidW的答案中描述的contextlib代码的简化版本就可以完美地工作。可以找到关于Cython问题的讨论。

如果在Cython中使用普通类而不是
cdef
类,那么
contextlib
几乎肯定是兼容的。我还不确定它是否可以与
cdef
类一起工作-它看起来很复杂@DavidW如果我使用普通类,那么我就不能在代码中使用任何
cdef
扩展类型,对吗?Vbos是我在使用python时遇到的性能瓶颈之一,因此我肯定希望键入尽可能多的变量。我可以使用
contextlib
来代替我的
cdef
类,而使用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
来解决这个问题,但是用户会用my uVBO:
调用类似
的东西,而不是用my uVBO.bind()
调用
,这并不理想。是的-一个普通类没有类型化属性。我认为您可以从
bind()
返回一个带有
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,但我还是不清楚。我更新了问题,以显示我使用
\uuuuu enter\uuuu
\uuu exit\uuuu
的备选版本。
bind
函数是什么样子的?如果在Cython中使用普通类而不是
cdef
类,那么
contextlib
几乎肯定是兼容的。我还不确定它是否可以与
cdef
类一起工作-它看起来很复杂@DavidW如果我使用普通类,那么我就不能在代码中使用任何
cdef
扩展类型,对吗?Vbos是我在使用python时遇到的性能瓶颈之一,因此我肯定希望键入尽可能多的变量。我可以使用
contextlib
来代替我的
cdef
类,而使用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
来解决这个问题,但是用户会用my uVBO:
调用类似
的东西,而不是用my uVBO.bind()
调用
,这并不理想。是的-一个普通类没有类型化属性。我认为您可以从
bind()
返回一个带有
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,但我还是不清楚。我更新了问题,以显示我使用
\uuuuu enter\uuuu
\uuu exit\uuuu
的备选版本。
bind
函数是什么样子的?非常感谢<代码>cython--version
显示我使用的是0.25.2,尽管我使用的是python 2.7.12。有趣的是,你的第一个样本给出了和我问题中相同的错误。但是,您的第二个解决方案对我的
gl*
函数非常有效!我不会想到重命名selfcdef class Vbo: def __init__(self): pass def bind(self): class C: def __enter__(self2): # note that I can access "self" from the enclosing function # provided I rename the parameter passed to __enter__ self.do_something() # gl.BindBuffer(self.target, self.id_) for you def __exit__(self2, exc_type, exc_val, exc_tb): print("Done") # gl.glBindBuffer(self.target, 0) return C() def do_something(self): print("something")