73 votes

Comment créer dynamiquement un type dérivé dans l’API Python C

Supposons que nous avons le type Noddy tel que défini dans le tutoriel sur l'écriture de C modules d'extension pour Python. Maintenant, nous voulons créer un type dérivé, en écrasant seulement l' __new__() méthode de Noddy.

Actuellement, je utiliser l'approche suivante (vérification des erreurs supprimées pour des raisons de lisibilité):

PyTypeObject *BrownNoddyType =
    (PyTypeObject *)PyType_Type.tp_alloc(&PyType_Type, 0);
BrownNoddyType->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
BrownNoddyType->tp_name = "noddy.BrownNoddy";
BrownNoddyType->tp_doc = "BrownNoddy objects";
BrownNoddyType->tp_base = &NoddyType;
BrownNoddyType->tp_new = BrownNoddy_new;
PyType_Ready(BrownNoddyType);

Cela fonctionne, mais je ne suis pas sûr si c'est La bonne Façon De le Faire. Je me serais attendu à ce que j'ai à régler l' Py_TPFLAGS_HEAPTYPE drapeau, trop, parce que j'alloue dynamiquement le type d'objet sur le tas, mais cela conduit à une erreur dans l'interpréteur.

J'ai aussi pensé à appeler explicitement type() l'aide PyObject_Call() ou similaire, mais j'ai écarté l'idée. J'aurais besoin d'envelopper la fonction BrownNoddy_new() dans une fonction Python objet et de créer un dictionnaire de mappage __new__ de cette fonction de l'objet, qui semble idiot.

Quelle est la meilleure façon d'aller à ce sujet? Mon approche correcte? Est-il une fonction d'interface j'ai raté?

Mise à jour

Il y a deux fils sur un sujet connexe sur le python-dev mailing list (1) (2). À partir de ces fils et de quelques expériences, je en déduire que je ne devrais pas réglé Py_TPFLAGS_HEAPTYPE moins que le type est attribué par un appel à l' type(). Il existe différentes recommandations dans ces fils si il est préférable d'allouer le type manuellement ou à l'appel d' type(). Je serais heureux avec ce dernier, si seulement je savais ce que de la manière recommandée pour envelopper la fonction C qui est censé aller dans l' tp_new logement. Pour les méthodes ordinaires de cette étape serait facile, je pourrais simplement utiliser PyDescr_NewMethod() pour obtenir un adapté objet wrapper. Je ne sais pas comment créer un tel objet wrapper pour ma __new__() méthode, bien que, peut-être que j'ai besoin de les sans papiers de la fonction PyCFunction_New() pour créer un tel objet wrapper.

2voto

synthesizerpatel Points 9762

Je m'excuse à l'avance si cette réponse est terrible, mais vous pouvez trouver une mise en œuvre de cette idée dans PythonQt, en particulier je pense que les fichiers suivants peuvent être utiles:

Ce fragment de PythonQtClassWrapper_init sauts hors de moi d'être un peu intéressant:

static int PythonQtClassWrapper_init(PythonQtClassWrapper* self, PyObject* args, PyObject* kwds)
{
  // call the default type init
  if (PyType_Type.tp_init((PyObject *)self, args, kwds) < 0) {
    return -1;
  }

  // if we have no CPP class information, try our base class
  if (!self->classInfo()) {
    PyTypeObject*  superType = ((PyTypeObject *)self)->tp_base;

    if (!superType || (superType->ob_type != &PythonQtClassWrapper_Type)) {
      PyErr_Format(PyExc_TypeError, "type %s is not derived from PythonQtClassWrapper", ((PyTypeObject*)self)->tp_name);
      return -1;
    }

    // take the class info from the superType
    self->_classInfo = ((PythonQtClassWrapper*)superType)->classInfo();
  }

  return 0;
}

Il est intéressant de noter que PythonQt ne utiliser un wrapper de générateur, de sorte qu'il n'est pas exactement en ligne avec ce que vous demandez, mais personnellement, je pense essayer de déjouer la vtable n'est pas la plus optimale de conception. Fondamentalement, il ya beaucoup de différents wrapper C++ générateurs pour Python et les gens les utilisent pour une bonne raison: ils sont documentés, il y a des exemples flottant autour dans les résultats de recherche et sur un débordement de pile. Si l'on part rouler une solution pour cela que personne n'a vu avant, il sera beaucoup plus difficile pour eux de débogage en cas de problèmes. Même si c'est closed-source, le gars à côté qui a pour le maintenir sera grattant la tête, et vous aurez à expliquer à chaque nouvelle personne qui vient le long.

Une fois que vous obtenez un générateur de code de travail, tout ce que vous devez faire est de maintenir le sous-jacent de code C++, vous n'avez pas à mettre à jour ou modifier votre code d'extension à la main. (Qui n'est probablement pas trop loin de la tentation solution vous êtes allé avec)

La solution proposée est un exemple de briser le type de sécurité que le nouvellement introduit PyCapsule fournit un peu plus de protection contre (lorsqu'il est utilisé selon les instructions).

Ainsi, alors que sa possible qu'il pourrait ne pas être le meilleur choix à long terme pour mettre en œuvre dérivée/sous-classes de cette façon, mais plutôt de placer le code et laissez la vtable de faire ce qu'il fait le mieux, et lorsque la nouvelle de guy a des questions, vous pouvez juste le point de lui à la documentation pour quelle solution convient le mieux.

C'est juste mon avis. :D

1voto

Demolishun Points 1036

Une façon d'essayer de comprendre comment procéder consiste à en créer une version à l'aide de SWIG. Voyez ce que cela produit et voyez si cela correspond ou est fait différemment. D'après ce que je peux dire aux personnes qui ont écrit SWIG, ils comprennent parfaitement l'extension de Python. Je ne peux pas faire de mal à voir comment ils font les choses en tout cas. Cela peut vous aider à comprendre ce problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X