Menu

Python docstrings

Help
2012-12-06
2013-04-06
  • Melven Röhrig-Zöllner

    Hello,

    I would like to add python docstrings to my classes and methods that are defined in C++,
    so the documentation is accessible through the __doc__ attribute.
    Is this possible somehow?

    Thanks in advance,
    Melven

     
  • Florian Link

    Florian Link - 2012-12-07

    As far as I know this is not possible, because the __doc__ string is part of the Python class type and I think I remember that I did not manage to overload it (because the underlying code requires the __doc__ string to be present when the wrapper class is registered).

    But you are welcome to investigate this, it might be possible to pass the doc string into PythonQt::registerClass and to get it into the derived PythonQtClassWrapper struct which is created for each registered wrapper class.

     
  • Florian Link

    Florian Link - 2012-12-07

    Look for

    PythonQtPrivate::createNewPythonQtClassWrapper

    and try to set result.type.tp_doc to your docstring. Make sure that the string you pass does not get deleted, Python expects a const char* and does not do a copy.

     
  • Melven Röhrig-Zöllner

    Thanks, that should probably help!

    I did a short test with a hard-coded string, but it didn't show up in python.
    So I think I need to understand whats happening there and have a short look at the python documentation first…

     
  • Melven Röhrig-Zöllner

    I have played a bit around with the docstrings and finally had some success:

    For the

    class.__doc__
    

    the

    tp_doc
    

    member didn't work for me, but it worked using the additionally supplied

    typeDict
    

    that already contains the

    __module__
    

    .

    For the docstring of class-methods (e.g. public slots), there was already a function that returned

    Py_None
    

    .

    In order to add doc-strings to a class a decorator-class must be used (e.g.

    PythonQt::self()->addDecorators(...)
    

    ).
    I think my solution is still improvable:
    * I call qt_metacall without setting/checking arguments correctly
    * the decorators have the form

    QByteArray doc_ClassName()
    

    for class documentation
     

    QByteArray doc_ClassName_MethodName()
    

    for method documentation.

    Here is a full diff (that can be applied with patch -p1 -i  <docString.patch>)

    diff -crB PythonQt2.1_Qt4.6.2.orig/examples/PyDecoratorsExample/example.py PythonQt2.1_Qt4.6.2/examples/PyDecoratorsExample/example.py
    *** PythonQt2.1_Qt4.6.2.orig/examples/PyDecoratorsExample/example.py    2012-12-24 18:38:33.000000000 +0100
    --- PythonQt2.1_Qt4.6.2/examples/PyDecoratorsExample/example.py 2012-12-24 18:42:13.000000000 +0100
    ***************
    *** 24,28 ****
    --- 24,34 ----
      # show slots available on yourCpp 
      print dir(yourCpp)
      
    + # show docstring of yourCpp
    + print yourCpp.__doc__
    + 
    + # show docstring of yourCpp.doSomething
    + print yourCpp.doSomething.__doc__
    + 
      # destructor will be called:
      yourCpp = None
    diff -crB PythonQt2.1_Qt4.6.2.orig/examples/PyDecoratorsExample/PyExampleDecorators.h PythonQt2.1_Qt4.6.2/examples/PyDecoratorsExample/PyExampleDecorators.h
    *** PythonQt2.1_Qt4.6.2.orig/examples/PyDecoratorsExample/PyExampleDecorators.h 2012-12-24 18:38:33.000000000 +0100
    --- PythonQt2.1_Qt4.6.2/examples/PyDecoratorsExample/PyExampleDecorators.h  2012-12-24 18:42:13.000000000 +0100
    ***************
    *** 89,94 ****
    --- 89,101 ----
      
        // add a method to your own CPP object
        int doSomething(YourCPPObject* obj, int arg1) { return obj->doSomething(arg1); }
    + 
    +   // add a docstring to your own CPP object
    +   QByteArray doc_YourCPPObject() {return "A C++ Class object";}
    + 
    +   // add a docstrint to YourCPPObject::doSomething
    +   QByteArray doc_YourCPPObject_doSomething() {return "this function does something";}
    + 
      };
      
      
    Nur in PythonQt2.1_Qt4.6.2: lib.
    diff -crB PythonQt2.1_Qt4.6.2.orig/src/PythonQtClassInfo.cpp PythonQt2.1_Qt4.6.2/src/PythonQtClassInfo.cpp
    *** PythonQt2.1_Qt4.6.2.orig/src/PythonQtClassInfo.cpp  2012-12-24 18:38:33.000000000 +0100
    --- PythonQt2.1_Qt4.6.2/src/PythonQtClassInfo.cpp   2012-12-24 18:42:15.000000000 +0100
    ***************
    *** 581,586 ****
    --- 581,589 ----
        decorator();
        QString h;
        h += QString("--- ") + QString(className()) + QString(" ---\n");
    + 
    +   if (_doc != "" )
    +       h += "Description:\n" + _doc + "\n";
        
        if (_isQObject) {
          h += "Properties:\n";
    ***************
    *** 882,885 ****
        _enumValue = NULL;
        _property = prop;
        _enumWrapper = NULL;
    ! }
    \ Kein Zeilenumbruch am Dateiende.
    --- 885,888 ----
        _enumValue = NULL;
        _property = prop;
        _enumWrapper = NULL;
    ! }
    diff -crB PythonQt2.1_Qt4.6.2.orig/src/PythonQtClassInfo.h PythonQt2.1_Qt4.6.2/src/PythonQtClassInfo.h
    *** PythonQt2.1_Qt4.6.2.orig/src/PythonQtClassInfo.h    2012-12-24 18:38:33.000000000 +0100
    --- PythonQt2.1_Qt4.6.2/src/PythonQtClassInfo.h 2012-12-24 18:42:15.000000000 +0100
    ***************
    *** 187,192 ****
    --- 187,198 ----
        //! clear all members that where cached as "NotFound"
        void clearNotFoundCachedMembers();
      
    +   //! set the python docstring (__doc__) for this class
    +   void setDoc(const QByteArray& str) {_doc=str;}
    + 
    +   //! get the python docstring (__doc__) for this class
    +   const QByteArray& doc() const {return _doc;}
    + 
      private:
        void createEnumWrappers();
        void createEnumWrappers(const QMetaObject* meta);
    ***************
    *** 238,243 ****
    --- 244,251 ----
      
        bool                                 _isQObject;
        bool                                 _enumsCreated;
    + 
    +   QByteArray                           _doc;
        
      };
      
    diff -crB PythonQt2.1_Qt4.6.2.orig/src/PythonQt.cpp PythonQt2.1_Qt4.6.2/src/PythonQt.cpp
    *** PythonQt2.1_Qt4.6.2.orig/src/PythonQt.cpp   2012-12-24 19:01:18.000000000 +0100
    --- PythonQt2.1_Qt4.6.2/src/PythonQt.cpp    2012-12-24 19:01:49.000000000 +0100
    ***************
    *** 518,523 ****
    --- 518,528 ----
        PyObject* typeDict = PyDict_New();
        PyObject* moduleName = PyObject_GetAttrString(parentModule, "__name__");
        PyDict_SetItemString(typeDict, "__module__", moduleName);
    +   if (info->doc() != "") {
    +     PyObject* docStr = PyString_FromString(info->doc().data());
    +     PyDict_SetItemString(typeDict, "__doc__", docStr);
    +     Py_DECREF(docStr);
    +   }
      
        PyObject* args  = Py_BuildValue("OOO", className, baseClasses, typeDict);
      
    ***************
    *** 1183,1188 ****
    --- 1188,1217 ----
              PythonQtClassInfo* classInfo = lookupClassInfoAndCreateIfNotPresent(nameOfClass);
              PythonQtSlotInfo* newSlot = new PythonQtSlotInfo(NULL, m, i, o, PythonQtSlotInfo::ClassDecorator);
              classInfo->addDecoratorSlot(newSlot);
    +       } else if (qstrncmp(m.signature(), "doc_", 4)==0) {
    +         if ((decoTypes & DocstringDecorator) == 0) continue;
    +         QByteArray signature = m.signature();
    +         QByteArray nameOfClassAndMethod = signature.mid(4, signature.indexOf('(')-4);
    +         QList<QByteArray> nameOfClassAndMethodAsList = nameOfClassAndMethod.split('_');
    +         QByteArray nameOfClass = nameOfClassAndMethodAsList.at(0);
    +         PythonQtClassInfo* classInfo = lookupClassInfoAndCreateIfNotPresent(nameOfClass);
    +         // TODO: make shure that the function does not take arguments and returns a QByteArray
    +         QByteArray docString;
    +         void* argList[1] = {&docString};
    +         o->qt_metacall(QMetaObject::InvokeMetaMethod, i, argList);
    +         if( nameOfClassAndMethodAsList.size() == 1 ) // __doc__ of class itself
    +         {
    +           classInfo->setDoc(docString);
    +         }
    +         else                                         // __doc__ of class method (e.g. public slot)
    +         {
    +           QByteArray nameOfMethod = nameOfClassAndMethodAsList.at(1);
    +           PythonQtMemberInfo member = classInfo->member(nameOfMethod);
    +           // check if we really found a slot
    +           if( member._type == PythonQtMemberInfo::Slot || member._type == PythonQtMemberInfo::Signal ) {
    +             member._slot->setDoc(docString);
    +           }
    +         }
            } else {
              if ((decoTypes & InstanceDecorator) == 0) continue;
              const PythonQtMethodInfo* info = PythonQtMethodInfo::getCachedMethodInfo(m, NULL);
    diff -crB PythonQt2.1_Qt4.6.2.orig/src/PythonQt.h PythonQt2.1_Qt4.6.2/src/PythonQt.h
    *** PythonQt2.1_Qt4.6.2.orig/src/PythonQt.h 2012-12-24 18:38:33.000000000 +0100
    --- PythonQt2.1_Qt4.6.2/src/PythonQt.h  2012-12-24 18:42:15.000000000 +0100
    ***************
    *** 549,554 ****
    --- 549,555 ----
          ConstructorDecorator = 2,
          DestructorDecorator = 4,
          InstanceDecorator = 8,
    +     DocstringDecorator = 16,
          AllDecorators = 0xffff
        };
      
    diff -crB PythonQt2.1_Qt4.6.2.orig/src/PythonQtMethodInfo.h PythonQt2.1_Qt4.6.2/src/PythonQtMethodInfo.h
    *** PythonQt2.1_Qt4.6.2.orig/src/PythonQtMethodInfo.h   2012-12-24 18:38:33.000000000 +0100
    --- PythonQt2.1_Qt4.6.2/src/PythonQtMethodInfo.h    2012-12-24 19:02:37.000000000 +0100
    ***************
    *** 101,106 ****
    --- 101,112 ----
        //! add an alias for a typename, e.g. QObjectList and QList<QObject*>.
        static void addParameterTypeAlias(const QByteArray& alias, const QByteArray& name);
      
    +   //! set the docstirng
    +   void setDoc(const QByteArray& str) {_doc = str;}
    + 
    +   //! get the docstring
    +   const QByteArray& doc() const {return _doc; }
    + 
        protected:
        static void fillParameterInfo(ParameterInfo& type, const QByteArray& name, PythonQtClassInfo* classInfo);
      
    ***************
    *** 111,116 ****
    --- 117,125 ----
        static QHash<QByteArray, PythonQtMethodInfo*> _cachedSignatures;
      
        QList<ParameterInfo> _parameters;
    + 
    +   //! stores the docstring
    +   QByteArray  _doc;
      };
      
      //! stores information about a slot, including a next pointer to overloaded slots
    diff -crB PythonQt2.1_Qt4.6.2.orig/src/PythonQtSlot.cpp PythonQt2.1_Qt4.6.2/src/PythonQtSlot.cpp
    *** PythonQt2.1_Qt4.6.2.orig/src/PythonQtSlot.cpp   2012-12-24 18:38:33.000000000 +0100
    --- PythonQt2.1_Qt4.6.2/src/PythonQtSlot.cpp    2012-12-24 18:42:15.000000000 +0100
    ***************
    *** 405,412 ****
      }
      
      static PyObject *
    ! meth_get__doc__(PythonQtSlotFunctionObject * /*m*/, void * /*closure*/)
      {
        Py_INCREF(Py_None);
        return Py_None;
      }
    --- 405,414 ----
      }
      
      static PyObject *
    ! meth_get__doc__(PythonQtSlotFunctionObject *m, void * /*closure*/)
      {
    +   if( m->m_ml->doc() != "" )
    +     return PyString_FromString(m->m_ml->doc().data());
        Py_INCREF(Py_None);
        return Py_None;
      }
    
     
  • Melven Röhrig-Zöllner

    (I'm sorry for the bad indentation, there seems to be no preview-feature in this forum…)

    Another idea for the syntax of the decorator functions:
    For the class-documentation:

      QByteArray doc(ClassName*) {return "some string";} // but don't use the pointer, so it can be NULL
    

    For the class-method documentation:

      QByteArray doc_MethodName(ClassName*) {return "some other string";} // again don't use the pointer
    

    This would prevent problems with underscores in the class-/method-names.

    Additionally I don't know how my code works together with overloaded public slot methods…

     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.