Class wrapping
Exposing D classes to Python is easy! The heart of Pyd's class wrapping features is the wrap_class
function template:
void wrap_class(T, char[] classname = symbolnameof!(T), Params...) ();
- T is the class being wrapped.
- classname is the name of the class as it will appear in Python. It defaults to the name of the D class. If you are wrapping an instance of a class template, you will have to provide this explicitly.
- Params is a series of struct types (defined below), which define the various members of the class.
Calls to wrap_class
must occur after calling module_init
.
To expose the constructors, methods, and properties of the class, you must pass wrap_class
instantiations of these struct templates.
struct Def(alias fn, char[] name = symbolnameof!(fn), fn_t = typeof(&fn));
- This wraps a method of the class. It functions very much like the
def
function used to wrap regular functions, with one very important difference: There is no support for default arguments. (This is a side-effect of the fact that you cannot call an alias of a method in D, and delegates do not understand default arguments.) struct StaticDef(alias fn, char[] name = symbolnameof!(fn), fn_t = typeof(&fn), uint MIN_ARGS = minArgs!(fn));
- This wraps a static member function of the class. It also functions exactly like the
def
function used to wrap regular functions, and even includes support for default arguments. struct Property(alias fn, char[] name = symbolnameof!(fn), bool RO = false);
- This wraps a property. See the examples below for more details.
- fn is the name of the property.
prop
will automatically attempt to wrap both the "get" and "set" forms of the property, unless RO is specified. - name is the name of the property as it will appear in Python. As with
def
,prop
will attempt to derive this automatically. - RO specifies whether this is a read-only property. If true, it will only wrap the "get" form of the property. If false, it will wrap both the "get" and "set" forms. (This is a little hackish, and I will probably try to make this detection more automatic in the future. It also means it cannot support a property that only has a "set" form.)
- fn is the name of the property.
struct Init(C ...);
- This allows you to expose the class's constructors to Python. If the class provides a zero-argument constructor, there is no need to specify it; it is always available. Each element of C should be a function type. Each function type should correspond to a constructor. (That is, the arguments to the function type should be the same as the arguments to the class constructor. The return type is ignored.) There is an additional limitation at this time: No two constructors may have the same number of arguments. Pyd will always attempt to call the first constructor with the right number of arguments. If you wish to support a constructor with default arguments, you must specify each possible constructor call as a different template argument to this function. The examples show a few uses of
Init
. struct Repr(alias fn);
- This allows you to expose a member function of the class as the Python type's
__repr__
function. The member function must have the signaturechar[] function()
. struct Iter(iter_t);
- This allows the user to specify a different overload of opApply than the default. (The default is always the one that is lexically first.) The iter_t argument should be the type of the delegate that forms the argument to opApply. This might be e.g.
int delegate(inout int)
. Don't forget theinout
modifiers! (This is not available in Linux; see the note below on opApply wrapping.) struct AltIter(alias fn, char[] name = symbolnameof!(fn), iter_t = implementationDetail);
- This wraps alternate iterator methods as Python methods that return iterator objects. The wrapped methods should have a signature like that of opApply. (In other words, they should be methods intended to be used with D's ability to iterate over delgates.) The iter_t argument should be the type of the delegate argument to the method. This will usually be derived automatically. (This is not available in Linux; see the note below on opApply wrapping.)
If you ever wish to check whether a given class has been wrapped, Pyd helpfully registers all wrapped classes with the is_wrapped
template, which is just a templated bool
:
template is_wrapped(T);
If you have a class Foo
, you can check whether it is wrapped by simply checking whether is_wrapped!(Foo)
is true. It is important to note that this is not a const bool
, it is a runtime check.
Automatic operator overloading
Pyd will automatically wrap most of D's operator overload functions with appropriate Python operator overloads. There are some caveats:
- Pyd will only automatically wrap the lexically first opFunc defined for a given opFunc. (In the future, I may add a mechanism allowing a user to specifiy a specific overload of an opFunc.)
- 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 lexically first opFunc has an unconvertable return type or argument, the operator overload will still be wrapped, but won't work.)
At the moment, only the following operator overloads are supported:
opNeg, opPos, opCom, opAdd, opSub, opMul, opDiv, opMod, opAnd, opOr, opXor, opShl, opShr, opCat, opAddAssign, opSubAssign, opMulAssign, opDivAssign, opModAssign, opAndAssign, opOrAssign, opXorAssign, opShlAssign, opShrAssign, opCatAssign, opIn_r, opCmp, opCall, opApply, opIndex, opIndexAssign, opSlice, opSliceAssign
Missing from this list are opUShr
and opUShrAssign
. Python does not have an unsigned right-shift operator, so these operator overloads are not supported. (You may still wrap them with a normal method using Def
, of course.) Also missing from the list is opApplyReverse
. This must be wrapped explicitly with AltIter
.
Also missing from the list is opAssign
. Python has strict reference semantics for its objects, so overloading the assignment operator is not possible. You must explicitly wrap opAssign
with a regular method.
Additionally, if a class provides a length
property, Pyd will automatically make it available via Python's built-in function len
and the special __len__
method. You may still wrap it with Property
or Def
if you wish it to be available as a normal property or method.
Notes on wrapped operators
opApply
- Pyd wraps D's iteration protocol with the help of Mikola Lysenko's StackThreads package. This package does not work in GDC, and so opApply wrapping is not available in Linux. See also the
with_st
option offered by CeleriD. opSlice, opSliceAssign
- Pyd only supports these overloads if both of their two indexes are implicitly convertable to type
int
. 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.) Because Pyd can only automatically wrap the lexically-first method in a class, it will fail to wrap opSlice and opSliceAssign if you define an empty form first. opCat, opCatAssign
- 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 bothopAdd/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. opIn_r
- Python expects the
in
operator to return a boolean value (it is a containment test). D convention is forin
to search for the value in the container, and to return a pointer to the found item, ornull
if the item is not found. That said, D does not enforce any particular signature on thein
overload, while the Python/C API does. Pyd will check the boolean result of a call toopIn_r
, and return that value to Python.
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 opAdd(Foo rhs) { return new Foo(m_i + rhs.m_i); } }
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!(void function(int), void function(int, int)) );
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 >>> g = Foo(30) >>> g.i 30 >>> e = f + g >>> e.i 50 >>> # 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 >>>