[PYTHON C++-SIG] Typecasts to/from PyObject?

Johann Hibschman johann at physics.berkeley.edu
Thu Feb 27 03:05:55 CET 1997


Geoffrey Furnish writes:

> Johann Hibschman writes:

>  > I'm looking at this in the context of creating a PyDict class.  If I 
>  > define the type conversions, I could write:
>  > 
>  >    PyList plist;
>  >    plist["spam"] = 3.0;
>  >    x = y + plist["spam"];
> 
> That is nice, if it works.  However, I am not sure how you avoid
> having multiple available conversions, and what effect that has on the
> matching rules.  

As you mention, having both a double conversion and an int conversion
in the same class would confuse the compiler & require an explicit
cast.

I was thinking more along the lines of having several types.  First, a
generic PyObj type which only supports implicit conversion to and from a
PyObject *, and which tracks the reference count of the object pointed to
automatically.  For compilers that can do member function templates, it
could also do explicit casts to any other type with defined python traits. 
All in all, it's pretty straightforward.  It would be initialized from a
PyObject *. 

Then, I was thinking of having a templated class PyType<T>, either
subclassed off of the PyObj class, or subclassed off of the same
abstract class as PyObj, which would support conversion to/from
a PyObject *, as well as conversion to/from type T.  The conversion
code could then be stored in the pytraits class.

(Question: would it be better to convert to/from T*, rather than T?
 These could then by PyPtr<T> classes, since in many ways they are
 similar to ideas of smart pointers.)

The PyObj class would be used when no assumptions are being made
about the type of the object, while the PyType<T>'s would make
the assumption that the python object was in fact of type T.

For lists, a templated adaptor class could be constructed fairly easily
which would provide a default return type for the list, so you could
write:

   intlist = PyAdapt<int>(pylist);
   x = 2*intlist[2]/intlist[3];

Basically, the class would define a non-explicit operator T() method,
to allow automatic conversion.

Since the lists would have to implement a proxy class to handle the
difference between using operator[] in an lvalue or rvalue context,
each adaptor would probably have to implement its own (templated)
proxy class.

Having babbled that much, a sketch of an implementation would look like:

// --- types ---

class PyAbstractType {
public:
	PyAbstractType( PyObject *x ) { p = x; }
	~PyAbstractType() = 0;
	operator (PyObject *)() { return p; }
	// reference counting machinery
private:
	PyObject *p;
}

// pure virtual destructor definition.  decrement reference count?
PyAbstractType::~PyAbstractType() {}

// allow default cast to PyObject, or explicit cast to anything...
class PyObj : public PyAbstractType {
public:
	PyObj( PyObject *x ) : PyAbstractType(x) {}
	template <class T>        // my own compiler can't do this...
	explicit operator T() { return pytraits<T>::fromPyObj(p); }
}

template <class T>
class PyType: public PyAbstractType {
public:
	PyType( PyObject *x ) : PyAbstractType(x) {}
	PyType( const T &x ) : PyAbstractType( pytraits<T>::toPyObj(x) ) {}
	operator T() const { return pytraits<T>::fromPyObj(p); }
}

// -- lists --
// uses casting to/from PyObject * ability of PyList, PyType
// needs error checking, must check syntax for SetItem/GetItem
// must also fiddle a bit for const-correctness
template <class T>
class PyListProxy {
public:
	int index;
	PyList &lst;
	
	PyListProxy( PyList &l, int i ) :  lst(l), index(i) {}
	// use as lvalue
	PyType<T> operator= ( const T &ob ) {
		PyType<T> temp(ob);
		PyList_SetItem( lst, index, temp );
		return temp;
	}
	// use as lvalue 2 (probably has bugs)
	PyAbstractType &operator= ( const PyAbstractType &ob ) {
		PyList_SetItem( lst, index, ob );
		return ob;
	}
	// use as rvalue (PyType can convert to T)
	operator PyType<T> () {
		return PyType<T>( PyList_GetItem( lst, index ) );
	}
}

// non-template version, works on PyObj's
class PyListProxy {
public:
	int index;
	PyList &lst;
	
	PyListProxy( PyList &l, int i ) :  lst(l), index(i) {}
	// use as lvalue
	PyObj operator= ( const PyObj &ob ) {
		PyList_SetItem( lst, index, ob );
		return ob;
	}
	// use as rvalue
	operator PyObj () {
		return PyObj( PyList_GetItem( lst, index ) );
	}
}

// actual list
class PyList : public PyAbstractType {
public:
	PyList( PyObject *x ) : PyAbstractType(x) {}
	// maybe a templated constructor from STL list<T>?
	PyListProxy operator[](int index) {
		return PyListProxy( *this, index );
	}
}

// template version = same as above, but uses PyListProxy<T>'s


Okay, that's enough for now.  That was a longer sketch than I thought it
would be, and it is all just off the top of my head, so it probably has
quite a few mistakes, but it gives an idea of what I'm thinking of.

I don't have the time to code it for real, test it out, etc.  As soon as I
get out of my current analytic phase and back into numeric calculations,
I'll have the incentive to hack it out for real, but for now I have other
things on my mind. 

Feedback?

- Johann

--
Johann Hibschman            | Grad student in Physics, lurking in Astronomy,
johann at physics.berkeley.edu | Probing Pulsar Pair Plasma Production Processes


_______________
C++-SIG - SIG for Development of a C++ Binding to Python

send messages to: c++-sig at python.org
administrivia to: c++-sig-request at python.org
_______________



More information about the Cplusplus-sig mailing list