From 105fd06b5e4f5715e6aeb15f8b94d463b1b7d076 Mon Sep 17 00:00:00 2001 From: KirkMcDonald Date: Thu, 1 Feb 2007 23:56:35 +0000 Subject: [PATCH] Improved inheritance support. Fixed ctor-related bug. git-svn-id: http://svn.dsource.org/projects/pyd/trunk@91 1df65b71-e716-0410-9316-ac55df2b1602 --- examples/inherit/inherit.d | 39 ++++++++++++++++---- examples/inherit/test.py | 22 ++++++------ html_doc/class_wrapping.html | 6 ++++ html_doc/inherit.html | 64 +++++++++++++++++++++++++++++++++ infrastructure/pyd/class_wrap.d | 56 +++++++++++++++++++---------- raw_html/class_wrapping.html | 6 ++++ raw_html/inherit.html | 64 +++++++++++++++++++++++++++++++++ 7 files changed, 221 insertions(+), 36 deletions(-) diff --git a/examples/inherit/inherit.d b/examples/inherit/inherit.d index f8ab6d3..1d6d991 100644 --- a/examples/inherit/inherit.d +++ b/examples/inherit/inherit.d @@ -4,6 +4,7 @@ import pyd.pyd; import std.stdio; class Base { + this(int i) { writefln("Base.this(): ", i); } void foo() { writefln("Base.foo"); } @@ -13,13 +14,26 @@ class Base { } class Derived : Base { + this(int i) { super(i); writefln("Derived.this(): ", i); } void foo() { writefln("Derived.foo"); } } -class WrapDerive : Derived { +class BaseWrap : Base { mixin OverloadShim; + this(int i) { super(i); } + void foo() { + get_overload(&super.foo, "foo"); + } + void bar() { + get_overload(&super.bar, "bar"); + } +} + +class DeriveWrap : Derived { + mixin OverloadShim; + this(int i) { super(i); } void foo() { get_overload(&super.foo, "foo"); } @@ -33,17 +47,17 @@ void call_poly(Base b) { Base b1, b2, b3; Base return_poly_base() { - if (b1 is null) b1 = new Base; + if (b1 is null) b1 = new Base(1); return b1; } Base return_poly_derived() { - if (b2 is null) b2 = new Derived; + if (b2 is null) b2 = new Derived(2); return b2; } Base return_poly_wrap() { - if (b3 is null) b3 = new WrapDerive; + if (b3 is null) b3 = new DeriveWrap(3); return b3; } @@ -56,15 +70,26 @@ extern(C) void PydMain() { module_init(); wrapped_class!(Base) b; + b.hide(); b.def!(Base.foo); b.def!(Base.bar); finalize_class(b); wrapped_class!(Derived) d; + d.hide(); d.def!(Derived.foo); finalize_class(d); - wrapped_class!(WrapDerive) w; - w.def!(WrapDerive.foo); - finalize_class(w); + wrapped_class!(BaseWrap, "Base") bw; + bw.init!(void function(int)); + bw.def!(BaseWrap.foo); + bw.def!(BaseWrap.bar); + finalize_class(bw); + + wrapped_class!(DeriveWrap, "Derived") dw; + dw.init!(void function(int)); + dw.parent!(BaseWrap); + dw.def!(DeriveWrap.foo); + finalize_class(dw); } + diff --git a/examples/inherit/test.py b/examples/inherit/test.py index 16066c1..b302e6e 100644 --- a/examples/inherit/test.py +++ b/examples/inherit/test.py @@ -11,8 +11,8 @@ import inherit -b = inherit.Base() -d = inherit.Derived() +b = inherit.Base(1) +d = inherit.Derived(2) b.foo() b.bar() @@ -25,14 +25,14 @@ inherit.call_poly(b) inherit.call_poly(d) -w = inherit.WrapDerive() -inherit.call_poly(w) +#w = inherit.WrapDerive() +#inherit.call_poly(w) -class PyClass(inherit.WrapDerive): +class PyClass(inherit.Derived): def foo(self): print 'PyClass.foo' -p = PyClass() +p = PyClass(3) #print "The basic inheritance support breaks down here:" inherit.call_poly(p) @@ -40,16 +40,18 @@ def foo(self): b1 = inherit.return_poly_base() print "inherit.return_poly_base returned instance of Base" -assert type(b1) == inherit.Base +b1.foo() +b1.bar() +#assert type(b1) == inherit.Base b2 = inherit.return_poly_derived() b2a = inherit.return_poly_derived() print "inherit.return_poly_derived returned instance of Derived" -assert type(b2) == inherit.Derived +#assert type(b2) == inherit.Derived print "inherit.return_poly_derived returned the same object twice" assert b2 is b2a b3 = inherit.return_poly_wrap() -print "inherit.return_poly_wrap returned instance of WrapDerive" -assert type(b3) == inherit.WrapDerive +print "inherit.return_poly_wrap returned instance of DeriveWrap" +assert type(b3) == inherit.Derived print print '-------' diff --git a/html_doc/class_wrapping.html b/html_doc/class_wrapping.html index 891a86a..2ea2387 100644 --- a/html_doc/class_wrapping.html +++ b/html_doc/class_wrapping.html @@ -58,6 +58,12 @@

Class wrapping

static void 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 should be the same as 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 the init function.
+
static void parent(Parent) ();
+
This allows the user to manually specify a class as this class's parent. This is intended for a very specific purpose (related to how Pyd handles inheritance), and should not be used heedlessly. If a class's parent was previously wrapped, then Pyd will detect this and set up a parent-child relationship automatically, in which case it is not neccessary to specify this.
+ +
static void hide();
+
Causes this class to be wrapped, but not actually directly exposed to Python. This can be useful if you want to return instances of a class without allowing Python code to instantiate them. This is mainly used when handling inheritance.
+
static void 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 the inout modifiers! (This is not available in Linux; see the note below on opApply wrapping.)
diff --git a/html_doc/inherit.html b/html_doc/inherit.html index fbc74ca..f8e297c 100644 --- a/html_doc/inherit.html +++ b/html_doc/inherit.html @@ -121,6 +121,70 @@

Inheritance

>>> polymorphic_call(p) PyClass.foo +

However, BaseWrap has no particular relationship to Derived. You may remember that Derived overloads bar but not foo. When we wrapped Derived in PydMain, we specified the foo overload but not the bar overload. Because Derived's parent class is no longer wrapped, Pyd no longer has any way to know about the bar method of the Derived class.

+ +

The solution is to explicitly tell Pyd that Derived's parent is BaseWrap. Furthermore, it is probably best to go the extra mile, by wrapping an OverloadShim subclass of Derived (call it DerivedWrap), and telling Pyd that BaseWrap is its parent. Additionally, the original Base and Derived classes should still be wrapped, in the event that functions return instances of them to Python, but should not actually be exposed to Python. The complete solution ends up looking like this:

+ +
import pyd.pyd;
+import std.stdio;
+
+class Base {
+    void foo() { writefln("Base.foo"); }
+    void bar() { writefln("Base.bar"); }
+}
+
+class Derived : Base {
+    void foo() { writefln("Derived.foo"); }
+}
+
+class BaseWrap : Base {
+    mixin OverloadShim;
+    void foo() {
+        get_overload(&super.foo, "foo");
+    }
+    void bar() {
+        get_overload(&super.bar, "bar");
+    }
+}
+
+class DerivedWrap : Derived {
+    mixin OverloadShim;
+    void foo() {
+        get_overload(&super.foo, "foo");
+    }
+}
+
+extern (C) void PydMain() {
+    module_init();
+
+    wrapped_class!(Base) b;
+    w.hide();
+    w.def!(Base.foo);
+    w.def!(Base.bar);
+    finalize_class(w);
+
+    wrapped_class!(Derived) d;
+    d.hide();
+    d.def!(Derived.foo);
+    finalize_class(d);
+
+    wrapped_class!(BaseWrap, "Base") bw;
+    bw.def!(BaseWrap.foo);
+    bw.def!(BaseWrap.bar);
+    finalize_class(bw);
+
+    wrapped_class!(DerivedWrap, "Derived") dw;
+    dw.parent!(BaseWrap);
+    dw.def!(DerivedWrap.foo);
+    finalize_class(dw);
+}
+ +

(I recognize that this is astoundingly ugly. However, it is the best solution I can come up with without resorting to code generation.)

+ +

The inherit example in the Pyd distribution provides a more complete version of this example, including how wrapper code should handle constructors.

+ +

(TODO: Add support for interfaces and abstract classes.)

+ diff --git a/infrastructure/pyd/class_wrap.d b/infrastructure/pyd/class_wrap.d index 8428a09..fda597d 100644 --- a/infrastructure/pyd/class_wrap.d +++ b/infrastructure/pyd/class_wrap.d @@ -85,7 +85,7 @@ template wrapped_class_type(T) { null, /*tp_getattro*/ null, /*tp_setattro*/ null, /*tp_as_buffer*/ - 0, /*tp_flags*/ + 0, /*tp_flags*/ null, /*tp_doc*/ null, /*tp_traverse*/ null, /*tp_clear*/ @@ -249,6 +249,7 @@ template wrapped_set(T, alias Fn) { struct wrapped_class(T, char[] classname = symbolnameof!(T)) { static if (is(T == class)) pragma(msg, "wrapped_class: " ~ classname); static const char[] _name = classname; + static bool _private = false; alias T wrapped_type; /** * Wraps a member function of the class. @@ -332,6 +333,14 @@ struct wrapped_class(T, char[] classname = symbolnameof!(T)) { &wrapped_ctors!(T, C).init_func; } + static void parent(Parent) () { + wrapped_class_type!(T).tp_base = &wrapped_class_type!(Parent); + } + + static void hide() { + _private = true; + } + // Iteration wrapping support requires StackThreads version(Pyd_with_StackThreads) { @@ -393,12 +402,14 @@ void finalize_class(CLS) (CLS cls, char[] docstring="", char[] modulename="") { type.tp_methods = wrapped_method_list!(T).ptr; type.tp_name = (module_name ~ "." ~ name ~ \0).ptr; - // Check for wrapped parent classes - static if (is(T B == super)) { - foreach (C; B) { - static if (is(C == class) && !is(C == Object)) { - if (is_wrapped!(C)) { - type.tp_base = &wrapped_class_type!(C); + // Check for wrapped parent classes, if one was not explicitly supplied. + if (type.tp_base is null) { + static if (is(T B == super)) { + foreach (C; B) { + static if (is(C == class) && !is(C == Object)) { + if (is_wrapped!(C)) { + type.tp_base = &wrapped_class_type!(C); + } } } } @@ -436,21 +447,27 @@ void finalize_class(CLS) (CLS cls, char[] docstring="", char[] modulename="") { type.tp_call = cast(ternaryfunc)&method_wrap!(T, T.opCall, typeof(&T.opCall)).func; } - // If a ctor wasn't supplied, try the default. - if (type.tp_init is null) { - static if (is(T == class)) { - type.tp_init = &wrapped_init!(T).init; - } else { - type.tp_init = &wrapped_struct_init!(T).init; + if (CLS._private) { + type.tp_init = null; + } else { + // If a ctor wasn't supplied, try the default. + static if (is(typeof(new T))) { + if (type.tp_init is null) { + static if (is(T == class)) { + type.tp_init = &wrapped_init!(T).init; + } else { + type.tp_init = &wrapped_struct_init!(T).init; + } + } } } if (PyType_Ready(&type) < 0) { - // XXX: This will probably crash the interpreter, as it isn't normally - // caught and translated. throw new Exception("Couldn't ready wrapped type!"); } Py_INCREF(cast(PyObject*)&type); - PyModule_AddObject(Pyd_Module_p(modulename), name.ptr, cast(PyObject*)&type); + if (!CLS._private) { + PyModule_AddObject(Pyd_Module_p(modulename), name.ptr, cast(PyObject*)&type); + } is_wrapped!(T) = true; static if (is(T == class)) { wrapped_classes[T.classinfo] = &type; @@ -459,12 +476,13 @@ void finalize_class(CLS) (CLS cls, char[] docstring="", char[] modulename="") { template OverloadShim() { std.traits.ReturnType!(dg_t) get_overload(dg_t, T ...) (dg_t dg, char[] name, T t) { - python.PyObject* _pyobj = wrapped_gc_objects[cast(void*)this]; - if (_pyobj.ob_type != &wrapped_class_type!(typeof(this))) { + python.PyObject** _pyobj = cast(void*)this in wrapped_gc_objects; + python.PyTypeObject** _pytype = this.classinfo in wrapped_classes; + if (_pyobj is null || _pytype is null || (*_pyobj).ob_type != *_pytype) { // If this object's type is not the wrapped class's type (that is, // if this object is actually a Python subclass of the wrapped // class), then call the Python object. - python.PyObject* method = python.PyObject_GetAttrString(_pyobj, (name ~ \0).ptr); + python.PyObject* method = python.PyObject_GetAttrString(*_pyobj, (name ~ \0).ptr); if (method is null) handle_exception(); dg_t pydg = PydCallable_AsDelegate!(dg_t)(method); python.Py_DECREF(method); diff --git a/raw_html/class_wrapping.html b/raw_html/class_wrapping.html index 8c1a817..ab97f4b 100644 --- a/raw_html/class_wrapping.html +++ b/raw_html/class_wrapping.html @@ -42,6 +42,12 @@

Class wrapping

static void 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 should be the same as 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 the init function.
+
static void parent(Parent) ();
+
This allows the user to manually specify a class as this class's parent. This is intended for a very specific purpose (related to how Pyd handles inheritance), and should not be used heedlessly. If a class's parent was previously wrapped, then Pyd will detect this and set up a parent-child relationship automatically, in which case it is not neccessary to specify this.
+ +
static void hide();
+
Causes this class to be wrapped, but not actually directly exposed to Python. This can be useful if you want to return instances of a class without allowing Python code to instantiate them. This is mainly used when handling inheritance.
+
static void 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 the inout modifiers! (This is not available in Linux; see the note below on opApply wrapping.)
diff --git a/raw_html/inherit.html b/raw_html/inherit.html index cb3fb42..459d475 100644 --- a/raw_html/inherit.html +++ b/raw_html/inherit.html @@ -105,6 +105,70 @@

Inheritance

>>> polymorphic_call(p) PyClass.foo +

However, BaseWrap has no particular relationship to Derived. You may remember that Derived overloads bar but not foo. When we wrapped Derived in PydMain, we specified the foo overload but not the bar overload. Because Derived's parent class is no longer wrapped, Pyd no longer has any way to know about the bar method of the Derived class.

+ +

The solution is to explicitly tell Pyd that Derived's parent is BaseWrap. Furthermore, it is probably best to go the extra mile, by wrapping an OverloadShim subclass of Derived (call it DerivedWrap), and telling Pyd that BaseWrap is its parent. Additionally, the original Base and Derived classes should still be wrapped, in the event that functions return instances of them to Python, but should not actually be exposed to Python. The complete solution ends up looking like this:

+ +
import pyd.pyd;
+import std.stdio;
+
+class Base {
+    void foo() { writefln("Base.foo"); }
+    void bar() { writefln("Base.bar"); }
+}
+
+class Derived : Base {
+    void foo() { writefln("Derived.foo"); }
+}
+
+class BaseWrap : Base {
+    mixin OverloadShim;
+    void foo() {
+        get_overload(&super.foo, "foo");
+    }
+    void bar() {
+        get_overload(&super.bar, "bar");
+    }
+}
+
+class DerivedWrap : Derived {
+    mixin OverloadShim;
+    void foo() {
+        get_overload(&super.foo, "foo");
+    }
+}
+
+extern (C) void PydMain() {
+    module_init();
+
+    wrapped_class!(Base) b;
+    w.hide();
+    w.def!(Base.foo);
+    w.def!(Base.bar);
+    finalize_class(w);
+
+    wrapped_class!(Derived) d;
+    d.hide();
+    d.def!(Derived.foo);
+    finalize_class(d);
+
+    wrapped_class!(BaseWrap, "Base") bw;
+    bw.def!(BaseWrap.foo);
+    bw.def!(BaseWrap.bar);
+    finalize_class(bw);
+
+    wrapped_class!(DerivedWrap, "Derived") dw;
+    dw.parent!(BaseWrap);
+    dw.def!(DerivedWrap.foo);
+    finalize_class(dw);
+}
+ +

(I recognize that this is astoundingly ugly. However, it is the best solution I can come up with without resorting to code generation.)

+ +

The inherit example in the Pyd distribution provides a more complete version of this example, including how wrapper code should handle constructors.

+ +

(TODO: Add support for interfaces and abstract classes.)

+