[PyKDE] wish list for sip.

Gerard Vermeulen gvermeul at labs.polycnrs-gre.fr
Mon Mar 19 09:14:03 GMT 2001


Phil,

I am still improving PyQwt (python bindings for Qwt) and wish the following
"improvements" for sip. The first two are related because I am a great fan of
Python doc strings.

Here, they are in decreasing order of difficulty (at least to me):

(1) I understand your motivation for "lazy" attributes. But quite often I do
dir(SOMETHING) to get a list of attribute names of SOMETHING. For sip
generated classes this does not work well. Could you force evaluation of
all SOMETHING's attribute names, when doing dir(SOMETHING)?

This goes beyond my current understanding of sip.

(2) Would it be possible to provide hooks for Python's documentation strings?
I understand that there are some nice tools extracting doc-strings in Python-2.1.
If wish (1) is implemented, I could do something like:

keys = dir(SOMETHING)
for key in keys:
    print keys.__doc__

I could always post-process the sip-generated sources to introduce doc-strings
afterwards, but I prefer that sip does it.

(3) Initially, I was quite unhappy with some of the translations of Qwt's C++
examples into Python, mainly due to the difference between Python's reference
semantics and C++'s copy semantics.
I came to the conclusion that keyword arguments are a natural solution
(heavily  used in Tkinter) to bypass the problems posed by the reference
semantics (inventing new names, if you do not want that objects change behind
your back).

It would be nice to write code like in CurveDemo1.py.

This is actually quite easy to implement, and sip-2.3-3mgv.patch contains a first
try. If the patched sip is invoked with the -k switch (for keyword arguments), it
generates Python code, which takes keyword arguments.
For the cost of 6 lines of Python code in the constructor of a sip generated Python
class, every method name of the class (in principle also of the inherited methods)
becomes the key of the keyword argument "key = value". value might be a tuple, this
allows to pass a variable number of arguments.

For instance:

curves(append(QwtCurve(setRange = (-0.5, 10.5, 0, -1.1, 1.1, 0)))

does the same as:

curve0 = QwtCurve()
curve0.setRange(-0.5, 10.5, 0, -1.1, 1.1, 0)
curves.append(curve0)

except that the next curves be called something like curve1, curve2, ...

Of course, this is only a toy implementation (but a nice toy), possible improvements:
- enable keyword arguments on a class by class basis (it makes more sense
for classes with many methods than for simple classes).
- error reporting: if the key is not an attribute, it is OK, but if the attribute is
not a method but something else it could be improved.
- an implementation at the C++ level instead of the Python level is probably more
efficient.

best regards -- Gerard Vermeulen
-------------- next part --------------
--- sip-2.3/sip/gencode.c.gv	Mon Jan 22 20:48:01 2001
+++ sip-2.3/sip/gencode.c	Sun Mar 18 14:29:56 2001
@@ -67,7 +67,7 @@
 
 static void generateDocumentation(sipSpec *,char *);
 static void generateMakefile(sipSpec *,char *,char *);
-static void generatePython(sipSpec *,char *);
+static void generatePython(sipSpec *,char *,int);
 static void generateModuleProxy(sipSpec *,FILE *);
 static void generateModuleProxyHeader(sipSpec *,char *);
 static void generatePackageHeader(sipSpec *,char *);
@@ -75,13 +75,13 @@
 static void generateVersionProgram(sipSpec *,char *,char *);
 static void generateClassCpp(classDef *,sipSpec *,char *,char *);
 static void generateClassVersCpp(classVersDef *,sipSpec *,FILE *);
-static void generatePythonClassWrappers(sipSpec *,FILE *);
-static void generatePythonClassVersWrapper(sipSpec *,pyClassVers *,pyClassVers *,FILE *);
+static void generatePythonClassWrappers(sipSpec *,FILE *,int);
+static void generatePythonClassVersWrapper(sipSpec *,pyClassVers *,pyClassVers *,FILE *,int);
 static int generatePythonPushVersion(sipSpec *,versionOrList *,int,FILE *);
 static void pushVersion(sipSpec *,versionAnded *,versionQual **,versionQual **,versionQual **);
 static void popVersion();
 static void generateCppVersionExpr(versionQual *,versionQual *,versionQual *,FILE *);
-static void generatePythonClassWrapper(sipSpec *,pyClassVers *,int,FILE *);
+static void generatePythonClassWrapper(sipSpec *,pyClassVers *,int,FILE *,int);
 static void generateClassFunctions(sipSpec *,classVersDef *,FILE *);
 static void generateComplexCode(sipSpec *,classVersDef *,FILE *);
 static void generateFunction(sipSpec *,memberDef *,overDef *,classVersDef *,classVersDef *,int,FILE *);
@@ -152,7 +152,7 @@
  * Generate the code from a specification.
  */
 
-void generateCode(sipSpec *pt,char *codeDir,stringList *makefiles,char *docFile,char *cppSuffix)
+void generateCode(sipSpec *pt,char *codeDir,stringList *makefiles,char *docFile,char *cppSuffix,int kwargs)
 {
 	/* Generate the documentation. */
 
@@ -175,7 +175,7 @@
 		generateVersionProgram(pt,codeDir,cppSuffix);
 		generatePackageHeader(pt,codeDir);
 		generateCpp(pt,codeDir,cppSuffix);
-		generatePython(pt,codeDir);
+		generatePython(pt,codeDir,kwargs);
 	}
 }
 
@@ -352,7 +352,7 @@
  * Generate the Python wrapper code.
  */
 
-static void generatePython(sipSpec *pt,char *codeDir)
+static void generatePython(sipSpec *pt,char *codeDir,int kwargs)
 {
 	char *pyfile, *mname = pt -> module -> name -> text;
 	FILE *fp;
@@ -415,10 +415,17 @@
 				,mod -> name -> text);
 	}
 
-	prcode(fp,
+	if (kwargs != 0)
+		prcode(fp,
+"import types\n"
 "\n"
 "lib%sc.sipInitModule()\n"
-		,mname);
+			,mname);
+	else
+		prcode(fp,
+"\n"
+"lib%sc.sipInitModule()\n"
+			,mname);
 
 	/* Generate the pre-Python code blocks. */
 
@@ -431,7 +438,7 @@
 
 	/* Generate the Python class objects for all versions. */
 
-	generatePythonClassWrappers(pt,fp);
+	generatePythonClassWrappers(pt,fp,kwargs);
 
 	/* Register the classes. */
 
@@ -613,7 +620,7 @@
  * Generate the Python class objects.
  */
 
-static void generatePythonClassWrappers(sipSpec *pt,FILE *fp)
+static void generatePythonClassWrappers(sipSpec *pt,FILE *fp,int kwargs)
 {
 	classDef *cd;
 	pyClassVers *list, *pcv;
@@ -671,7 +678,7 @@
 
 	for (pcv = list; pcv != NULL; pcv = pcv -> next)
 		if (pcv -> cd -> module == pt -> module)
-			generatePythonClassVersWrapper(pt,list,pcv,fp);
+			generatePythonClassVersWrapper(pt,list,pcv,fp,kwargs);
 }
 
 
@@ -741,7 +748,7 @@
  * Generate the Python class object for a particular version.
  */
 
-static void generatePythonClassVersWrapper(sipSpec *pt,pyClassVers *list,pyClassVers *pcv,FILE *fp)
+static void generatePythonClassVersWrapper(sipSpec *pt,pyClassVers *list,pyClassVers *pcv,FILE *fp,int kwargs)
 {
 	/* It may already have been done. */
 
@@ -787,7 +794,7 @@
 				/* Generate it if the versions overlap. */
 
 				if (pcv -> vol == NULL || pscv -> vol == NULL)
-					generatePythonClassVersWrapper(pt,list,pscv,fp);
+					generatePythonClassVersWrapper(pt,list,pscv,fp,kwargs);
 				else
 				{
 					versionOrList *v1;
@@ -799,7 +806,7 @@
 						for (v2 = pcv -> vol; v2 != NULL; v2 = v2 -> next)
 							if (versionsOverlap(&v1 -> va,&v2 -> va))
 							{
-								generatePythonClassVersWrapper(pt,list,pscv,fp);
+								generatePythonClassVersWrapper(pt,list,pscv,fp,kwargs);
 								break;
 							}
 
@@ -811,7 +818,7 @@
 		}
 
 		indent = generatePythonPushVersion(pt,pcv -> vol,0,fp);
-		generatePythonClassWrapper(pt,pcv,indent,fp);
+		generatePythonClassWrapper(pt,pcv,indent,fp,kwargs);
 
 		pcv -> generated = TRUE;
 	}
@@ -864,7 +871,7 @@
  * Generate the wrapper for a Python class.
  */
 
-static void generatePythonClassWrapper(sipSpec *pt,pyClassVers *pcv,int indent,FILE *fp)
+static void generatePythonClassWrapper(sipSpec *pt,pyClassVers *pcv,int indent,FILE *fp, int kwargs)
 {
 	char *mname = pt -> module -> name -> text;
 	char *cname = pcv -> cd -> name -> text;
@@ -898,11 +905,33 @@
 
 	/* Generate the standard methods. */
 
-	prcode(fp,
+	if (kwargs != 0)
+	{
+		prcode(fp,
+"%I	def __init__(self,*args,**kwargs):\n"
+"%I		lib%sc.sipCallCtor(%d,self,args)\n"
+			,indent
+			,indent,mname,pcv -> cd -> classnr);
+		prcode(fp,
+"%I		for key in kwargs.keys():\n"
+"%I			method = getattr(self, key)\n"
+"%I			if type(kwargs[key]) == types.TupleType:\n"
+"%I				apply(method,kwargs[key])\n"
+"%I			else:\n"
+"%I				method(kwargs[key])\n"
+			,indent
+			,indent
+			,indent
+			,indent
+			,indent
+			,indent);
+	}
+	else
+		prcode(fp,
 "%I	def __init__(self,*args):\n"
 "%I		lib%sc.sipCallCtor(%d,self,args)\n"
-		,indent
-		,indent,mname,pcv -> cd -> classnr);
+			,indent
+			,indent,mname,pcv -> cd -> classnr);
 
 	if (pcv -> supers == NULL)
 		prcode(fp,
--- sip-2.3/sip/main.c.gv	Sun Jan 21 12:33:09 2001
+++ sip-2.3/sip/main.c	Sun Mar 18 14:20:36 2001
@@ -26,6 +26,7 @@
 int main(int argc,char **argv)
 {
 	char *filename, *docFile, *codeDir, *cppSuffix, *cppMName;
+	int kwargs;
 	int arg;
 	FILE *file;
 	classList *cl;
@@ -41,12 +42,13 @@
 	docFile = NULL;
 	cppSuffix = ".cpp";
 	cppMName = NULL;
+	kwargs = 0;
 
 	/* Parse the command line. */
 
 	opterr = 0;
 
-	while ((arg = getopt(argc,argv,"hVm:c:d:I:s:p:")) != EOF)
+	while ((arg = getopt(argc,argv,"hVkm:c:d:I:s:p:")) != EOF)
 		switch (arg)
 		{
 		case 'c':
@@ -97,6 +99,12 @@
 			version();
 			break;
 
+		case 'k':
+			/* Generate Python code for keyword arguments */
+
+			kwargs = 1;
+			break;
+
 		default:
 			usage();
 		}
@@ -125,7 +133,7 @@
 
 	/* Generate code. */
 
-	generateCode(&spec,codeDir,makefiles,docFile,cppSuffix);
+	generateCode(&spec,codeDir,makefiles,docFile,cppSuffix,kwargs);
 
 	/* All done. */
 
@@ -202,10 +210,11 @@
 {
 	printf(
 "Usage:\n"
-"    %s [-h] [-V] [-c dir] [-d file] [-m file] [-I dir] [-s suffix] [-p module] [file]\n"
+"    %s [-h] [-V] [-k] [-c dir] [-d file] [-m file] [-I dir] [-s suffix] [-p module] [file]\n"
 "where:\n"
 "    -h         display this help message\n"
 "    -V         display the %s version number\n"
+"    -k         generate Python code for keyword arguments\n"
 "    -c dir     the name of the code directory [default not generated]\n"
 "    -d file    the name of the documentation file [default not generated]\n"
 "    -m file    the name of the Makefile [default none generated]\n"
--- sip-2.3/sip/sip.h.gv	Sun Jan 21 12:20:50 2001
+++ sip-2.3/sip/sip.h	Sun Mar 18 14:20:36 2001
@@ -657,7 +657,7 @@
 void parse(sipSpec *,FILE *,char *,char *);
 void parserEOF(char *,parserContext *);
 void transform(sipSpec *);
-void generateCode(sipSpec *,char *,stringList *,char *,char *);
+void generateCode(sipSpec *,char *,stringList *,char *,char *,int);
 void fatal(char *,...);
 void fatalVersion(sipSpec *,versionAnded *);
 void setInputFile(FILE *,char *,parserContext *);
-------------- next part --------------
#!/usr/bin/env python

# The Python version of qwt-0.3/examples/curvedemo1/curvdemo1.cpp

import sys

# The next two insert statements assure importation of qwt and qwtc from the
# local source tree. They are useful for debugging and testing.
sys.path.insert(0, '../qwt')
sys.path.insert(0, '../qwt/.libs')

from qt import *
from qwt import *
from Numeric import *

SIZE=27

class CurveDemo(QFrame):

    def __init__(self, *args):
        apply(QFrame.__init__, (self,) + args)

        self.setFrameStyle(QFrame.Box | QFrame.Raised)
        self.setLineWidth(2)
        self.setMidLineWidth(3)

        # define curve styles
        self.curves = []
        # curve 0
        self.curves.append(QwtCurve(
            setPen = QPen(Qt.darkGreen), setStyle = QwtCurve.Spline,
            setSymbol = QwtSymbol(QwtSymbol.Cross, QBrush(),
                                  QPen(Qt.black), QSize(5, 5))))
        # curve 1
        self.curves.append(QwtCurve(
            setPen = QPen(Qt.red), setStyle = QwtCurve.Sticks,
            setSymbol = QwtSymbol(QwtSymbol.Ellipse, QBrush(Qt.yellow),
                                  QPen(Qt.blue), QSize(5, 5))))
        # curve 2
        self.curves.append(QwtCurve(
            setPen = QPen(Qt.darkBlue), setStyle = QwtCurve.Lines))
        # curve 3
        self.curves.append(
            QwtCurve(setPen = QPen(Qt.darkCyan), setStyle = QwtCurve.Steps))
        # curve 4
        self.curves.append(QwtCurve(
            setStyle = QwtCurve.NoCurve,
            setSymbol = QwtSymbol(QwtSymbol.XCross, QBrush(),
                                  QPen(Qt.darkMagenta), QSize(5, 5))))

        # attach data, using Numeric
        self.x = arrayrange(0, 10.0, 10.0/SIZE)
        self.y = sin(self.x)*cos(2*self.x)
        for curve in self.curves:
            curve.setRawData(self.x, self.y)
            curve.setRange(-0.5, 10.5, 0, -1.1, 1.1, 0)


    def drawContents(self, painter):
        # draw curves
        r = self.contentsRect()
        dy = r.height()/len(self.curves)
        r.setHeight(dy)
        for curve in self.curves:
            curve.draw(painter, r)
            r.moveBy(0, dy)
        # draw titles
        r = self.contentsRect()
        r.setHeight(dy)
        painter.setFont(QFont('Helvetica', 8))
        painter.setPen(Qt.black)
        titles = [ 'Style: Spline, Symbol: Cross',
                   'Style: Sticks, Symbol: Ellipse',
                   'Style: Lines, Symbol: None',
                   'Style: Steps, Symbol: None',
                   'Style: NoCurve, Symbol: XCross' ]
        for title in titles:
            painter.drawText(
                0, r.top(), r.width(), painter.fontMetrics().height(),
                Qt.AlignTop | Qt.AlignHCenter, title)
            r.moveBy(0, dy)

# Admire!         
app = QApplication(sys.argv)
demo = CurveDemo()
app.setMainWidget(demo)
demo.resize(300, 600)
demo.show()
app.exec_loop()


More information about the PyQt mailing list