1. ariovistus
  2. pyd

Wiki

Clone wiki

pyd / ClassWrap

Class Wrapping

Exposing D classes to Python is easy! The heart of Pyd's class wrapping features is the wrap_class function template.

Calls to wrap_class must occur after calling module_init.

See also

pyd.class_wrap

Member wrapping

Python member type D member typePyd Param
static functionstatic functionStaticDef!(Foo.fun)
instance functioninstance functionDef!(Foo.fun)
propertyinstance function or instance propertyProperty!(Foo.fun)
instance fieldinstance fieldMember!(fieldname)

Operator Overloading

The following operators may be wrapped:

OperatorD functionPyd Param
+ - * / % ^^ << >> & ^ | ~opBinary!(op)OpBinary!(op)
+ - * / % ^^ << >> & ^ | ~ inopBinaryRight!(op)OpBinaryRight!(op)
+= -= *= /= %= ^^= <<= >>= &= ^= |= ~=opOpAssign!(op)OpAssign!(op)
+ - ~opUnary!(op)OpUnary!(op)
< <= > >=opCmpOpCompare!()
a[i]opIndexOpIndex!()
a[i] = bopIndexAssignOpIndexAssign!()
a[i .. j] (python a[i:j])opSliceOpSlice!()
a[i .. j] = bopSliceAssignOpSliceAssign!()
a(args)opCallOpCall!(Args)
a.length (python len(a))lengthLen!()

The usual rules for function wrapping apply: Only an opFunc whose return type and arguments are convertable may be wrapped. (The current implementation is pretty dumb: If the specified opFunc has an unconvertable return type or argument, the operator overload will still be wrapped, but won't work.)

Notes on wrapped operators

  • Only one overload is permitted per operator; however OpBinary and OpBinaryRight may "share" an operator.
  • opSlice, opSliceAssign: Pyd only supports these overloads if both of their two indexes are implicitly convertable to type Py_ssize_t. This is a limitation of the Python/C API. Note that this means the zero-argument form of opSlice (for allowing the "empty slice," e.g. foo[]) cannot be wrapped. (I may work around this in the future.) In the presence of multiple overloads, Pyd is capable of selecting the appropriate one.
  • ~, ~=: Python does not have a dedicated array concatenation operator. The plus sign (+) is reused for this purpose. Therefore, odd behavior may result with classes that define both opAdd/opAddAssign and one or both of these operators. (Consider yourself warned.) However, the Python/C API considers addition and concatenation distinct operations, and so both of these sets of operator overloads are supported.
  • in: Python expects the in operator to return a boolean value (it is a containment test). D convention is for in to search for the value in the container, and to return a pointer to the found item, or null if the item is not found. That said, D does not enforce any particular signature on the in overload, while the Python/C API does. Pyd will check the boolean result of a call to the in overload, and return that value to Python.

Iterator wrapping

A wrapped class can be made iterable in python by supplying Defs with the python names:

  • __iter__ which should return this.
  • next which should return the next item, or null for termination. Signature must be PyObject* next().

Alternately, you can supply a Def with __iter__ only and have it return a Range that iterates your object.

Examples

Suppose we have the following simple class:

import std.stdio;

class Foo {
    int m_i;

    this() { m_i = 0; }
    this(int j) { m_i = j; }
    this(int j, int k) { m_i = j + k; }

    int i() { return m_i; }
    void i(int j) { m_i = j; }

    void foo(char[] s) {
        writefln(s, m_i);
    }

    Foo opBinary(string op)(int rhs) if(op == "+"){
        return new Foo(m_i + rhs);
    }
    Foo opBinaryRight(string op)(int rhs) if(op == "+"){
        return new Foo(m_i + rhs);
    }
}

We would expose this class to Python by putting this code in PydMain after the call to module_init:

// Call wrap_class
wrap_class!(
    Foo,
    // Wrap the "foo" method
    Def!(Foo.foo),
    // Wrap the "i" property
    Property!(Foo.i),
    // Wrap the constructors.
    Init!(int), 
    Init!(int, int),
    // Wrap the '+' operator
    OpBinary!("+"),
    OpBinaryRight!("+"),
)();

Now we can use this type from within Python like any other type.

>>> from testmodule import Foo
>>> f = Foo()
>>> f.i
0
>>> f.i = 20
>>> f.foo("Hello! i is ")
Hello! i is 20
>>> f = Foo(10, 10)
>>> f.i
20
>>> e = f + 30
>>> e.i
50
>>> e = 35 + f
>>> e.i
55
>>> # We can even subclass our D type
>>> class MyFoo(Foo):
... 	def bar(self):
... 		print "Hey, i+3 is", self.i + 3
... 
>>> h = MyFoo(3)
>>> h.bar()
Hey, i+3 is 6
>>> 

Updated