Просмотр исходного кода

ENH: ctkAbstractPythonManager: pythonAttributes can instantiate class and return instance members

This function now allows to return all the attributes of an object,
even if it's a callable object: it instantiates it and returns instance members.

Moreover, it goes through all pythonAttributeName and iteratively can
access every attributes in one line.

For example, we can access to bar_instance_member
(according to the prerequisite file pythonAttributes-test.py), doing:
d.foo_class().instantiate_bar().[TAB]

Notes:

(1) An attribute of main named '_ctkAbstractPythonManager_autocomplete_tmp'
is created temporary during the research of auocompletion but immediately deleted

(2) Add a function dir_object returning the attributes of a PyObject into a StringList

(3) Can't yet instantiate a class with argument
Mayeul Chassagnard лет назад: 8
Родитель
Сommit
814c3975ab

+ 104 - 32
Libs/Scripting/Python/Core/ctkAbstractPythonManager.cpp

@@ -338,6 +338,43 @@ void ctkAbstractPythonManager::setInitializationFunction(void (*initFunction)())
   d->InitFunction = initFunction;
 }
 
+//-----------------------------------------------------------------------------
+QStringList ctkAbstractPythonManager::dir_object(PyObject* object,
+                                                 bool appendParenthesis)
+{
+  QStringList results;
+  if (!object)
+    {
+    return results;
+    }
+  PyObject* keys = PyObject_Dir(object);
+  if (keys)
+    {
+    PyObject* key;
+    PyObject* value;
+    int nKeys = PyList_Size(keys);
+    for (int i = 0; i < nKeys; ++i)
+      {
+      key = PyList_GetItem(keys, i);
+      value = PyObject_GetAttr(object, key);
+      if (!value)
+        {
+        continue;
+        }
+      QString key_str(PyString_AsString(key));
+      // Append "()" if the associated object is a function
+      if (appendParenthesis && PyCallable_Check(value))
+        {
+        key_str.append("()");
+        }
+      results << key_str;
+      Py_DECREF(value);
+      }
+    Py_DECREF(keys);
+    }
+  return results;
+}
+
 //----------------------------------------------------------------------------
 QStringList ctkAbstractPythonManager::pythonAttributes(const QString& pythonVariableName,
                                                        const QString& module,
@@ -351,6 +388,7 @@ QStringList ctkAbstractPythonManager::pythonAttributes(const QString& pythonVari
   PyObject* object = ctkAbstractPythonManager::pythonModule(precedingModule);
   PyObject* prevObject = 0;
   QStringList moduleList = module.split(".", QString::SkipEmptyParts);
+
   foreach(const QString& module, moduleList)
     {
     object = PyDict_GetItemString(dict, module.toLatin1().data());
@@ -375,57 +413,91 @@ QStringList ctkAbstractPythonManager::pythonAttributes(const QString& pythonVari
 //    }
 //  Py_INCREF(object);
 
+  PyObject* main_object = object; // save the modue object (usually __main__ or __main__.__builtins__)
+  QString instantiated_class_name = "_ctkAbstractPythonManager_autocomplete_tmp";
+  QStringList results; // the list of attributes to return
+  QString line_code="";
+
   if (!pythonVariableName.isEmpty())
     {
     QStringList tmpNames = pythonVariableName.split('.');
     for (int i = 0; i < tmpNames.size() && object; ++i)
       {
+      // fill the line step by step
+      // For example: pythonVariableName = d.foo_class().instantiate_bar().
+      // line_code will be filled first by 'd.' and then, line_code = 'd.foo_class().', etc
+      line_code.append(tmpNames[i]);
+      line_code.append(".");
+
       QByteArray tmpName = tmpNames.at(i).toLatin1();
-      PyObject* prevObj = object;
-      if (PyDict_Check(object))
+      if (tmpName.contains("()")) // TODO: Make it work for arguments
         {
-        object = PyDict_GetItemString(object, tmpName.data());
-        Py_XINCREF(object);
+        tmpNames[i].remove("()");
+        tmpName = tmpNames.at(i).toLatin1();
+
+        // Attempt to instantiate the associated python class
+        PyObject* classToInstantiate;
+        if (PyDict_Check(dict))
+          classToInstantiate = PyDict_GetItemString(dict, tmpName.data());
+        else
+          classToInstantiate = PyObject_GetAttrString(object, tmpName.data());
+
+        if (classToInstantiate)
+          {
+          QString code = " = ";
+          code.prepend(instantiated_class_name);
+          line_code.remove(line_code.size()-1,1); // remove the last char which is a dot
+          code.append(line_code);
+          // create a temporary attribute which will instantiate the class
+          // For example: code = '_ctkAbstractPythonManager_autocomplete_tmp = d.foo_class()'
+          PyRun_SimpleString(code.toLatin1().data());
+          line_code.append('.'); // add the point again in case we need to continue to fill line_code
+          object = PyObject_GetAttrString(main_object,instantiated_class_name.toLatin1().data());
+
+          dict = object;
+          results = ctkAbstractPythonManager::dir_object(object,appendParenthesis);
+          }
         }
       else
         {
-        object = PyObject_GetAttrString(object, tmpName.data());
+        PyObject* prevObj = object;
+        if (PyDict_Check(object))
+          {
+          object = PyDict_GetItemString(object, tmpName.data());
+          Py_XINCREF(object);
+          }
+        else
+          {
+          object = PyObject_GetAttrString(object, tmpName.data());
+          dict = object;
+          }
+        Py_DECREF(prevObj);
+
+        if (object)
+          {
+          results = ctkAbstractPythonManager::dir_object(object,appendParenthesis);
+          }
         }
-      Py_DECREF(prevObj);
       }
     PyErr_Clear();
     }
+  // By default if pythonVariable is empty, return the attributes of the module
+  else
+    {
+    results = ctkAbstractPythonManager::dir_object(object,appendParenthesis);
+    }
 
-  QStringList results;
   if (object)
     {
-    PyObject* keys = PyObject_Dir(object);
-    if (keys)
-      {
-      PyObject* key;
-      PyObject* value;
-      int nKeys = PyList_Size(keys);
-      for (int i = 0; i < nKeys; ++i)
-        {
-        key = PyList_GetItem(keys, i);
-        value = PyObject_GetAttr(object, key);
-        if (!value)
-          {
-          continue;
-          }
-        QString key_str(PyString_AsString(key));
-        // Append "()" if the associated object is a function
-        if (appendParenthesis && PyCallable_Check(value))
-          {
-          key_str.append("()");
-          }
-        results << key_str;
-        Py_DECREF(value);
-        }
-      Py_DECREF(keys);
-      }
     Py_DECREF(object);
     }
+
+  // remove the temporary attribute (created to instantiate a class) from the module object
+  if (PyObject_HasAttrString(main_object,instantiated_class_name.toLatin1().data()))
+    {
+    PyObject_DelAttrString(main_object,instantiated_class_name.toLatin1().data());
+    }
+
   return results;
 }
 

+ 8 - 1
Libs/Scripting/Python/Core/ctkAbstractPythonManager.h

@@ -104,7 +104,14 @@ public:
   /// \sa preInitialization executeInitializationScripts
   void setInitializationFunction(void (*initFunction)());
 
-  /// Given a python variable name, lookup its attributes and return them in a string list.
+  /// Given a python object, lookup its attributes and return them in a string list.
+  /// If the argument \c appendParenthesis is set to True, "()" will be appended to attributes
+  /// being Python callable.
+  static QStringList dir_object(PyObject* object,
+                                bool appendParenthesis = false);
+
+  /// Given a python variable name, if it can be called, try to call the method or instantiate the class,
+  /// lookup its attributes and return them in a string list.
   /// By default the attributes are looked up from \c __main__.
   /// If the argument \c appendParenthesis is set to True, "()" will be appended to attributes
   /// being Python callable.

+ 21 - 5
Libs/Scripting/Python/Widgets/ctkPythonConsole.cpp

@@ -275,13 +275,29 @@ void ctkPythonConsoleCompleter::updateCompletionModel(const QString& completion)
     return;
     }
 
+  bool appendParenthesis = true;
+
+  int numeberOfParenthesisClosed = 0;
   // Search backward through the string for usable characters
   QString textToComplete;
   for (int i = completion.length()-1; i >= 0; --i)
     {
     QChar c = completion.at(i);
-    if (c.isLetterOrNumber() || c == '.' || c == '_')
+    if (c.isLetterOrNumber() || c == '.' || c == '_' || c == '(' || c == ')' || c.isSymbol() || c.isPunct() || c.isSpace())
       {
+      if (c == '(')
+        {
+        if (numeberOfParenthesisClosed>0)
+          {
+          numeberOfParenthesisClosed--;
+          }
+        else
+          break; // stop to prepend
+        }
+      if (c == ')')
+        {
+        numeberOfParenthesisClosed++;
+        }
       textToComplete.prepend(c);
       }
     else
@@ -304,9 +320,10 @@ void ctkPythonConsoleCompleter::updateCompletionModel(const QString& completion)
   QStringList attrs;
   if (!lookup.isEmpty() || !compareText.isEmpty())
     {
-    bool appendParenthesis = true;
-    attrs = this->PythonManager.pythonAttributes(lookup, QLatin1String("__main__"), appendParenthesis);
-    attrs << this->PythonManager.pythonAttributes(lookup, QLatin1String("__main__.__builtins__"),
+    QString module = "__main__";
+    attrs = this->PythonManager.pythonAttributes(lookup, module.toLatin1(), appendParenthesis);
+    module = "__main__.__builtins__";
+    attrs << this->PythonManager.pythonAttributes(lookup, module.toLatin1(),
                                                   appendParenthesis);
     attrs.removeDuplicates();
     }
@@ -319,7 +336,6 @@ void ctkPythonConsoleCompleter::updateCompletionModel(const QString& completion)
     this->setCaseSensitivity(Qt::CaseInsensitive);
     this->setCompletionPrefix(compareText.toLower());
 
-    //qDebug() << "completion" << completion;
     // If a dot as been entered and if an item of possible
     // choices matches one of the preference list, it will be selected.
     QModelIndex preferredIndex = this->completionModel()->index(0, 0);