2017-06-24 1 views
0

Dans le cadre de la transition du code de mon moteur de jeu vers Cython, je porte ma classe Vertex Buffer Object (Vbo). J'utilise cette classe Vbo pour envoyer des données de modèle 3D au GPU. Le code (vbo.pyx) ressemble actuellement à ceci:Utilisation de contextlib avec cython

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) 

Je voudrais utiliser contextlib comme il ferait en sorte que le tampon de liaison et déliaison au GPU se produirait proprement et automatiquement. Le code de Cython compile sans erreur; Cependant, quand j'importer ce module cython dans mon code python, je reçois le message d'erreur suivant:

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__' 

Je ne suis pas vraiment sûr de savoir comment interpréter ce message. Puis-je utiliser des décorateurs contextlib avec un cdef class et si oui, comment? Est-ce que contextlib est même compatible avec Cython?


Mise à jour: Voici une version alternative en utilisant __enter__ et __exit__ à la place:

cimport gl 
from enum import Enum 
from cpython cimport array 
import array 

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

cdef class Vbo: 
    cdef readonly gl.GLuint id_ 
    cdef readonly float[:] 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 

    def set_data(self, float[:] new_data): 
     self.data = new_data 

    def update(self):#perform gpu update 
     with self: 
      gl.glBufferData(self.target, self.data.nbytes, &self.data[0], gl.GL_STATIC_DRAW) 

    def __enter__(self): 
     gl.glBindBuffer(self.target, self.id_) 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     gl.glBindBuffer(self.target, 0) 
+0

Si vous utilisez une classe normale en Cython au lieu d'une classe 'cdef', alors' contextlib' est presque certainement compatible. Je ne sais pas encore si ça peut fonctionner avec une classe 'cdef' - ça a l'air compliqué! – DavidW

+0

@DavidW Si j'utilise une classe normale à la place, alors je ne serais pas capable d'utiliser n'importe quel type d'extension 'cdef' dans mon code, non? Les Vbos étaient l'un des goulets d'étranglement de performance que j'utilisais juste en python, donc je veux définitivement taper autant de variables que possible. Je peux travailler en utilisant 'contextlib' en utilisant' __enter__' et '__exit__' à la place avec ma classe' cdef', mais ensuite l'utilisateur appelle quelque chose comme 'avec my_vbo:' plutôt qu'avec 'my_vbo.bind()', qui n'est pas idéal. – CodeSurgeon

+1

Oui - une classe normale n'a pas d'attributs typés. Je pense que vous pouvez renvoyer une classe normale avec les attributs '__enter__' et' __exit__' de 'bind()' à la place, ce qui conserverait l'interface que vous voulez et la classe 'cdef' (et nécessite simplement d'écrire un peu plus de code que d'utiliser contextlib – DavidW

Répondre

2

Après avoir essayé une version simplifiée de votre code, il semble fonctionner pour moi est (Cython 0.25.1, Python 3.6.1):

import contextlib 

cdef class Vbo: 

    def __init__(self): 
     pass 

    @contextlib.contextmanager 
    def bind(self): 
     self.do_something() 
     try: 
      yield 
     finally: 
      print("Finally") 

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

Je ne pense pas que l'un des changements pour votre exemple plus compliqué devrait affecter réellement, mais je ne pas avoir le gl.pxd donc c'est très difficile à tester. Il peut être utile de vous assurer que votre version de Cython est à jour (si vous ne l'avez pas déjà fait) ...

Éditer: Je pense que la différence importante pourrait être Python 2.7 vs Python 3.6. Python 3.6 has an AttributeError catch block tandis que Python 2.7 doesn't catch the error. Donc je ne pense pas que ce soit un changement dans le comportement de Cython et donc ce n'est probablement pas un bug.


Comme indiqué dans les commentaires, vous pouvez utiliser un non cdefclass avec __enter__ et __exit__ pour obtenir le même comportement:

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") 

Donc, en résumé - Je ne peux pas reproduire votre problème, mais voici une alternative ...

+0

Merci beaucoup! 'cython --version' a montré que j'utilisais 0.25.2, bien que j'utilise python 2.7.12 avec. Votre premier échantillon donne la même erreur que dans ma question, assez intéressant. Votre deuxième solution, cependant, fonctionne parfaitement avec mes fonctions 'gl *'! Je n'aurais pas pensé à renommer 'self' en' self2'. – CodeSurgeon

+1

Le nom 'self' est seulement une convention en Python donc vous pouvez toujours le changer si nécessaire. Il pourrait être utile de soumettre un rapport de bogue à https://github.com/cython/cython/issues/ pour le problème 'contextlib' car il semble que ce soit une régression inutile. – DavidW

+0

Ça éclaircit ça! J'ai maintenant soumis un rapport de bogue, donc j'espère que cela sera résolu. – CodeSurgeon

1

Il semble que ce soit actuellement un non-problème avec le dernier maître Cython (Version Cython 0.26b0). La version simplifiée du code contextlib décrit dans la réponse de @ DavidW fonctionne parfaitement tant que l'indicateur binding=True est appliqué en haut de votre fichier source. La discussion de ce problème Cython peut être trouvée here.