c++boost.gif (8819 bytes)Special Method Name Support

Overview

Py_cpp supports all of the standard special method names supported by real Python class instances except:

(more on the latter two below). So, for example, we can wrap a std::map<std::size_t,std::string> as follows:

Example

typedef std::map<std::size_t, std::string> StringMap;

// A helper function for dealing with errors. Throw a Python exception
// if p == m.end().
void throw_key_error_if_end(
        const StringMap& m, 
        StringMap::const_iterator p, 
        std::size_t key)
{
    if (p == m.end())
    {
        PyErr_SetObject(PyExc_KeyError, py::converters::to_python(key));
        throw py::ErrorAlreadySet();
    }
}

// Define some simple wrapper functions which match the Python  protocol
// for __getitem__, __setitem__, and __delitem__.  Just as in Python, a
// free function with a "self" first parameter makes a fine class method.

const std::string& get_item(const StringMap& self, std::size_t key)
{
    const StringMap::const_iterator p = self.find(key);
    throw_key_error_if_end(self, p, key);
    return p->second;
}

// Sets the item corresponding to key in the map.
void StringMapPythonClass::set_item(StringMap& self, std::size_t key, const std::string& value)
{
    self[key] = value;
}

// Deletes the item corresponding to key from the map.
void StringMapPythonClass::del_item(StringMap& self, std::size_t key)
{
    const StringMap::iterator p = self.find(key);
    throw_key_error_if_end(self, p, key);
    self.erase(p);
}

ClassWrapper<StringMap> string_map(my_module, "StringMap");
string_map.def(py::Constructor<>());
string_map.def(&StringMap::size, "__len__");
string_map.def(get_item, "__getitem__");
string_map.def(set_item, "__setitem__");
string_map.def(del_item, "__delitem__");

Then in Python:

>>> m = StringMap()
>>> m[1]
Traceback (innermost last):
  File "<stdin>", line 1, in ?
KeyError: 1
>>> m[1] = 'hello'
>>> m[1]
'hello'
>>> del m[1]
>>> m[1]            # prove that it's gone
Traceback (innermost last):
  File "<stdin>", line 1, in ?
KeyError: 1
>>> del m[2]
Traceback (innermost last):
  File "<stdin>", line 1, in ?
KeyError: 2
>>> len(m)
0
>>> m[3] = 'farther'
>>> len(m)
1

Getters and Setters

Py_cpp extension classes support some additional "special method" protocols not supported by built-in Python classes. Because writing __getattr__, __setattr__, and __delattr__ functions can be tedious in the common case where the attributes being accessed are known statically, py_cpp checks the special names

to provide functional access to the attribute <name>. This facility can be used from C++ or entirely from Python. For example, the following shows how we can implement a "computed attribute" in Python:
>>> class Range(AnyPy_cppExtensionClass):
...    def __init__(self, start, end):
...        self.start = start
...        self.end = end
...    def __getattr__length__(self):
...        return self.end - self.start
...
>>> x = Range(3, 9)
>>> x.length
6

Direct Access to Data Members

Py_cpp uses the special __xxxattr__<name>__ functionality described above to allow direct access to data members through the following special functions on ClassWrapper<> and ExtensionClass<>:

Note that the first two functions, used alone, may produce surprising behavior. For example, when def_getter() is used, the default functionality for setattr() and delattr() remains in effect, operating on items in the extension instance's name-space (i.e., its __dict__). For that reason, you'll usually want to stick with def_readonly and def_read_write.

For example, to expose a std::pair<int,long> we might write:

typedef std::pair<int,long> Pil;
int first(const Pil& x) { return x.first; }
long second(const Pil& x) { return x.second; }
   ...
my_module.def(first, "first");
my_module.def(second, "second");

ClassWrapper<Pil> pair_int_long(my_module, "Pair");
pair_int_long.def(py::Constructor<>());
pair_int_long.def(py::Constructor<int,long>());
pair_int_long.def_read_write(&Pil::first, "first");
pair_int_long.def_read_write(&Pil::second, "second");

Now your Python class has attributes first and second which, when accessed, actually modify or reflect the values of corresponding data members of the underlying C++ object. Now in Python:

>>> x = Pair(3,5)
>>> x.first
3
>>> x.second
5
>>> x.second = 8
>>> x.second
8
>>> second(x) # Prove that we're not just changing the instance __dict__
8

Numeric Method Support

Py_cpp supports the following Python special numeric method names:

Name Notes
__add__(self, other) operator+(const T&, const T&)
__sub__(self, other) operator-(const T&, const T&)
__mul__(self, other) operator*(const T&, const T&)
__div__(self, other) operator/(const T&, const T&)
__mod__(self, other) operator%(const T&, const T&)
__divmod__(self, other) return a py::Tuple initialized with (quotient, remainder).
__pow__(self, other [, modulo]) use overloading to support both forms of __pow__
__lshift__(self, other) operator<<(const T&, const T&)
__rshift__(self, other) operator>>(const T&, const T&)
__and__(self, other) operator&(const T&, const T&)
__xor__(self, other) operator^(const T&, const T&)
__or__(self, other) operator|(const T&, const T&)
__neg__(self) operator-(const T&) (unary negation)
__pos__(self) operator+(const T&) (identity)
__abs__(self) Called to implement the built-in function abs()
__invert__(self) operator~(const T&)
__int__(self) operator long() const
__long__(self) Should return a Python long object. Can be implemented with PyLong_FromLong(value), for example.
__float__(self) operator double() const
__oct__(self) Called to implement the built-in function oct(). Should return a string value.
__hex__(self) Called to implement the built-in function hex(). Should return a string value.
__coerce__(self, other) Should return a Python 2-tuple (C++ code may return a py::Tuple) where the elements represent the values of self and other converted to the same type.

Where are the __r<name>__ functions?

At first we thought that supporting __radd__ and its ilk would be impossible, since Python doesn't supply any direct support and in fact implements a special case for its built-in class instances. This article gives a pretty good overview of the direct support for numerics that Python supplies for extension types. We've since discovered that it can be done, but there are some pretty convincing arguments out there that this arrangement is less-than-ideal. Instead of supplying a sub-optimal solution for the sake of compatibility with built-in Python classes, we're doing the neccessary research so we can "do it right". This will also give us a little time to hear from users about what they want. The direction we're headed in is based on the idea of multimethods rather than on trying to find a coercion function bound to one of the arguments.

And what about __complex__?

That, dear reader, is one problem we don't know how to solve. The Python source contains the following fragment, indicating the special-case code really is hardwired:

/* XXX Hack to support classes with __complex__ method */
if (PyInstance_Check(r)) { ...

Previous: Function Overloading Next: A Peek Under the Hood Up: Top

© Copyright David Abrahams 2000. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.

Updated: Oct 19, 2000