commit ada1ce37e9fcc772c6b52e1a63bdeb440fa06f68 Author: nobody Date: Sun Dec 10 15:53:47 2000 +0000 This commit was manufactured by cvs2svn to create tag 'Version_1_19_0'. [SVN r8447] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3e84d7c7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,96 @@ +* text=auto !eol svneol=native#text/plain +*.gitattributes text svneol=native#text/plain + +# Scriptish formats +*.bat text svneol=native#text/plain +*.bsh text svneol=native#text/x-beanshell +*.cgi text svneol=native#text/plain +*.cmd text svneol=native#text/plain +*.js text svneol=native#text/javascript +*.php text svneol=native#text/x-php +*.pl text svneol=native#text/x-perl +*.pm text svneol=native#text/x-perl +*.py text svneol=native#text/x-python +*.sh eol=lf svneol=LF#text/x-sh +configure eol=lf svneol=LF#text/x-sh + +# Image formats +*.bmp binary svneol=unset#image/bmp +*.gif binary svneol=unset#image/gif +*.ico binary svneol=unset#image/ico +*.jpeg binary svneol=unset#image/jpeg +*.jpg binary svneol=unset#image/jpeg +*.png binary svneol=unset#image/png +*.tif binary svneol=unset#image/tiff +*.tiff binary svneol=unset#image/tiff +*.svg text svneol=native#image/svg%2Bxml + +# Data formats +*.pdf binary svneol=unset#application/pdf +*.avi binary svneol=unset#video/avi +*.doc binary svneol=unset#application/msword +*.dsp text svneol=crlf#text/plain +*.dsw text svneol=crlf#text/plain +*.eps binary svneol=unset#application/postscript +*.gz binary svneol=unset#application/gzip +*.mov binary svneol=unset#video/quicktime +*.mp3 binary svneol=unset#audio/mpeg +*.ppt binary svneol=unset#application/vnd.ms-powerpoint +*.ps binary svneol=unset#application/postscript +*.psd binary svneol=unset#application/photoshop +*.rdf binary svneol=unset#text/rdf +*.rss text svneol=unset#text/xml +*.rtf binary svneol=unset#text/rtf +*.sln text svneol=native#text/plain +*.swf binary svneol=unset#application/x-shockwave-flash +*.tgz binary svneol=unset#application/gzip +*.vcproj text svneol=native#text/xml +*.vcxproj text svneol=native#text/xml +*.vsprops text svneol=native#text/xml +*.wav binary svneol=unset#audio/wav +*.xls binary svneol=unset#application/vnd.ms-excel +*.zip binary svneol=unset#application/zip + +# Text formats +.htaccess text svneol=native#text/plain +*.bbk text svneol=native#text/xml +*.cmake text svneol=native#text/plain +*.css text svneol=native#text/css +*.dtd text svneol=native#text/xml +*.htm text svneol=native#text/html +*.html text svneol=native#text/html +*.ini text svneol=native#text/plain +*.log text svneol=native#text/plain +*.mak text svneol=native#text/plain +*.qbk text svneol=native#text/plain +*.rst text svneol=native#text/plain +*.sql text svneol=native#text/x-sql +*.txt text svneol=native#text/plain +*.xhtml text svneol=native#text/xhtml%2Bxml +*.xml text svneol=native#text/xml +*.xsd text svneol=native#text/xml +*.xsl text svneol=native#text/xml +*.xslt text svneol=native#text/xml +*.xul text svneol=native#text/xul +*.yml text svneol=native#text/plain +boost-no-inspect text svneol=native#text/plain +CHANGES text svneol=native#text/plain +COPYING text svneol=native#text/plain +INSTALL text svneol=native#text/plain +Jamfile text svneol=native#text/plain +Jamroot text svneol=native#text/plain +Jamfile.v2 text svneol=native#text/plain +Jamrules text svneol=native#text/plain +Makefile* text svneol=native#text/plain +README text svneol=native#text/plain +TODO text svneol=native#text/plain + +# Code formats +*.c text svneol=native#text/plain +*.cpp text svneol=native#text/plain +*.h text svneol=native#text/plain +*.hpp text svneol=native#text/plain +*.ipp text svneol=native#text/plain +*.tpp text svneol=native#text/plain +*.jam text svneol=native#text/plain +*.java text svneol=native#text/plain diff --git a/base_object.h b/base_object.h new file mode 100644 index 00000000..882ed9bd --- /dev/null +++ b/base_object.h @@ -0,0 +1,62 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef BASE_OBJECT_DWA051600_H_ +# define BASE_OBJECT_DWA051600_H_ + +# include "pyconfig.h" +# include "signatures.h" // really just for type<> +# include "wrap_python.h" +# include + +namespace python { namespace detail { + +// base_object - adds a constructor and non-virtual destructor to a +// base Python type (e.g. PyObject, PyTypeObject). +template +struct base_object : python_type +{ + typedef python_type base_python_type; + + // Initializes type and reference count. All other fields of base_python_type are 0 + base_object(PyTypeObject* type_obj); + + // Decrements reference count on the type + ~base_object(); +}; + +// Easy typedefs for common usage +typedef base_object python_object; +typedef base_object python_type; + + +// +// class_t template member function implementations +// +template +base_object::base_object(PyTypeObject* type_obj) +{ + base_python_type* bp = this; +#if !defined(_MSC_VER) || defined(__STLPORT) + std:: +#endif + memset(bp, 0, sizeof(base_python_type)); + ob_refcnt = 1; + ob_type = type_obj; + Py_INCREF(type_obj); +} + +template +inline base_object::~base_object() +{ + Py_DECREF(ob_type); +} + +}} // namespace python::detail + +#endif // BASE_OBJECT_DWA051600_H_ diff --git a/building.html b/building.html new file mode 100644 index 00000000..a25f08e4 --- /dev/null +++ b/building.html @@ -0,0 +1,43 @@ + + + Building a Module with Py_cpp + +
+

+ c++boost.gif (8819 bytes)Building a Module with Py_cpp +

+

+ Right now, the only supported configuration is one in which the py_cpp + source files are statically linked with the source for your extension + module. You may first build them into a library and link it with your + extension module source, but the effect is the same as compiling all + the source files together. Some users have successfully built the + py_cpp sources into a shared library, and support for a shared library + build is planned, but not yet implemented. The py_cpp source files are: +

+
+extclass.cpp
+functions.cpp
+init_function.cpp
+module.cpp
+newtypes.cpp
+objects.cpp
+py.cpp
+subclass.cpp
+         
+
+

+ Previous: 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 30, 2000 +

+ diff --git a/callback.h b/callback.h new file mode 100644 index 00000000..e6faf03d --- /dev/null +++ b/callback.h @@ -0,0 +1,829 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file was generated for 10-argument python callbacks by gen_callback.python + +#ifndef CALLBACK_DWA_052100_H_ +# define CALLBACK_DWA_052100_H_ + +# include "pyconfig.h" +# include "py.h" + +namespace python { + +namespace detail { + template + inline void callback_adjust_refcount(PyObject*, type) {} + + inline void callback_adjust_refcount(PyObject* p, type) + { Py_INCREF(p); } +} + +// Calling Python from C++ +template +struct callback +{ + static R call_method(PyObject* self, const char* name) + { + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("()"))); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + static R call(PyObject* self) + { + ref result(PyEval_CallFunction(self, const_cast("()"))); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1) + { + ref p1(to_python(a1)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(O)"), + p1.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1) + { + ref p1(to_python(a1)); + ref result(PyEval_CallFunction(self, const_cast("(O)"), + p1.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OO)"), + p1.get(), + p2.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref result(PyEval_CallFunction(self, const_cast("(OO)"), + p1.get(), + p2.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOO)"), + p1.get(), + p2.get(), + p3.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2, const A3& a3) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref result(PyEval_CallFunction(self, const_cast("(OOO)"), + p1.get(), + p2.get(), + p3.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref result(PyEval_CallFunction(self, const_cast("(OOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref p9(to_python(a9)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get(), + p9.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref p9(to_python(a9)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get(), + p9.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, const A10& a10) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref p9(to_python(a9)); + ref p10(to_python(a10)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get(), + p9.get(), + p10.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + + template + static R call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, const A10& a10) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref p9(to_python(a9)); + ref p10(to_python(a10)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get(), + p9.get(), + p10.get())); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } +}; + +// This specialization wouldn't be needed, but MSVC6 doesn't correctly allow the following: +// void g(); +// void f() { return g(); } +template <> +struct callback +{ + + static void call_method(PyObject* self, const char* name) + { + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("()"))); + } + + static void call(PyObject* self) + { + ref result(PyEval_CallFunction(self, const_cast("()"))); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1) + { + ref p1(to_python(a1)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(O)"), + p1.get())); + } + + template + static void call(PyObject* self, const A1& a1) + { + ref p1(to_python(a1)); + ref result(PyEval_CallFunction(self, const_cast("(O)"), + p1.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OO)"), + p1.get(), + p2.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref result(PyEval_CallFunction(self, const_cast("(OO)"), + p1.get(), + p2.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOO)"), + p1.get(), + p2.get(), + p3.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2, const A3& a3) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref result(PyEval_CallFunction(self, const_cast("(OOO)"), + p1.get(), + p2.get(), + p3.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref result(PyEval_CallFunction(self, const_cast("(OOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref p9(to_python(a9)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get(), + p9.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref p9(to_python(a9)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get(), + p9.get())); + } + + template + static void call_method(PyObject* self, const char* name, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, const A10& a10) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref p9(to_python(a9)); + ref p10(to_python(a10)); + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(OOOOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get(), + p9.get(), + p10.get())); + } + + template + static void call(PyObject* self, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, const A10& a10) + { + ref p1(to_python(a1)); + ref p2(to_python(a2)); + ref p3(to_python(a3)); + ref p4(to_python(a4)); + ref p5(to_python(a5)); + ref p6(to_python(a6)); + ref p7(to_python(a7)); + ref p8(to_python(a8)); + ref p9(to_python(a9)); + ref p10(to_python(a10)); + ref result(PyEval_CallFunction(self, const_cast("(OOOOOOOOOO)"), + p1.get(), + p2.get(), + p3.get(), + p4.get(), + p5.get(), + p6.get(), + p7.get(), + p8.get(), + p9.get(), + p10.get())); + } +}; + +// Make it a compile-time error to try to return a const char* from a virtual +// function. The standard conversion +// +// from_python(PyObject* string, python::type) +// +// returns a pointer to the character array which is internal to string. The +// problem with trying to do this in a standard callback function is that the +// Python string would likely be destroyed upon return from the calling function +// (python::callback::call[_method]) when its reference count is +// decremented. If you absolutely need to do this and you're sure it's safe (it +// usually isn't), you can use +// +// python::string result(python::callback::call[_method](...args...)); +// ...result.c_str()... // access the char* array +template <> +struct callback +{ + // Try hard to generate a readable error message + typedef struct unsafe_since_python_string_may_be_destroyed {} call, call_method; +}; + +} // namespace python + +#endif // CALLBACK_DWA_052100_H_ diff --git a/caller.h b/caller.h new file mode 100644 index 00000000..00cf65b7 --- /dev/null +++ b/caller.h @@ -0,0 +1,1279 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file generated for 10-argument member functions and 11-argument free +// functions by gen_caller.python + +#ifndef CALLER_DWA05090_H_ +# define CALLER_DWA05090_H_ + +# include "pyconfig.h" +# include "wrap_python.h" +# include +# include "signatures.h" +# include "none.h" + +namespace python { + +// Calling C++ from Python +template +struct caller +{ + template + static PyObject* call(R (T::*pmf)(), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)()); + } + + template + static PyObject* call(R (T::*pmf)(A1), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &a1)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &self, &a1, &a2, &a3, &a4)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &self, &a1, &a2, &a3, &a4, &a5)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6, A7), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8, A9), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()), + from_python(a10, type()))); + } + + + template + static PyObject* call(R (T::*pmf)() const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)()); + } + + template + static PyObject* call(R (T::*pmf)(A1) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &a1)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &self, &a1, &a2, &a3, &a4)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &self, &a1, &a2, &a3, &a4, &a5)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6, A7) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8, A9) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()))); + } + + template + static PyObject* call(R (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()), + from_python(a10, type()))); + } + + // Free functions + static PyObject* call(R (*f)(), PyObject* args, PyObject* /* keywords */ ) { + if (!PyArg_ParseTuple(args, const_cast(""))) + return 0; + return to_python(f()); + } + + template + static PyObject* call(R (*f)(A1), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("O"), &a1)) + return 0; + return to_python(f(from_python(a1, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OO"), &a1, &a2)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &a1, &a2, &a3)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &a1, &a2, &a3, &a4)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4, A5), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &a1, &a2, &a3, &a4, &a5)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4, A5, A6), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4, A5, A6, A7), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4, A5, A6, A7, A8), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4, A5, A6, A7, A8, A9), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()), + from_python(a10, type()))); + } + + template + static PyObject* call(R (*f)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + PyObject* a11; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10, &a11)) + return 0; + return to_python(f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()), + from_python(a10, type()), + from_python(a11, type()))); + } + +}; + +template <> +struct caller +{ + template + static PyObject* call(void (T::*pmf)(), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &a1)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &self, &a1, &a2, &a3, &a4)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &self, &a1, &a2, &a3, &a4, &a5)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6, A7), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8, A9), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()), + from_python(a10, type())); + return detail::none(); + } + + + template + static PyObject* call(void (T::*pmf)() const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &a1)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &self, &a1, &a2, &a3, &a4)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &self, &a1, &a2, &a3, &a4, &a5)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6, A7) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8, A9) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type())); + return detail::none(); + } + + template + static PyObject* call(void (T::*pmf)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOOO"), &self, &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10)) + return 0; + T& target = from_python(self, type()); + (target.*pmf)(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()), + from_python(a10, type())); + return detail::none(); + } + + + // Free functions + static PyObject* call(void (*f)(), PyObject* args, PyObject* /* keywords */ ) { + if (!PyArg_ParseTuple(args, const_cast(""))) + return 0; + f(); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("O"), &a1)) + return 0; + f(from_python(a1, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OO"), &a1, &a2)) + return 0; + f(from_python(a1, type()), + from_python(a2, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &a1, &a2, &a3)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &a1, &a2, &a3, &a4)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4, A5), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &a1, &a2, &a3, &a4, &a5)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4, A5, A6), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4, A5, A6, A7), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4, A5, A6, A7, A8), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4, A5, A6, A7, A8, A9), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()), + from_python(a10, type())); + return detail::none(); + } + + template + static PyObject* call(void (*f)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11), PyObject* args, PyObject* /* keywords */ ) { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + PyObject* a11; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10, &a11)) + return 0; + f(from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()), + from_python(a4, type()), + from_python(a5, type()), + from_python(a6, type()), + from_python(a7, type()), + from_python(a8, type()), + from_python(a9, type()), + from_python(a10, type()), + from_python(a11, type())); + return detail::none(); + } + +}; + +} + +#endif diff --git a/cast.h b/cast.h new file mode 100644 index 00000000..c5a59ade --- /dev/null +++ b/cast.h @@ -0,0 +1,81 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef CAST_DWA052500_H_ +# define CAST_DWA052500_H_ + +# include "wrap_python.h" +# include + +namespace python { + +namespace detail { + // The default way of converting a PyObject* or PyTypeObject* to a T* + template + struct downcast_traits + { + template + static T* cast(U* p) { return static_cast(p); } + }; + + inline PyTypeObject* as_base_object(const PyTypeObject*, PyObject* p) + { + return reinterpret_cast(p); + } + + inline PyObject* as_base_object(const PyObject*, PyObject* p) + { + return p; + } + + inline const PyTypeObject* as_base_object(const PyTypeObject*, const PyObject* p) + { + return reinterpret_cast(p); + } + + inline const PyObject* as_base_object(const PyObject*, const PyObject* p) + { + return p; + } +} // namespace detail + +// Convert a pointer to any type derived from PyObject or PyTypeObject to a PyObject* +inline PyObject* as_object(PyObject* p) { return p; } +inline PyObject* as_object(PyTypeObject* p) { return reinterpret_cast(p); } + +// If I didn't have to support stupid MSVC6 we could just use a simple template function: +// template T* downcast(PyObject*). +template +struct downcast : boost::dereferenceable, T*> +{ + downcast(PyObject* p) + : m_p(detail::downcast_traits::cast(detail::as_base_object((T*)0, p))) + {} + + downcast(const PyObject* p) + : m_p(detail::downcast_traits::cast(detail::as_base_object((const T*)0, p))) + {} + + downcast(PyTypeObject* p) + : m_p(detail::downcast_traits::cast(p)) + {} + + downcast(const PyTypeObject* p) + : m_p(detail::downcast_traits::cast(p)) + {} + + operator T*() const { return m_p; } + T* get() const { return m_p; } + T& operator*() const { return *m_p; } + private: + T* m_p; +}; + +} // namespace python + +#endif // CAST_DWA052500_H_ diff --git a/class_wrapper.h b/class_wrapper.h new file mode 100644 index 00000000..f378a6f6 --- /dev/null +++ b/class_wrapper.h @@ -0,0 +1,156 @@ +#ifndef CLASS_WRAPPER_DWA101000_H_ +# define CLASS_WRAPPER_DWA101000_H_ + +#include "extclass.h" +#include "operators.h" +#include "module.h" +#include "py.h" +#include "cast.h" +#include "pyptr.h" + +namespace python { + +// Syntactic sugar to make wrapping classes more convenient +template > +class class_builder + : python_extension_class_converters // Works around MSVC6.x/GCC2.95.2 bug described below +{ + public: + class_builder(module_builder& module, const char* name) + : m_class(new detail::extension_class(name)) + { + module.add(ref(as_object(m_class.get()), ref::increment_count), name); + } + + ~class_builder() + {} + + // define constructors + template + void def(const signature& signature) + { m_class->def(signature); } + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub), Foo>(), + // python::left_operand()); + template + void def(operators o1, left_operand o2) + { m_class->def(o1, o2); } + + // export heterogeneous operators (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub), Foo>(), + // python::right_operand()); + template + void def(operators o1, right_operand o2) + { m_class->def(o1, o2); } + + // define a function that passes Python arguments and keywords + // to C++ verbatim (as a 'tuple const &' and 'dictionary const &' + // respectively). This is useful for manual argument passing. + // It's also the only possibility to pass keyword arguments to C++. + // Fn must have a signatur that is compatible to + // PyObject * (*)(PyObject * aTuple, PyObject * aDictionary) + template + void def_raw(Fn fn, const char* name) + { m_class->def_raw(fn, name); } + + // define member functions. In fact this works for free functions, too - + // they act like static member functions, or if they start with the + // appropriate self argument (as a pointer or reference), they can be used + // just like ordinary member functions -- just like Python! + template + void def(Fn fn, const char* name) + { m_class->def(fn, name); } + + // Define a virtual member function with a default implementation. + // default_fn should be a function which provides the default implementation. + // Be careful that default_fn does not in fact call fn virtually! + template + void def(Fn fn, const char* name, DefaultFn default_fn) + { m_class->def(fn, name, default_fn); } + + // Provide a function which implements x., reading from the given + // member (pm) of the T obj + template + void def_getter(MemberType T::*pm, const char* name) + { m_class->def_getter(pm, name); } + + // Provide a function which implements assignment to x., writing to + // the given member (pm) of the T obj + template + void def_setter(MemberType T::*pm, const char* name) + { m_class->def_getter(pm, name); } + + // Expose the given member (pm) of the T obj as a read-only attribute + template + void def_readonly(MemberType T::*pm, const char* name) + { m_class->def_readonly(pm, name); } + + // Expose the given member (pm) of the T obj as a read/write attribute + template + void def_read_write(MemberType T::*pm, const char* name) + { m_class->def_read_write(pm, name); } + + // define the standard coercion needed for operator overloading + void def_standard_coerce() + { m_class->def_standard_coerce(); } + + // declare the given class a base class of this one and register + // conversion functions + template + void declare_base(class_builder const & base) + { + m_class->declare_base(base.get_extension_class()); + } + + // declare the given class a base class of this one and register + // upcast conversion function + template + void declare_base(class_builder const & base, without_downcast_t) + { + m_class->declare_base(base.get_extension_class(), without_downcast); + } + + // get the embedded ExtensioClass object + detail::extension_class * get_extension_class() const + { + return m_class.get(); + } + + // set an arbitrary attribute. Useful for non-function class data members, + // e.g. enums + void add(PyObject* x, const char* name) + { m_class->set_attribute(name, x); } + void add(ref x, const char* name) + { m_class->set_attribute(name, x); } + private: + // declare the given class a base class of this one and register + // conversion functions + template + void declare_base(detail::extension_class * base) + { + m_class->declare_base(base); + } + + // declare the given class a base class of this one and register + // upcast conversion function + template + void declare_base(detail::extension_class * base, without_downcast_t) + { + m_class->declare_base(base, without_downcast); + } + + reference > m_class; +}; + +// The bug mentioned at the top of this file is that on certain compilers static +// global functions declared within the body of a class template will only be +// generated when the class template is constructed, and when (for some reason) +// the construction does not occur via a new-expression. Otherwise, we could +// rely on the initialization of the m_class data member to cause all of the +// to_/from_python functions to come into being. + +} + +#endif // CLASS_WRAPPER_DWA101000_H_ diff --git a/comparisons.html b/comparisons.html new file mode 100644 index 00000000..6f6bf65a --- /dev/null +++ b/comparisons.html @@ -0,0 +1,220 @@ + + + Comparisons with Other Systems + +
+

+ c++boost.gif (8819 bytes)Comparisons with + Other Systems +

+ +

CXX

+

+ Like py_cpp, CXX attempts to + provide a C++-oriented interface to Python. In most cases, like py_cpp, + it relieves the user from worrying about reference-counts. As far as I + can tell, there is no support for subclassing C++ extension types in + Python. An even more-significant difference is that a user's C++ code is + still basically “dealing with Python objects”, though they are wrapped + in C++ classes. This means such jobs as argument parsing and conversion + are still left to be done explicitly by the user. + +

+ CXX claims to interoperate well with the C++ Standard Library + (a.k.a. STL) by providing iterators into Python Lists and Dictionaries, + but the claim is unfortunately unsupportable. The problem is that in + general, access to Python sequence and mapping elements through + iterators requires the use of proxy objects as the return value of + iterator dereference operations. This usage conflicts with the basic + ForwardIterator requirements in + section 24.1.3 of the standard (dereferencing must produce a + reference). Although you may be able to use these iterators with some + operations in some standard library implementations, it is neither + guaranteed to work nor portable. + +

+ As far as I can tell, CXX enables one to write what is essentially + idiomatic Python code in C++, manipulating Python objects through the + same fully-generic interfaces we use in Python. I think it would be fair + to say that while you're not programming directly to the “bare + metal” with CXX, in comparison to py_cpp, it presents a low-level + interface to Python. That use is also supported by the py_cpp object + wrappers. + +

+ Paul F. Dubois, the original + author of CXX, has told me that what I've described is only half of the + picture with CXX, but I never understood his explanation well-enough to + fill in the other half. Here is his response to the commentary above: + +

+“My intention with CXX was not to do what you are doing. It was to enable a +person to write an extension directly in C++ rather than C. I figured others had +the wrapping business covered. I thought maybe CXX would provide an easier +target language for those making wrappers, but I never explored +that.”
-Paul Dubois +
+ +

SWIG

+

+ SWIG is an impressively mature tool + for exporting an existing ANSI 'C' interface into various scripting + languages. Swig relies on a parser to read your source code and produce + additional source code files which can be compiled into a Python (or + Perl or Tcl) extension module. It has been successfully used to create + many Python extension modules. Like py_cpp, SWIG is trying to allow an + existing interface to be wrapped with little or no change to the + existing code. The documentation says “SWIG parses a form of ANSI C + syntax that has been extended with a number of special directives. As a + result, interfaces are usually built by grabbing a header file and + tweaking it a little bit.” For C++ interfaces, the tweaking has often + proven to amount to more than just a little bit. One user + writes: + +

“The problem with swig (when I used it) is that it + couldnt handle templates, didnt do func overloading properly etc. For + ANSI C libraries this was fine. But for usual C++ code this was a + problem. Simple things work. But for anything very complicated (or + realistic), one had to write code by hand. I believe py_cpp doesn't have + this problem[sic]... IMHO overloaded functions are very important to + wrap correctly.”
-Prabhu Ramachandran +
+ +

+ By contrast, py_cpp doesn't attempt to parse C++ - the problem is simply + too complex to do correctly. Technically, one does + write code by hand to use py_cpp. The goal, however, has been to make + that code nearly as simple as listing the names of the classes and + member functions you want to expose in Python. + +

SIP

+

+ SIP + is a system similar to SWIG, though seemingly more + C++-oriented. The author says that like py_cpp, SIP supports overriding + extension class member functions in Python subclasses. It appears to + have been designed specifically to directly support some features of + PyQt/PyKDE, which is its primary client. Documentation is almost + entirely missing at the time of this writing, so a detailed comparison + is difficult. + +

ILU

+

+ ILU + is a very ambitious project which tries to describe a module's interface + (types and functions) in terms of an Interface + Specification Language (ISL) so that it can be uniformly interfaced + to a wide range of computer languages, including Common Lisp, C++, C, + Modula-3, and Python. ILU can parse the ISL to generate a C++ language + header file describing the interface, of which the user is expected to + provide an implementation. Unlike py_cpp, this means that the system + imposes implementation details on your C++ code at the deepest level. It + is worth noting that some of the C++ names generated by ILU are supposed + to be reserved to the C++ implementation. It is unclear from the + documentation whether ILU supports overriding C++ virtual functions in Python. + +

GRAD

+

+ GRAD + is another very ambitious project aimed at generating Python wrappers for + interfaces written in “legacy languages”, among which C++ is the first one + implemented. Like SWIG, it aims to parse source code and automatically + generate wrappers, though it appears to take a more sophisticated approach + to parsing in general and C++ in particular, so it should do a much better + job with C++. It appears to support function overloading. The + documentation is missing a lot of information I'd like to see, so it is + difficult to give an accurate and fair assessment. I am left with the + following questions: +

    +
  • Does it support overriding of virtual functions? +
  • What about overriding private or protected virtual functions (the documentation indicates +that only public interfaces are supported)? +
  • Which C++ language constructs are supportd? +
  • Does it support implicit conversions between wrapped C++ classes that have +an inheritance relationship? +
  • Does it support smart pointers? +
+

+ Anyone in the possession of the answers to these questions will earn my + gratitude for a write-up ;-) + +

Zope ExtensionClasses

+

+ + ExtensionClasses in Zope use the same underlying mechanism as py_cpp + to support subclassing of extension types in Python, including + multiple-inheritance. Both systems support pickling/unpickling of + extension class instances in very similar ways. Both systems rely on the + same “Don + Beaudry Hack” that also inspired Don's MESS System. +

+ The major differences are: +

    +
  • + py_cpp lifts the burden on the user to parse and convert function + argument types. Zope provides no such facility. +
  • + py_cpp lifts the burden on the user to maintain Python + reference-counts. +
  • + py_cpp supports function overloading; Zope does not. +
  • + py_cpp supplies a simple mechanism for exposing read-only and + read/write access to data members of the wrapped C++ type as Python + attributes. +
  • + Writing a Zope ExtensionClass is significantly more complex than + exposing a C++ class to python using py_cpp (mostly a summary of the + previous 4 items). A + Zope Example illustrates the differences. +
  • + Zope's ExtensionClasses are specifically motivated by “the need for a + C-based persistence mechanism”. Py_cpp's are motivated by the desire + to simply reflect a C++ API into Python with as little modification as + possible. +
  • + The following Zope restriction does not apply to py_cpp: “At most one + base extension direct or indirect super class may define C data + members. If an extension subclass inherits from multiple base + extension classes, then all but one must be mix-in classes that + provide extension methods but no data.” +
  • + Zope requires use of the somewhat funky inheritedAttribute (search for + “inheritedAttribute” on this page) + method to access base class methods. In py_cpp, base class methods can + be accessed in the usual way by writing + “BaseClass.method”. +
  • + Zope supplies some creative but esoteric idioms such as + Acquisition. No specific support for this is built into py_cpp. +
  • + Zope's ComputedAttribute support is designed to be used from Python. + The analogous feature of + py_cpp can be used from C++ or Python. The feature is arguably + easier to use in py_cpp. +
+

+ Previous: A Brief Introduction to writing Python Extension Modules + Next: A Simple Example Using py_cpp + 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: Nov 25, 2000 +

+ diff --git a/data_structures.txt b/data_structures.txt new file mode 100644 index 00000000..90e41b91 --- /dev/null +++ b/data_structures.txt @@ -0,0 +1,192 @@ +Given a real Python class 'A', a wrapped C++ class 'B', and this definition: + + class C(A, B): + def __init__(self): + B.__init__(self) + self.x = 1 + ... + + c = C() + +this diagram describes the internal structure of an instance of 'C', including +its inheritance relationships. Note that ExtensionClass is derived from +Class, and is in fact identical for all intents and purposes. + + MetaClass + +---------+ +---------+ +types.ClassType: | | | | + | | | | + | | | | + +---------+ +---------+ + ^ ^ ^ + PyClassObject | ExtensionClass | | + A: +------------+ | B: +------------+ | | + | ob_type -+-+ | ob_type -+-----+ | + | | ()<--+- __bases__ | | + | | | __dict__ -+->{...} | + | | 'B'<-+- __name__ | | + +------------+ +------------+ | + ^ ^ | + | | | + +-----+ +-------------+ | + | | | + | | Class | + | | C: +------------+ | + | | | ob_type -+------------+ + tuple:(*, *)<--+- __bases__ | + | __dict__ -+->{__module__, } + 'C' <-+- __name__ | + +------------+ + ^ (in case of inheritance from more than one + | extension class, this vector would contain + +---------------+ a pointer to an instance holder for the data + | of each corresponding C++ class) + | ExtensionInstance + | c: +---------------------+ std::vector + +----+- __class__ | +---+-- + | m_wrapped_objects -+->| * | ... + {'x': 1}<-+- __dict__ | +-|-+-- + +---------------------+ | InstanceValueHolder + | +--------------------------------+ + +-->| (contains a C++ instance of B) | + +--------------------------------+ + + + + + + +In our inheritance test cases in extclass_demo.cpp/test_extclass.py, we have the +following C++ inheritance hierarchy: + + +-----+ +----+ + | A1 | | A2 | + +-----+ +----+ + ^ ^ ^ ^ ^ + | | | | | + +-----+ | +---------+-----+ + | | | | + | +---+----------+ + .......!...... | | + : A_callback : +-+--+ +-+--+ + :............: | B1 | | B2 | + +----+ +----+ + ^ + | + +-------+---------+ + | | + +-+-+ ......!....... + | C | : B_callback : + +---+ :............: + + +A_callback and B_callback are used as part of the wrapping mechanism but not +represented in Python. C is also not represented in Python but is delivered +there polymorphically through a smart pointer. + +This is the data structure in Python. + + ExtensionClass + A1: +------------+ + ()<--+- __bases__ | + | __dict__ -+->{...} + +------------+ + ^ + | ExtensionInstance + | a1: +---------------------+ vec InstanceValueHolder + +---------+- __class__ | +---+ +---------------------+ + | | m_wrapped_objects -+->| *-+-->| contains A_callback | + | +---------------------+ +---+ +---------------------+ + | + | ExtensionInstance + | pa1_a1: +---------------------+ vec InstancePtrHolder,A1> + +---------+- __class__ | +---+ +---+ + | | m_wrapped_objects -+->| *-+-->| *-+-+ A1 + | +---------------------+ +---+ +---+ | +---+ + | +->| | + | ExtensionInstance +---+ + | pb1_a1: +---------------------+ vec InstancePtrHolder,A1> + +---------+- __class__ | +---+ +---+ + | | m_wrapped_objects -+->| *-+-->| *-+-+ B1 + | +---------------------+ +---+ +---+ | +---+ + | +->| | + | ExtensionInstance +---+ + | pb2_a1: +---------------------+ vec InstancePtrHolder,A1> + +---------+- __class__ | +---+ +---+ + | | m_wrapped_objects -+->| *-+-->| *-+-+ B2 + | +---------------------+ +---+ +---+ | +---+ + | +->| | + | +---+ + | ExtensionClass + | A2: +------------+ + | ()<--+- __bases__ | + | | __dict__ -+->{...} + | +------------+ + | ^ + | | ExtensionInstance + | a2: | +---------------------+ vec InstanceValueHolder + | +-+- __class__ | +---+ +-------------+ + | | | m_wrapped_objects -+->| *-+-->| contains A2 | + | | +---------------------+ +---+ +-------------+ + | | + | | ExtensionInstance + | pa2_a2: | +---------------------+ vec InstancePtrHolder,A2> + | +-+- __class__ | +---+ +---+ + | | | m_wrapped_objects -+->| *-+-->| *-+-+ A2 + | | +---------------------+ +---+ +---+ | +---+ + | | +->| | + | | ExtensionInstance +---+ + | pb1_a2: | +---------------------+ vec InstancePtrHolder,A2> + | +-+- __class__ | +---+ +---+ + | | | m_wrapped_objects -+->| *-+-->| *-+-+ B1 + | | +---------------------+ +---+ +---+ | +---+ + | | +->| | + | | +---+ + | | + | +---------------+------------------------------+ + | | | + +------+-------------------------+-|----------------------------+ | + | | | | | + | Class | | ExtensionClass | | ExtensionClass + | DA1: +------------+ | | B1: +------------+ | | B2: +------------+ +(*,)<---+- __bases__ | (*,*)<---+- __bases__ | (*,*)<---+- __bases__ | + | __dict__ -+->{...} | __dict__ -+->{...} | __dict__ -+->{...} + +------------+ +------------+ +------------+ + ^ ^ ^ + | ExtensionInstance | | + | da1: +---------------------+ | vec InstanceValueHolder + +-------+- __class__ | | +---+ +---------------------+ | + | m_wrapped_objects -+--|-->| *-+-->| contains A_callback | | + +---------------------+ | +---+ +---------------------+ | + +--------------------------------------+ | + | ExtensionInstance | + b1: | +---------------------+ vec InstanceValueHolder | + +-+- __class__ | +---+ +---------------------+ | + | | m_wrapped_objects -+->| *-+-->| contains B_callback | | + | +---------------------+ +---+ +---------------------+ | + | | + | ExtensionInstance | +pb1_b1: | +---------------------+ vec InstancePtrHolder,B1> | + +-+- __class__ | +---+ +---+ | + | | m_wrapped_objects -+->| *-+-->| *-+-+ B1 | + | +---------------------+ +---+ +---+ | +---+ | + | +->| | | + | ExtensionInstance +---+ | + pc_b1: | +---------------------+ vec InstancePtrHolder,B1> | + +-+- __class__ | +---+ +---+ | + | | m_wrapped_objects -+->| *-+-->| *-+-+ C | + | +---------------------+ +---+ +---+ | +---+ | + | +->| | | + | +---+ | + | | + | Class +---------------------------------------+ + | DB1: +------------+ | ExtensionInstance + (*,)<---+- __bases__ | a2: | +---------------------+ vec InstanceValueHolder + | __dict__ -+->{...} +-+- __class__ | +---+ +-------------+ + +------------+ | m_wrapped_objects -+->| *-+-->| contains A2 | + ^ +---------------------+ +---+ +-------------+ + | ExtensionInstance + db1: | +---------------------+ vec InstanceValueHolder + +-+- __class__ | +---+ +----------------------+ + | m_wrapped_objects -+-->| *-+-->| contains B1_callback | + +---------------------+ +---+ +----------------------+ diff --git a/doctest.py b/doctest.py new file mode 100644 index 00000000..248da82a --- /dev/null +++ b/doctest.py @@ -0,0 +1,1112 @@ +# Module doctest version 0.9.4 +# Released to the public domain 27-Mar-1999, +# by Tim Peters (tim_one@email.msn.com). + +# Provided as-is; use at your own risk; no warranty; no promises; enjoy! + +"""module_builder doctest -- a framework for running examples in docstrings. + +NORMAL USAGE + +In normal use, end each module M with: + +def _test(): + import doctest, M # replace M with your module's name + return doctest.testmod(M) # ditto + +if __name__ == "__main__": + _test() + +Then running the module as a script will cause the examples in the +docstrings to get executed and verified: + +python M.python + +This won't display anything unless an example fails, in which case +the failing example(s) and the cause(s) of the failure(s) are printed +to stdout (why not stderr? because stderr is a lame hack <0.2 wink>), +and the final line of output is "Test failed.". + +Run it with the -v switch instead: + +python M.python -v + +and a detailed report of all examples tried is printed to stdout, along +with assorted summaries at the end. + +You can force verbose mode by passing "verbose=1" to testmod, or prohibit +it by passing "verbose=0". In either of those cases, sys.argv is not +examined by testmod. + +In any case, testmod returns a 2-tuple of ints (f, t), where f is the +number of docstring examples that failed and t is the total number of +docstring examples attempted. + + +WHICH DOCSTRINGS ARE EXAMINED? + ++ M.__doc__. + ++ f.__doc__ for all functions f in M.__dict__.values(), except those + with private names. + ++ C.__doc__ for all classes C in M.__dict__.values(), except those with + private names. + ++ If M.__test__ exists and "is true", it must be a dict, and + each entry maps a (string) name to a function object, class object, or + string. function and class object docstrings found from M.__test__ + are searched even if the name is private, and strings are searched + directly as if they were docstrings. In output, a key K in M.__test__ + appears with name + .__test__.K + +Any classes found are recursively searched similarly, to test docstrings +in their contained methods and nested classes. Private names reached +from M's globals are skipped, but all names reached from M.__test__ are +searched. + +By default, a name is considered to be private if it begins with an +underscore (like "_my_func") but doesn't both begin and end with (at +least) two underscores (like "__init__"). You can change the default +by passing your own "isprivate" function to testmod. + +If you want to test docstrings in objects with private names too, stuff +them into an M.__test__ dict, or see ADVANCED USAGE below (e.g., pass your +own isprivate function to Tester's constructor, or call the rundoc method +of a Tester obj). + +Warning: imports can cause trouble; e.g., if you do + +from XYZ import XYZclass + +then XYZclass is a name in M.__dict__ too, and doctest has no way to +know that XYZclass wasn't *defined* in M. So it may try to execute the +examples in XYZclass's docstring, and those in turn may require a +different set of globals to work correctly. I prefer to do "import *"- +friendly imports, a la + +import XYY +_XYZclass = XYZ.XYZclass +del XYZ + +and then the leading underscore stops testmod from going nuts. You may +prefer the method in the next section. + + +WHAT'S THE EXECUTION CONTEXT? + +By default, each time testmod finds a docstring to test, it uses a +*copy* of M's globals (so that running tests on a module doesn't change +the module's real globals, and so that one test in M can't leave behind +crumbs that accidentally allow another test to work). This means +examples can freely use any names defined at top-level in M. It also +means that sloppy imports (see above) can cause examples in external +docstrings to use globals inappropriate for them. + +You can force use of your own dict as the execution context by passing +"globs=your_dict" to testmod instead. Presumably this would be a copy +of M.__dict__ merged with the globals from other imported modules. + + +WHAT IF I WANT TO TEST A WHOLE PACKAGE? + +Piece o' cake, provided the modules do their testing from docstrings. +Here's the test.python I use for the world's most elaborate Rational/ +floating-base-conversion pkg (which I'll distribute some day): + +from Rational import Cvt +from Rational import Format +from Rational import machprec +from Rational import Rat +from Rational import Round +from Rational import utils + +modules = (Cvt, + Format, + machprec, + Rat, + Round, + utils) + +def _test(): + import doctest + import sys + verbose = "-v" in sys.argv + for mod in modules: + doctest.testmod(mod, verbose=verbose, report=0) + doctest.master.summarize() + +if __name__ == "__main__": + _test() + +IOW, it just runs testmod on all the pkg modules. testmod remembers the +names and outcomes (# of failures, # of tries) for each item it's seen, +and passing "report=0" prevents it from printing a summary in verbose +mode. Instead, the summary is delayed until all modules have been +tested, and then "doctest.master.summarize()" forces the summary at the +end. + +So this is very nice in practice: each module can be tested individually +with almost no work beyond writing up docstring examples, and collections +of modules can be tested too as a unit with no more work than the above. + + +WHAT ABOUT EXCEPTIONS? + +No problem, as long as the only output generated by the example is the +traceback itself. For example: + + >>> 1/0 + Traceback (innermost last): + File "", line 1, in ? + ZeroDivisionError: integer division or modulo + >>> + +Note that only the exception type and value are compared (specifically, +only the last line in the traceback). + + +ADVANCED USAGE + +doctest.testmod() captures the testing policy I find most useful most +often. You may want other policies. + +testmod() actually creates a local obj of class doctest.Tester, +runs appropriate methods of that class, and merges the results into +global Tester obj doctest.master. + +You can create your own instances of doctest.Tester, and so build your +own policies, or even run methods of doctest.master directly. See +doctest.Tester.__doc__ for details. + + +SO WHAT DOES A DOCSTRING EXAMPLE LOOK LIKE ALREADY!? + +Oh ya. It's easy! In most cases a copy-and-paste of an interactive +console session works fine -- just make sure the leading whitespace +is rigidly consistent (you can mix tabs and spaces if you're too lazy +to do it right, but doctest is not in the business of guessing what +you think a tab means). + + >>> # comments are ignored + >>> x = 12 + >>> x + 12 + >>> if x == 13: + ... print "yes" + ... else: + ... print "no" + ... print "NO" + ... print "NO!!!" + ... + no + NO + NO!!! + >>> + +Any expected output must immediately follow the final ">>>" or "..." +line containing the code, and the expected output (if any) extends +to the next ">>>" or all-whitespace line. That's it. + +Bummers: + ++ Expected output cannot contain an all-whitespace line, since such a + line is taken to signal the end of expected output. + ++ Output to stdout is captured, but not output to stderr (exception + tracebacks are captured via a different means). + ++ If you continue a line via backslashing in an interactive session, + or for any other reason use a backslash, you need to double the + backslash in the docstring version. This is simply because you're + in a string, and so the backslash must be escaped for it to survive + intact. Like: + +>>> if "yes" == \\ +... "y" + \\ +... "es": # in the source code you'll see the doubled backslashes +... print 'yes' +yes + +The starting column doesn't matter: + +>>> assert "Easy!" + >>> import math + >>> math.floor(1.9) + 1.0 + +and as many leading whitespace characters are stripped from the expected +output as appeared in the initial ">>>" line that triggered it. + +If you execute this very file, the examples above will be found and +executed, leading to this output in verbose mode: + +Running doctest.__doc__ +Trying: 1/0 +Expecting: +Traceback (innermost last): + File "", line 1, in ? +ZeroDivisionError: integer division or modulo +ok +Trying: x = 12 +Expecting: nothing +ok +Trying: x +Expecting: 12 +ok +Trying: +if x == 13: + print "yes" +else: + print "no" + print "NO" + print "NO!!!" +Expecting: +no +NO +NO!!! +ok +... and a bunch more like that, with this summary at the end: + +5 items had no tests: + doctest.Tester.__init__ + doctest.Tester.run__test__ + doctest.Tester.summarize + doctest.run_docstring_examples + doctest.testmod +12 items passed all tests: + 8 tests in doctest + 6 tests in doctest.Tester + 10 tests in doctest.Tester.merge + 7 tests in doctest.Tester.rundict + 3 tests in doctest.Tester.rundoc + 3 tests in doctest.Tester.runstring + 2 tests in doctest.__test__._TestClass + 2 tests in doctest.__test__._TestClass.__init__ + 2 tests in doctest.__test__._TestClass.get + 1 tests in doctest.__test__._TestClass.square + 2 tests in doctest.__test__.string + 7 tests in doctest.is_private +53 tests in 17 items. +53 passed and 0 failed. +Test passed. +""" + +# 0,0,1 06-Mar-1999 +# initial version posted +# 0,0,2 06-Mar-1999 +# loosened parsing: +# cater to stinkin' tabs +# don't insist on a blank after PS2 prefix +# so trailing "... " line from a compound stmt no longer +# breaks if the file gets whitespace-trimmed +# better error msgs for inconsistent leading whitespace +# 0,9,1 08-Mar-1999 +# exposed the Tester class and added client methods +# plus docstring examples of their use (eww - head-twisting!) +# fixed logic error in reporting total # of tests & failures +# added __test__ support to testmod (a pale reflection of Christian +# Tismer's vision ...) +# removed the "deep" argument; fiddle __test__ instead +# simplified endcase logic for extracting tests, and running them. +# before, if no output was expected but some was produced +# anyway via an eval'ed result, the discrepancy wasn't caught +# made TestClass private and used __test__ to get at it +# many doc updates +# speed _SpoofOut for long expected outputs +# 0,9,2 09-Mar-1999 +# throw out comments from examples, enabling use of the much simpler +# exec compile(... "single") ... +# for simulating the runtime; that barfs on comment-only lines +# used the traceback module to do a much better job of reporting +# exceptions +# run __doc__ values thru str(), "just in case" +# privateness of names now determined by an overridable "isprivate" +# function +# by default a name now considered to be private iff it begins with +# an underscore but doesn't both begin & end with two of 'em; so +# e.g. class_t.__init__ etc are searched now -- as they always +# should have been +# 0,9,3 18-Mar-1999 +# added .flush stub to _SpoofOut (JPython buglet diagnosed by +# Hugh Emberson) +# repaired ridiculous docs about backslashes in examples +# minor internal changes +# changed source to Unix line-end conventions +# moved __test__ logic into new Tester.run__test__ method +# 0,9,4 27-Mar-1999 +# report item name and line # in failing examples +# 0,9,5 29-Jun-1999 +# allow straightforward exceptions in examples - thanks to Mark Hammond! +# 0,9,5,1 31-Mar-2000 +# break cyclic references to functions which are defined in docstrings, +# avoiding cyclic trash +# 0,9,5,2 11-Apr-2000 +# made module argument to testmod optional; it runs testmod on the __main__ +# module in that case. + +__version__ = 0, 9, 5 + +import types +_FunctionType = types.FunctionType +_ClassType = types.ClassType +_ModuleType = types.ModuleType +_StringType = types.StringType +del types + +import string +_string_find = string.find +_string_join = string.join +_string_split = string.split +_string_rindex = string.rindex +del string + +import re +PS1 = ">>>" +PS2 = "..." +_isPS1 = re.compile(r"(\s*)" + re.escape(PS1)).match +_isPS2 = re.compile(r"(\s*)" + re.escape(PS2)).match +_isEmpty = re.compile(r"\s*$").match +_isComment = re.compile(r"\s*#").match +del re + +# Extract interactive examples from a string. Return a list of triples, +# (source, outcome, lineno). "source" is the source code, and ends +# with a newline iff the source spans more than one line. "outcome" is +# the expected output if any, else an empty string. When not empty, +# outcome always ends with a newline. "lineno" is the line number, +# 0-based wrt the start of the string, of the first source line. + +def _extract_examples(s): + isPS1, isPS2 = _isPS1, _isPS2 + isEmpty, isComment = _isEmpty, _isComment + examples = [] + lines = _string_split(s, "\n") + i, n = 0, len(lines) + while i < n: + line = lines[i] + i = i + 1 + m = isPS1(line) + if m is None: + continue + j = m.end(0) # beyond the prompt + if isEmpty(line, j) or isComment(line, j): + # a bare prompt or comment -- not interesting + continue + lineno = i - 1 + if line[j] != " ": + raise ValueError("line " + `lineno` + " of docstring lacks " + "blank after " + PS1 + ": " + line) + j = j + 1 + blanks = m.group(1) + nblanks = len(blanks) + # suck up this and following PS2 lines + source = [] + while 1: + source.append(line[j:]) + line = lines[i] + m = isPS2(line) + if m: + if m.group(1) != blanks: + raise ValueError("inconsistent leading whitespace " + "in line " + `i` + " of docstring: " + line) + i = i + 1 + else: + break + if len(source) == 1: + source = source[0] + else: + # get rid of useless null line from trailing empty "..." + if source[-1] == "": + del source[-1] + source = _string_join(source, "\n") + "\n" + # suck up response + if isPS1(line) or isEmpty(line): + expect = "" + else: + expect = [] + while 1: + if line[:nblanks] != blanks: + raise ValueError("inconsistent leading whitespace " + "in line " + `i` + " of docstring: " + line) + expect.append(line[nblanks:]) + i = i + 1 + line = lines[i] + if isPS1(line) or isEmpty(line): + break + expect = _string_join(expect, "\n") + "\n" + examples.append( (source, expect, lineno) ) + return examples + +# Capture stdout when running examples. + +class _SpoofOut: + def __init__(self): + self.clear() + def write(self, s): + self.buf.append(s) + def get(self): + return _string_join(self.buf, "") + def clear(self): + self.buf = [] + def flush(self): + # JPython calls flush + pass + +# Display some tag-and-msg pairs nicely, keeping the tag and its msg +# on the same line when that makes sense. + +def _tag_out(printer, *tag_msg_pairs): + for tag, msg in tag_msg_pairs: + printer(tag + ":") + msg_has_nl = msg[-1:] == "\n" + msg_has_two_nl = msg_has_nl and \ + _string_find(msg, "\n") < len(msg) - 1 + if len(tag) + len(msg) < 76 and not msg_has_two_nl: + printer(" ") + else: + printer("\n") + printer(msg) + if not msg_has_nl: + printer("\n") + +# Run list of examples, in context globs. "out" can be used to display +# stuff to "the real" stdout, and fakeout is an obj of _SpoofOut +# that captures the examples' std output. Return (#failures, #tries). + +def _run_examples_inner(out, fakeout, examples, globs, verbose, name): + import sys, traceback + OK, BOOM, FAIL = range(3) + NADA = "nothing" + stderr = _SpoofOut() + failures = 0 + for source, want, lineno in examples: + if verbose: + _tag_out(out, ("Trying", source), + ("Expecting", want or NADA)) + fakeout.clear() + try: + exec compile(source, "", "single") in globs + got = fakeout.get() + state = OK + except: + # See whether the exception was expected. + if _string_find(want, "Traceback (innermost last):\n") == 0: + # Only compare exception type and value - the rest of + # the traceback isn't necessary. + want = _string_split(want, '\n')[-2] + '\n' + exc_type, exc_val, exc_tb = sys.exc_info() + got = traceback.format_exception_only(exc_type, exc_val)[0] + state = OK + else: + # unexpected exception + stderr.clear() + traceback.print_exc(file=stderr) + state = BOOM + + if state == OK: + if got == want: + if verbose: + out("ok\n") + continue + state = FAIL + + assert state in (FAIL, BOOM) + failures = failures + 1 + out("*" * 65 + "\n") + _tag_out(out, ("Failure in example", source)) + out("from line #" + `lineno` + " of " + name + "\n") + if state == FAIL: + _tag_out(out, ("Expected", want or NADA), ("Got", got)) + else: + assert state == BOOM + _tag_out(out, ("Exception raised", stderr.get())) + return failures, len(examples) + +# Run list of examples, in context globs. Return (#failures, #tries). + +def _run_examples(examples, globs, verbose, name): + import sys + saveout = sys.stdout + try: + sys.stdout = fakeout = _SpoofOut() + x = _run_examples_inner(saveout.write, fakeout, examples, + globs, verbose, name) + finally: + sys.stdout = saveout + return x + +def run_docstring_examples(f, globs, verbose=0, name="NoName"): + """f, globs, verbose=0, name="NoName" -> run examples from f.__doc__. + + Use dict globs as the globals for execution. + Return (#failures, #tries). + + If optional arg verbose is true, print stuff even if there are no + failures. + Use string name in failure msgs. + """ + + try: + doc = f.__doc__ + if not doc: + # docstring empty or None + return 0, 0 + # just in case CT invents a doc object that has to be forced + # to look like a string <0.9 wink> + doc = str(doc) + except: + return 0, 0 + + e = _extract_examples(doc) + if not e: + return 0, 0 + return _run_examples(e, globs, verbose, name) + +def is_private(prefix, base): + """prefix, base -> true iff name prefix + "." + base is "private". + + Prefix may be an empty string, and base does not contain a period. + Prefix is ignored (although functions you write conforming to this + protocol may make use of it). + Return true iff base begins with an (at least one) underscore, but + does not both begin and end with (at least) two underscores. + + >>> is_private("a.b", "my_func") + 0 + >>> is_private("____", "_my_func") + 1 + >>> is_private("someclass", "__init__") + 0 + >>> is_private("sometypo", "__init_") + 1 + >>> is_private("x.y.z", "_") + 1 + >>> is_private("_x.y.z", "__") + 0 + >>> is_private("", "") # senseless but consistent + 0 + """ + + return base[:1] == "_" and not base[:2] == "__" == base[-2:] + +class Tester: + """class_t Tester -- runs docstring examples and accumulates stats. + +In normal use, function doctest.testmod() hides all this from you, +so use that if you can. Create your own instances of Tester to do +fancier things. + +Methods: + runstring(s, name) + Search string s for examples to run; use name for logging. + Return (#failures, #tries). + + rundoc(object, name=None) + Search object.__doc__ for examples to run; use name (or + object.__name__) for logging. Return (#failures, #tries). + + rundict(d, name) + Search for examples in docstrings in all of d.values(); use name + for logging. Return (#failures, #tries). + + run__test__(d, name) + Treat dict d like module.__test__. Return (#failures, #tries). + + summarize(verbose=None) + Display summary of testing results, to stdout. Return + (#failures, #tries). + + merge(other) + Merge in the test results from Tester obj "other". + +>>> from doctest import Tester +>>> t = Tester(globs={'x': 42}, verbose=0) +>>> t.runstring(r''' +... >>> x = x * 2 +... >>> print x +... 42 +... ''', 'XYZ') +***************************************************************** +Failure in example: print x +from line #2 of XYZ +Expected: 42 +Got: 84 +(1, 2) +>>> t.runstring(">>> x = x * 2\\n>>> print x\\n84\\n", 'example2') +(0, 2) +>>> t.summarize() +1 items had failures: + 1 of 2 in XYZ +***Test Failed*** 1 failures. +(1, 4) +>>> t.summarize(verbose=1) +1 items passed all tests: + 2 tests in example2 +1 items had failures: + 1 of 2 in XYZ +4 tests in 2 items. +3 passed and 1 failed. +***Test Failed*** 1 failures. +(1, 4) +>>> +""" + + def __init__(self, mod=None, globs=None, verbose=None, + isprivate=None): + """mod=None, globs=None, verbose=None, isprivate=None + +See doctest.__doc__ for an overview. + +Optional keyword arg "mod" is a module, whose globals are used for +executing examples. If not specified, globs must be specified. + +Optional keyword arg "globs" gives a dict to be used as the globals +when executing examples; if not specified, use the globals from +module mod. + +In either case, a copy of the dict is used for each docstring +examined. + +Optional keyword arg "verbose" prints lots of stuff if true, only +failures if false; by default, it's true iff "-v" is in sys.argv. + +Optional keyword arg "isprivate" specifies a function used to determine +whether a name is private. The default function is doctest.is_private; +see its docs for details. +""" + + if mod is None and globs is None: + raise TypeError("Tester.__init__: must specify mod or globs") + if mod is not None and type(mod) is not _ModuleType: + raise TypeError("Tester.__init__: mod must be a module; " + + `mod`) + if globs is None: + globs = mod.__dict__ + self.globs = globs + + if verbose is None: + import sys + verbose = "-v" in sys.argv + self.verbose = verbose + + if isprivate is None: + isprivate = is_private + self.isprivate = isprivate + + self.name2ft = {} # map name to (#failures, #trials) pair + + def runstring(self, s, name): + """ + s, name -> search string s for examples to run, logging as name. + + Use string name as the key for logging the outcome. + Return (#failures, #examples). + + >>> t = Tester(globs={}, verbose=1) + >>> test = r''' + ... # just an example + ... >>> x = 1 + 2 + ... >>> x + ... 3 + ... ''' + >>> t.runstring(test, "Example") + Running string Example + Trying: x = 1 + 2 + Expecting: nothing + ok + Trying: x + Expecting: 3 + ok + 0 of 2 examples failed in string Example + (0, 2) + """ + + if self.verbose: + print "Running string", name + f = t = 0 + e = _extract_examples(s) + if e: + globs = self.globs.copy() + f, t = _run_examples(e, globs, self.verbose, name) + globs.clear() # DWA - break cyclic references to functions defined in docstrings + if self.verbose: + print f, "of", t, "examples failed in string", name + self.__record_outcome(name, f, t) + return f, t + + def rundoc(self, object, name=None): + """ + object, name=None -> search object.__doc__ for examples to run. + + Use optional string name as the key for logging the outcome; + by default use object.__name__. + Return (#failures, #examples). + If object is a class object, search recursively for method + docstrings too. + object.__doc__ is examined regardless of name, but if object is + a class, whether private names reached from object are searched + depends on the constructor's "isprivate" argument. + + >>> t = Tester(globs={}, verbose=0) + >>> def _f(): + ... '''Trivial docstring example. + ... >>> assert 2 == 2 + ... ''' + ... return 32 + ... + >>> t.rundoc(_f) # expect 0 failures in 1 example + (0, 1) + """ + + if name is None: + try: + name = object.__name__ + except AttributeError: + raise ValueError("Tester.rundoc: name must be given " + "when object.__name__ doesn't exist; " + `object`) + if self.verbose: + print "Running", name + ".__doc__" + globs = self.globs.copy() + f, t = run_docstring_examples(object, globs, + self.verbose, name) + globs.clear() # DWA - break cyclic references to functions defined in docstrings + + if self.verbose: + print f, "of", t, "examples failed in", name + ".__doc__" + self.__record_outcome(name, f, t) + if type(object) is _ClassType: + f2, t2 = self.rundict(object.__dict__, name) + f = f + f2 + t = t + t2 + return f, t + + def rundict(self, d, name): + """ + d. name -> search for docstring examples in all of d.values(). + + For k, v in d.items() such that v is a function or class, + do self.rundoc(v, name + "." + k). Whether this includes + objects with private names depends on the constructor's + "isprivate" argument. + Return aggregate (#failures, #examples). + + >>> def _f(): + ... '''>>> assert 1 == 1 + ... ''' + >>> def g(): + ... '''>>> assert 2 != 1 + ... ''' + >>> d = {"_f": _f, "g": g} + >>> t = Tester(globs={}, verbose=0) + >>> t.rundict(d, "rundict_test") # _f is skipped + (0, 1) + >>> t = Tester(globs={}, verbose=0, isprivate=lambda x,y: 0) + >>> t.rundict(d, "rundict_test_pvt") # both are searched + (0, 2) + """ + + if not hasattr(d, "items"): + raise TypeError("Tester.rundict: d must support .items(); " + + `d`) + f = t = 0 + for thisname, value in d.items(): + if type(value) in (_FunctionType, _ClassType): + f2, t2 = self.__runone(value, name + "." + thisname) + f = f + f2 + t = t + t2 + return f, t + + def run__test__(self, d, name): + """d, name -> Treat dict d like module.__test__. + + Return (#failures, #tries). + See testmod.__doc__ for details. + """ + + failures = tries = 0 + prefix = name + "." + savepvt = self.isprivate + try: + self.isprivate = lambda *args: 0 + for k, v in d.items(): + thisname = prefix + k + if type(v) is _StringType: + f, t = self.runstring(v, thisname) + elif type(v) in (_FunctionType, _ClassType): + f, t = self.rundoc(v, thisname) + else: + raise TypeError("Tester.run__test__: values in " + "dict must be strings, functions " + "or classes; " + `v`) + failures = failures + f + tries = tries + t + finally: + self.isprivate = savepvt + return failures, tries + + def summarize(self, verbose=None): + """ + verbose=None -> summarize results, return (#failures, #tests). + + Print summary of test results to stdout. + Optional arg 'verbose' controls how wordy this is. By + default, use the verbose setting established by the + constructor. + """ + + if verbose is None: + verbose = self.verbose + notests = [] + passed = [] + failed = [] + totalt = totalf = 0 + for x in self.name2ft.items(): + name, (f, t) = x + assert f <= t + totalt = totalt + t + totalf = totalf + f + if t == 0: + notests.append(name) + elif f == 0: + passed.append( (name, t) ) + else: + failed.append(x) + if verbose: + if notests: + print len(notests), "items had no tests:" + notests.sort() + for thing in notests: + print " ", thing + if passed: + print len(passed), "items passed all tests:" + passed.sort() + for thing, count in passed: + print " %3d tests in %s" % (count, thing) + if failed: + print len(failed), "items had failures:" + failed.sort() + for thing, (f, t) in failed: + print " %3d of %3d in %s" % (f, t, thing) + if verbose: + print totalt, "tests in", len(self.name2ft), "items." + print totalt - totalf, "passed and", totalf, "failed." + if totalf: + print "***Test Failed***", totalf, "failures." + elif verbose: + print "Test passed." + return totalf, totalt + + def merge(self, other): + """ + other -> merge in test results from the other Tester obj. + + If self and other both have a test result for something + with the same name, the (#failures, #tests) results are + summed, and a warning is printed to stdout. + + >>> from doctest import Tester + >>> t1 = Tester(globs={}, verbose=0) + >>> t1.runstring(''' + ... >>> x = 12 + ... >>> print x + ... 12 + ... ''', "t1example") + (0, 2) + >>> + >>> t2 = Tester(globs={}, verbose=0) + >>> t2.runstring(''' + ... >>> x = 13 + ... >>> print x + ... 13 + ... ''', "t2example") + (0, 2) + >>> common = ">>> assert 1 + 2 == 3\\n" + >>> t1.runstring(common, "common") + (0, 1) + >>> t2.runstring(common, "common") + (0, 1) + >>> t1.merge(t2) + *** Tester.merge: 'common' in both testers; summing outcomes. + >>> t1.summarize(1) + 3 items passed all tests: + 2 tests in common + 2 tests in t1example + 2 tests in t2example + 6 tests in 3 items. + 6 passed and 0 failed. + Test passed. + (0, 6) + >>> + """ + + d = self.name2ft + for name, (f, t) in other.name2ft.items(): + if d.has_key(name): + print "*** Tester.merge: '" + name + "' in both" \ + " testers; summing outcomes." + f2, t2 = d[name] + f = f + f2 + t = t + t2 + d[name] = f, t + + def __record_outcome(self, name, f, t): + if self.name2ft.has_key(name): + print "*** Warning: '" + name + "' was tested before;", \ + "summing outcomes." + f2, t2 = self.name2ft[name] + f = f + f2 + t = t + t2 + self.name2ft[name] = f, t + + def __runone(self, target, name): + if "." in name: + i = _string_rindex(name, ".") + prefix, base = name[:i], name[i+1:] + else: + prefix, base = "", base + if self.isprivate(prefix, base): + return 0, 0 + return self.rundoc(target, name) + +master = None + +def testmod(m=None, name=None, globs=None, verbose=None, isprivate=None, + report=1): + """m=None, name=None, globs=None, verbose=None, isprivate=None, report=1 + + Test examples in docstrings in functions and classes reachable from + module m, starting with m.__doc__. Private names are skipped. + + Also test examples reachable from dict m.__test__ if it exists and is + not None. m.__dict__ maps names to functions, classes and strings; + function and class docstrings are tested even if the name is private; + strings are tested directly, as if they were docstrings. + + Return (#failures, #tests). + + See doctest.__doc__ for an overview. + + Optional keyword arg "name" gives the name of the module; by default + use m.__name__. + + Optional keyword arg "globs" gives a dict to be used as the globals + when executing examples; by default, use m.__dict__. A copy of this + dict is actually used for each docstring, so that each docstring's + examples start with a clean slate. + + Optional keyword arg "verbose" prints lots of stuff if true, prints + only failures if false; by default, it's true iff "-v" is in sys.argv. + + Optional keyword arg "isprivate" specifies a function used to + determine whether a name is private. The default function is + doctest.is_private; see its docs for details. + + Optional keyword arg "report" prints a summary at the end when true, + else prints nothing at the end. In verbose mode, the summary is + detailed, else very brief (in fact, empty if all tests passed). + + Advanced tomfoolery: testmod runs methods of a local obj of + class doctest.Tester, then merges the results into (or creates) + global Tester obj doctest.master. Methods of doctest.master + can be called directly too, if you want to do something unusual. + Passing report=0 to testmod is especially useful then, to delay + displaying a summary. Invoke doctest.master.summarize(verbose) + when you're done fiddling. + """ + + global master + + if m is None: + import sys + # DWA - m will still be None if this wasn't invoked from the command + # line, in which case the following TypeError is about as good an error + # as we should expect + m = sys.modules.get('__main__') + + if type(m) is not _ModuleType: + raise TypeError("testmod: module required; " + `m`) + if name is None: + name = m.__name__ + tester = Tester(m, globs=globs, verbose=verbose, isprivate=isprivate) + failures, tries = tester.rundoc(m, name) + f, t = tester.rundict(m.__dict__, name) + failures = failures + f + tries = tries + t + if hasattr(m, "__test__"): + testdict = m.__test__ + if testdict: + if not hasattr(testdict, "items"): + raise TypeError("testmod: module.__test__ must support " + ".items(); " + `testdict`) + f, t = tester.run__test__(testdict, name + ".__test__") + failures = failures + f + tries = tries + t + if report: + tester.summarize() + if master is None: + master = tester + else: + master.merge(tester) + return failures, tries + +class _TestClass: + """ + A pointless class, for sanity-checking of docstring testing. + + Methods: + square() + get() + + >>> _TestClass(13).get() + _TestClass(-12).get() + 1 + >>> hex(_TestClass(13).square().get()) + '0xa9' + """ + + def __init__(self, val): + """val -> _TestClass object with associated value val. + + >>> t = _TestClass(123) + >>> print t.get() + 123 + """ + + self.val = val + + def square(self): + """square() -> square TestClass's associated value + + >>> _TestClass(13).square().get() + 169 + """ + + self.val = self.val ** 2 + return self + + def get(self): + """get() -> return TestClass's associated value. + + >>> x = _TestClass(-42) + >>> print x.get() + -42 + """ + + return self.val + +__test__ = {"_TestClass": _TestClass, + "string": r""" + Example of a string object, searched as-is. + >>> x = 1; y = 2 + >>> x + y, x * y + (3, 2) + """ + } + +def _test(): + import doctest + return doctest.testmod(doctest) + +if __name__ == "__main__": + _test() diff --git a/doxyfile b/doxyfile new file mode 100644 index 00000000..5b7bbd25 --- /dev/null +++ b/doxyfile @@ -0,0 +1,708 @@ +# Doxyfile 1.2.2 + +# This file describes the settings to be used by doxygen for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# General configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = py_cpp + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0.9 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./docs + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Dutch, French, Italian, Czech, Swedish, German, Finnish, Japanese, +# Korean, Hungarian, Spanish, Romanian, Russian, Croatian, Polish, and +# Portuguese. + +OUTPUT_LANGUAGE = English + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these class will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. It is allowed to use relative paths in the argument list. + +STRIP_FROM_PATH = + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a class diagram (in Html and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. + +CLASS_DIAGRAMS = YES + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower case letters. If set to YES upper case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# users are adviced to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the JAVADOC_AUTOBRIEF tag is set to YES (the default) then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the Javadoc-style will +# behave just like the Qt-style comments. + +JAVADOC_AUTOBRIEF = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# reimplements. + +INHERIT_DOCS = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# The ENABLE_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = c:/prj/manhattan/py_cpp + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +FILE_PATTERNS = *.cpp *.h *.py *.hpp + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = example1.cpp test_extclass.py extclass_demo.cpp extclass_demo.h + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. + +INPUT_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimised for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = YES + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using a WORD or other. +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assigments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. Warning: This feature +# is still experimental and very incomplete. + +GENERATE_XML = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_PREDEF_ONLY tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +#--------------------------------------------------------------------------- +# Configuration::addtions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tagfiles. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = c:\dragon\ntbin\perl.exe + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the ENABLE_PREPROCESSING, INCLUDE_GRAPH, and HAVE_DOT tags are set to +# YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other +# documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, INCLUDED_BY_GRAPH, and HAVE_DOT tags are set to +# YES then doxygen will generate a graph for each documented header file showing +# the documented files that directly or indirectly include this file + +INCLUDED_BY_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +#--------------------------------------------------------------------------- +# Configuration::addtions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# The CGI_NAME tag should be the name of the CGI script that +# starts the search engine (doxysearch) with the correct parameters. +# A script with this name will be generated by doxygen. + +CGI_NAME = search.cgi + +# The CGI_URL tag should be the absolute URL to the directory where the +# cgi binaries are located. See the documentation of your http daemon for +# details. + +CGI_URL = + +# The DOC_URL tag should be the absolute URL to the directory where the +# documentation is located. If left blank the absolute path to the +# documentation, with file:// prepended to it, will be used. + +DOC_URL = + +# The DOC_ABSPATH tag should be the absolute path to the directory where the +# documentation is located. If left blank the directory on the local machine +# will be used. + +DOC_ABSPATH = + +# The BIN_ABSPATH tag must point to the directory where the doxysearch binary +# is installed. + +BIN_ABSPATH = C:\tools\doxygen-1.2.2\bin + +# The EXT_DOC_PATHS tag can be used to specify one or more paths to +# documentation generated for other projects. This allows doxysearch to search +# the documentation for these projects as well. + +EXT_DOC_PATHS = diff --git a/enums.html b/enums.html new file mode 100644 index 00000000..7b3b3cd8 --- /dev/null +++ b/enums.html @@ -0,0 +1,94 @@ + + + Wrapping enums + +
+

+ c++boost.gif (8819 bytes)Wrapping enums +

+

Because there is in general no way to deduce that a value of arbitrary type T +is an enumeration constant, py_cpp cannot automatically convert enum values to +and from Python. To handle this case, you need to decide how you want the enum +to show up in Python (since Python doesn't have enums). Once you have done that, +you can write some simple from_python() and +to_python() functions. + +

If you are satisfied with a Python int as a way to represent your enum +values, py_cpp provides a shorthand for these functions. You just need to +instantiate python::enum_as_int_converters<EnumType> where +EnumType is your enumerated type. There are two convenient ways to do this: + +

    +
  1. +
    +// drop into namespace python and explicitly instantiate
    +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE // this is a gcc 2.95.2 bug workaround
    +  template class enum_as_int_converters;
    +BOOST_PYTHON_END_CONVERSION_NAMESPACE
    +
    +
    +
  2. +// instantiate as base class in any namespace
    +struct EnumTypeConverters
    +    : python::py_enum_as_int_converters
    +{
    +};
    +
    +
+ +

Either of the above is equivalent to the following declarations: +

+BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE // this is a gcc 2.95.2 bug workaround
+
+  MyEnumType from_python(PyObject* x, python::type<MyEnumType>)
+  {
+      return static_cast<MyEnum>(
+        from_python(x, python::type<long>()));
+  }
+
+  MyEnumType from_python(PyObject* x, python::type<const MyEnumType&>)
+  {
+      return static_cast<MyEnum>(
+        from_python(x, python::type<long>()));
+  }
+
+  PyObject* to_python(MyEnumType x)
+  {
+      return to_python(static_cast<long>(x));
+  }
+BOOST_PYTHON_END_CONVERSION_NAMESPACE
+
+ +

This technique defines the conversions of +MyEnumType in terms of the conversions for the built-in + long type. + +You may also want to add a bunch of lines like this to your module +initialization: + +

+mymodule.add(python::to_python(enum_value_1), "enum_value_1");
+mymodule.add(python::to_python(enum_value_2), "enum_value_2");
+...
+
+ +You can also add these to an extension class definition, if your enum happens to +be local to a class and you want the analogous interface in Python: + +
+my_class.add(python::to_python(enum_value_1), "enum_value_1");
+my_class.add(python::to_python(enum_value_2), "enum_value_2");
+...
+
+

+ © 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: Nov 12, 2000 +

+ diff --git a/errors.h b/errors.h new file mode 100644 index 00000000..cd4b6837 --- /dev/null +++ b/errors.h @@ -0,0 +1,30 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef ERRORS_DWA052500_H_ +# define ERRORS_DWA052500_H_ + +namespace python { + +struct error_already_set {}; +struct argument_error : error_already_set {}; + +// Handles exceptions caught just before returning to Python code. +void handle_exception(); + +template +T* expect_non_null(T* x) +{ + if (x == 0) + throw error_already_set(); + return x; +} + +} // namespace python + +#endif // ERRORS_DWA052500_H_ diff --git a/example1.cpp b/example1.cpp new file mode 100644 index 00000000..17098208 --- /dev/null +++ b/example1.cpp @@ -0,0 +1,54 @@ +#include + +namespace hello { + class world + { + public: + world(int) {} + ~world() {} + const char* get() const { return "hi, world"; } + }; + + size_t length(const world& x) { return strlen(x.get()); } +} + +#include + +// Python requires an exported function called init in every +// extension module. This is where we build the module contents. +extern "C" +#ifdef _WIN32 +__declspec(dllexport) +#endif +void inithello() +{ + try + { + // create an object representing this extension module + python::module_builder hello("hello"); + + // Create the Python type object for our extension class + python::class_builder world_class(hello, "world"); + + // Add the __init__ function + world_class.def(python::constructor()); + // Add a regular member function + world_class.def(&hello::world::get, "get"); + + // Add a regular function to the module + hello.def(hello::length, "length"); + } + catch(...) + { + python::handle_exception(); // Deal with the exception for Python + } +} + +// Win32 DLL boilerplate +#if defined(_WIN32) +#include +extern "C" BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) +{ + return 1; +} +#endif // _WIN32 diff --git a/example1.html b/example1.html new file mode 100644 index 00000000..ae643e0f --- /dev/null +++ b/example1.html @@ -0,0 +1,129 @@ + + + A Simple Example Using py_cpp + +
+

+ + +

+

+ A Simple Example Using py_cpp +

+

+ Suppose we have the following C++ API which we want to expose in + Python: +

+
+#include <string>
+
+namespace hello {
+  class world
+  {
+   public:
+      world(int);
+      ~world();
+      std::string get() const { return "hi, world"; }
+    ...
+  };
+  std::size_t length(const world& x) { return std::strlen(x.get()); }
+}
+
+
+
+

+ Here is the C++ code for a python module called hello + which exposes the API using py_cpp: +

+
+#include <py_cpp/class_wrapper.h>
+// Python requires an exported function called init<module-name> in every
+// extension module. This is where we build the module contents.
+extern "C"
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+void inithello()
+{
+    try
+    {
+       // create an object representing this extension module
+       python::module_builder hello("hello");
+       // Create the Python type object for our extension class
+       python::class_builder<hello::world> world_class(hello, "world");
+       // Add the __init__ function
+       world_class.def(python::constructor<int>());
+       // Add a regular member function
+       world_class.def(&hello::world::get, "get");
+       // Add a regular function to the module
+       hello.def(hello::length, "length");
+    }
+    catch(...)
+    {
+       python::handle_exception();    // Deal with the exception for Python
+    }
+}
+// Win32 DLL boilerplate
+#if defined(_WIN32)
+#include <windows.h>
+extern "C" BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID)
+{
+    return 1;
+}
+#endif // _WIN32
+
+
+

+ That's it! If we build this shared library and put it on our + PYTHONPATH we can now access our C++ class and function from + Python. +

+
+>>> import hello
+>>> hi_world = hello.world(3)
+>>> hi_world.get()
+'hi, world'
+>>> hello.length(hi_world)
+9
+
+
+

+ We can even make a subclass of hello.world: +

+
+>>> class my_subclass(hello.world):
+...     def get(self):
+...         return 'hello, world'
+...
+>>> y = my_subclass(4)
+>>> y.get()
+'hello, world'
+
+
+

+ Pretty cool! You can't do that with an ordinary Python extension type! +

+
+>>> hello.length(y)
+9
+
+
+

+ Of course, you may now have a slightly empty feeling in the pit of + your little pythonic stomach. Perhaps you feel your subclass deserves + to have a length() of 12? If so, read on... +

+ Previous: Comparisons with other systems Next: Overridable virtual functions 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 30, 2000 +

+ diff --git a/extclass.cpp b/extclass.cpp new file mode 100644 index 00000000..94d9bf68 --- /dev/null +++ b/extclass.cpp @@ -0,0 +1,683 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "extclass.h" +#include +#include + +namespace python { +namespace detail { + + struct operator_dispatcher + : public PyObject + { + static PyTypeObject type_obj; + static PyNumberMethods number_methods; + + static operator_dispatcher* create(const ref& o, const ref& s); + + ref m_object; + ref m_self; + + // data members for allocation/deallocation optimization + operator_dispatcher* m_free_list_link; + static operator_dispatcher* free_list; + + private: + // only accessible through create() + operator_dispatcher(const ref& o, const ref& s); + }; + + operator_dispatcher* operator_dispatcher::free_list = 0; + +}} + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE + +inline PyObject* to_python(python::detail::operator_dispatcher* n) { return n; } + +BOOST_PYTHON_END_CONVERSION_NAMESPACE + + +namespace python{ + +namespace detail { + + tuple extension_class_coerce(ref l, ref r) + { + // Introduced sequence points for exception-safety. + ref first(operator_dispatcher::create(l, l)); + ref second; + + if(r->ob_type == &operator_dispatcher::type_obj) + { + second = r; + } + else + { + second = ref(operator_dispatcher::create(r, ref())); + } + return python::tuple(first, second); + } + + enum { unwrap_exception_code = -1000 }; + + int unwrap_args(PyObject* left, PyObject* right, PyObject*& self, PyObject*& other) + { + if (left->ob_type != &operator_dispatcher::type_obj || + right->ob_type != &operator_dispatcher::type_obj) + { + PyErr_SetString(PyExc_RuntimeError, "operator_dispatcher::unwrap_args(): expecting operator_dispatcher arguments only!"); + return unwrap_exception_code; + } + + typedef reference DPtr; + DPtr lwrapper(static_cast(left), DPtr::increment_count); + DPtr rwrapper(static_cast(right), DPtr::increment_count); + + if (lwrapper->m_self.get() != 0) + { + self = lwrapper->m_self.get(); + other = rwrapper->m_object.get(); + return 0; + } + else + { + self = rwrapper->m_self.get(); + other = lwrapper->m_object.get(); + return 1; + } + } + + int unwrap_pow_args(PyObject* left, PyObject* right, PyObject* m, + PyObject*& self, PyObject*& first, PyObject*& second) + { + if (left->ob_type != &operator_dispatcher::type_obj || + right->ob_type != &operator_dispatcher::type_obj || + m->ob_type != &operator_dispatcher::type_obj) + { + PyErr_SetString(PyExc_RuntimeError, "operator_dispatcher::unwrap_pow_args(): expecting operator_dispatcher arguments only!"); + return unwrap_exception_code; + } + + typedef reference DPtr; + DPtr lwrapper(static_cast(left), DPtr::increment_count); + DPtr rwrapper(static_cast(right), DPtr::increment_count); + DPtr mwrapper(static_cast(m), DPtr::increment_count); + + if (lwrapper->m_self.get() != 0) + { + self = lwrapper->m_self.get(); + first = rwrapper->m_object.get(); + second = mwrapper->m_object.get(); + return 0; + } + else if (rwrapper->m_self.get() != 0) + { + self = rwrapper->m_self.get(); + first = lwrapper->m_object.get(); + second = mwrapper->m_object.get(); + return 1; + } + else + { + self = mwrapper->m_self.get(); + first = lwrapper->m_object.get(); + second = rwrapper->m_object.get(); + return 2; + } + } + +extension_instance* get_extension_instance(PyObject* p) +{ + // The object's type will just be some class_t object, + // but if its meta-type is right, then it is an extension_instance. + if (p->ob_type->ob_type != extension_meta_class()) + { + PyErr_SetString(PyExc_TypeError, p->ob_type->tp_name); + throw python::argument_error(); + } + return static_cast(p); +} + +void +extension_instance::add_implementation(std::auto_ptr holder) +{ + for (held_objects::const_iterator p = m_wrapped_objects.begin(); + p != m_wrapped_objects.end(); ++p) + { + if (typeid(*holder) == typeid(**p)) + { + PyErr_SetString(PyExc_RuntimeError, "Base class already initialized"); + throw error_already_set(); + } + } + m_wrapped_objects.push_back(holder.release()); +} + +extension_instance::extension_instance(PyTypeObject* class_) + : instance(class_) +{ +} + +extension_instance::~extension_instance() +{ + for (held_objects::const_iterator p = m_wrapped_objects.begin(), + finish = m_wrapped_objects.end(); + p != finish; ++p) + { + delete *p; + } +} + +meta_class* extension_meta_class() +{ + static meta_class result; + return &result; +} + +typedef class_t extension_class_t; + +bool is_subclass(const extension_class_t* derived, + const PyObject* possible_base) +{ + + tuple bases = derived->bases(); + + for (std::size_t i = 0, size = bases.size(); i < size; ++i) + { + const PyObject* base = bases[i].get(); + + if (base == possible_base) + return true; + + if (base->ob_type == extension_meta_class()) + { + const extension_class_t* base_class = downcast(base); + if (is_subclass(base_class, possible_base)) + return true; + } + } + return false; +} + +// Return true iff obj is an obj of target_class +bool is_instance(extension_instance* obj, + class_t* target_class) +{ + if (obj->ob_type == target_class) + return true; + else + { + return is_subclass( + downcast >(obj->ob_type).get(), + as_object(target_class)); + } +} + +void two_string_error(PyObject* exception_object, const char* format, const char* s1, const char* s2) +{ + char buffer[256]; + std::size_t format_length = BOOST_CSTD_::strlen(format); + std::size_t length1 = BOOST_CSTD_::strlen(s1); + std::size_t length2 = BOOST_CSTD_::strlen(s2); + + std::size_t additional_length = length1 + length2; + if (additional_length + format_length > format_length - 1) + { + std::size_t difference = sizeof(buffer) - 1 - additional_length; + length1 -= difference / 2; + additional_length -= difference / 2; + } + + sprintf(buffer, format, length1, s1, length2, s2); + + PyErr_SetString(exception_object, buffer); + if (exception_object == PyExc_TypeError) + throw argument_error(); + else + throw error_already_set(); +} + +// This is called when an attempt has been made to convert the given obj to +// a C++ type for which it doesn't have any obj data. In that case, either +// the obj was not derived from the target_class, or the appropriate +// __init__ function wasn't called to initialize the obj data of the target class. +void report_missing_instance_data( + extension_instance* obj, // The object being converted + class_t* target_class, // the extension class of the C++ type + const std::type_info& target_typeid, // The typeid of the C++ type + bool target_is_ptr) +{ + char buffer[256]; + if (is_instance(obj, target_class)) + { + if (target_is_ptr) + { + two_string_error(PyExc_RuntimeError, + "Object of extension class '%.*s' does not wrap <%.*s>.", + obj->ob_type->tp_name, target_typeid.name()); + } + else + { + const char message[] = "__init__ function for extension class '%.*s' was never called."; + sprintf(buffer, message, sizeof(buffer) - sizeof(message) - 1, + target_class->tp_name); + } + PyErr_SetString(PyExc_RuntimeError, buffer); + } + else if (target_class == 0) + { + const char message[] = "Cannot convert to <%.*s>; its Python class was never created or has been deleted."; + sprintf(buffer, message, sizeof(buffer) - sizeof(message) - 1, target_typeid.name()); + PyErr_SetString(PyExc_RuntimeError, buffer); + } + else + { + two_string_error(PyExc_TypeError, "extension class '%.*s' is not convertible into '%.*s'.", + obj->ob_type->tp_name, target_class->tp_name); + } +} + +void report_missing_instance_data( + extension_instance* obj, // The object being converted + class_t* target_class, // the extension class of the C++ type + const std::type_info& target_typeid) // The typeid of the C++ type +{ + report_missing_instance_data(obj, target_class, target_typeid, false); +} + +void report_missing_ptr_data( + extension_instance* obj, // The object being converted + class_t* target_class, // the extension class of the C++ type + const std::type_info& target_typeid) // The typeid of the C++ type +{ + report_missing_instance_data(obj, target_class, target_typeid, true); +} + +void report_missing_class_object(const std::type_info& info) +{ + char buffer[256]; + const char message[] = "Cannot convert <%.*s> to python; its Python class was never created or has been deleted."; + sprintf(buffer, message, sizeof(buffer) - sizeof(message) - 1, info.name()); + PyErr_SetString(PyExc_RuntimeError, buffer); + throw error_already_set(); +} + +void report_released_smart_pointer(const std::type_info& info) +{ + char buffer[256]; + const char message[] = "Converting from python, pointer or smart pointer to <%.*s> is NULL."; + sprintf(buffer, message, sizeof(buffer) - sizeof(message) - 1, info.name()); + PyErr_SetString(PyExc_RuntimeError, buffer); + throw argument_error(); +} + +read_only_setattr_function::read_only_setattr_function(const char* name) + : m_name(name) +{ +} + +PyObject* read_only_setattr_function::do_call(PyObject* /*args*/, PyObject* /*keywords*/) const +{ + PyErr_SetObject(PyExc_AttributeError, ("'" + m_name + "' attribute is read-only").get()); + return 0; +} + +const char* read_only_setattr_function::description() const +{ + return "uncallable"; +} + +extension_class_base::extension_class_base(const char* name) + : class_t( + extension_meta_class(), string(name), tuple(), dictionary()) +{ +} + +// This function is used in from_python() to convert wrapped classes that are +// related by inheritance. The problem is this: although C++ provides all necessary +// conversion operators, source and target of a conversion must be known at compile +// time. However, in Python we want to convert classes at runtime. The solution is to +// generate conversion functions at compile time, register them within the appropriate +// class objects and call them when a particular runtime conversion is required. + +// If functions for any possible conversion have to be stored, their number will grow +// qudratically. To reduce this number, we actually store only conversion functions +// between adjacent levels in the inheritance tree. By traversing the tree recursively, +// we can build any allowed conversion as a concatenation of simple conversions. This +// traversal is done in the functions try_base_class_conversions() and +// try_derived_class_conversions(). If a particular conversion is impossible, all +// conversion functions will return a NULL pointer. + +// The function extract_object_from_holder() attempts to actually extract the pointer +// to the contained object from an instance_holder_base (a wrapper class). A conversion +// of the held object to 'T *' is allowed when the conversion +// 'dynamic_cast *>(an_instance_holder_base)' succeeds. +void* extension_class_base::try_class_conversions(instance_holder_base* object) const +{ + void* result = try_derived_class_conversions(object); + if (result) + return result; + + if (!object->held_by_value()) + return try_base_class_conversions(object); + else + return 0; +} + +void* extension_class_base::try_base_class_conversions(instance_holder_base* object) const +{ + for (std::size_t i = 0; i < base_classes().size(); ++i) + { + if (base_classes()[i].convert == 0) + continue; + void* result1 = base_classes()[i].class_object->extract_object_from_holder(object); + if (result1) + return (*base_classes()[i].convert)(result1); + + void* result2 = base_classes()[i].class_object->try_base_class_conversions(object); + if (result2) + return (*base_classes()[i].convert)(result2); + } + return 0; +} + +void* extension_class_base::try_derived_class_conversions(instance_holder_base* object) const +{ + for (std::size_t i = 0; i < derived_classes().size(); ++i) + { + void* result1 = derived_classes()[i].class_object->extract_object_from_holder(object); + if (result1) + return (*derived_classes()[i].convert)(result1); + + void* result2 = derived_classes()[i].class_object->try_derived_class_conversions(object); + if (result2) + return (*derived_classes()[i].convert)(result2); + } + return 0; +} + +void extension_class_base::add_method(function* method, const char* name) +{ + add_method(reference(method), name); +} + +void extension_class_base::add_method(reference method, const char* name) +{ + // Add the attribute to the computed target + function::add_to_namespace(method, name, this->dict().get()); + + // If it is a special member function it should be enabled both here and there. + detail::enable_named_method(this, name); +} + +void extension_class_base::add_constructor_object(function* init_fn) +{ + add_method(init_fn, "__init__"); +} + +void extension_class_base::add_setter_method(function* setter_, const char* name) +{ + reference setter(setter_); + add_method(setter, (detail::setattr_string() + name + "__").c_str()); +} + +void extension_class_base::add_getter_method(function* getter_, const char* name) +{ + reference getter(getter_); + add_method(getter, (detail::getattr_string() + name + "__").c_str()); +} + +void extension_class_base::set_attribute(const char* name, PyObject* x_) +{ + ref x(x_); + set_attribute(name, x); +} + +void extension_class_base::set_attribute(const char* name, ref x) +{ + dict().set_item(string(name), x); + if (PyCallable_Check(x.get())) + detail::enable_named_method(this, name); +} + +operator_dispatcher::operator_dispatcher(const ref& o, const ref& s) + : m_object(o), m_self(s), m_free_list_link(0) + +{ + ob_refcnt = 1; + ob_type = &type_obj; +} + +operator_dispatcher* +operator_dispatcher::create(const ref& object, const ref& self) +{ + operator_dispatcher* const result = free_list; + if (result == 0) + return new operator_dispatcher(object, self); + + free_list = result->m_free_list_link; + result->m_object = object; + result->m_self = self; + Py_INCREF(result); + return result; +} + +extern "C" +{ + +void operator_dispatcher_dealloc(PyObject* self) +{ + operator_dispatcher* obj = static_cast(self); + obj->m_free_list_link = operator_dispatcher::free_list; + operator_dispatcher::free_list = obj; + obj->m_object.reset(); + obj->m_self.reset(); +} + +int operator_dispatcher_coerce(PyObject** l, PyObject** r) +{ + Py_INCREF(*l); + try + { + *r = operator_dispatcher::create(ref(*r, ref::increment_count), ref()); + } + catch(...) + { + handle_exception(); + return -1; + } + return 0; +} + + +#define PY_DEFINE_OPERATOR(id, symbol) \ + PyObject* operator_dispatcher_call_##id(PyObject* left, PyObject* right) \ + { \ + /* unwrap the arguments from their OperatorDispatcher */ \ + PyObject* self; \ + PyObject* other; \ + int reverse = unwrap_args(left, right, self, other); \ + if (reverse == unwrap_exception_code) \ + return 0; \ + \ + /* call the function */ \ + PyObject* result = \ + PyEval_CallMethod(self, \ + const_cast(reverse ? "__r" #id "__" : "__" #id "__"), \ + const_cast("(O)"), \ + other); \ + if (result == 0 && PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_AttributeError)) \ + { \ + PyErr_Clear(); \ + PyErr_SetString(PyExc_TypeError, "bad operand type(s) for " #symbol); \ + } \ + return result; \ + } + +PY_DEFINE_OPERATOR(add, +) +PY_DEFINE_OPERATOR(sub, -) +PY_DEFINE_OPERATOR(mul, *) +PY_DEFINE_OPERATOR(div, /) +PY_DEFINE_OPERATOR(mod, %) +PY_DEFINE_OPERATOR(divmod, divmod) +PY_DEFINE_OPERATOR(lshift, <<) +PY_DEFINE_OPERATOR(rshift, >>) +PY_DEFINE_OPERATOR(and, &) +PY_DEFINE_OPERATOR(xor, ^) +PY_DEFINE_OPERATOR(or, |) + +/* coercion rules for heterogeneous pow(): + pow(Foo, int): left, right coerced; m: None => reverse = 0 + pow(int, Foo): left, right coerced; m: None => reverse = 1 + pow(Foo, int, int): left, right, m coerced => reverse = 0 + pow(int, Foo, int): left, right, m coerced => reverse = 1 + pow(int, int, Foo): left, right, m coerced => reverse = 2 + pow(Foo, Foo, int): left, right coerced; m coerced twice => reverse = 0 + pow(Foo, int, Foo): left, right, m coerced => reverse = 0 + pow(int, Foo, Foo): left, right, m coerced => reverse = 1 +*/ +PyObject* operator_dispatcher_call_pow(PyObject* left, PyObject* right, PyObject* m) +{ + int reverse; + PyObject* self; + PyObject* first; + PyObject* second; + + if (m->ob_type == Py_None->ob_type) + { + reverse = unwrap_args(left, right, self, first); + second = m; + } + else + { + reverse = unwrap_pow_args(left, right, m, self, first, second); + } + + if (reverse == unwrap_exception_code) + return 0; + + // call the function + PyObject* result = + PyEval_CallMethod(self, + const_cast((reverse == 0) + ? "__pow__" + : (reverse == 1) + ? "__rpow__" + : "__rrpow__"), + const_cast("(OO)"), + first, second); + if (result == 0 && + (PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_TypeError) || + PyErr_GivenExceptionMatches(PyErr_Occurred(), PyExc_AttributeError))) + { + PyErr_Clear(); + PyErr_SetString(PyExc_TypeError, "bad operand type(s) for pow()"); + } + return result; +} + +int operator_dispatcher_call_cmp(PyObject* left, PyObject* right) +{ + // unwrap the arguments from their OperatorDispatcher + PyObject* self; + PyObject* other; + int reverse = unwrap_args(left, right, self, other); + if (reverse == unwrap_exception_code) + return -1; + + // call the function + PyObject* result = + PyEval_CallMethod(self, + const_cast(reverse ? "__rcmp__" : "__cmp__"), + const_cast("(O)"), + other); + if (result == 0) + { + PyErr_Clear(); + PyErr_SetString(PyExc_TypeError, "bad operand type(s) for cmp() or <"); + return -1; + } + else + { + try + { + return BOOST_PYTHON_CONVERSION::from_python(result, type()); + } + catch(...) + { + PyErr_Clear(); + PyErr_SetString(PyExc_TypeError, "cmp() didn't return int"); + return -1; + } + } +} + +} // extern "C" + +PyTypeObject operator_dispatcher::type_obj = +{ + PyObject_HEAD_INIT(&PyType_Type) + 0, + const_cast("operator_dispatcher"), + sizeof(operator_dispatcher), + 0, + &operator_dispatcher_dealloc, + 0, + 0, + 0, + &operator_dispatcher_call_cmp, + 0, + &operator_dispatcher::number_methods, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +}; + +PyNumberMethods operator_dispatcher::number_methods = +{ + &operator_dispatcher_call_add, + &operator_dispatcher_call_sub, + &operator_dispatcher_call_mul, + &operator_dispatcher_call_div, + &operator_dispatcher_call_mod, + &operator_dispatcher_call_divmod, + &operator_dispatcher_call_pow, + 0, + 0, + 0, + 0, + 0, + &operator_dispatcher_call_lshift, + &operator_dispatcher_call_rshift, + &operator_dispatcher_call_and, + &operator_dispatcher_call_xor, + &operator_dispatcher_call_or, + &operator_dispatcher_coerce, + 0, + 0, + 0, + 0, + 0 +}; + +} // namespace detail + +} // namespace python diff --git a/extclass.h b/extclass.h new file mode 100644 index 00000000..925f441b --- /dev/null +++ b/extclass.h @@ -0,0 +1,834 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file automatically generated for 5-argument constructors by +// gen_extclass.python + +#ifndef EXTENSION_CLASS_DWA052000_H_ +# define EXTENSION_CLASS_DWA052000_H_ + +# include "pyconfig.h" +# include "subclass.h" +# include +# include "none.h" +# include "objects.h" +# include "functions.h" +# include +# include "init_function.h" +# include +# include + +namespace python { + +// forward declarations +template struct operators; +template struct left_operand; +template struct right_operand; + +enum without_downcast_t { without_downcast }; + +namespace detail { + +// forward declarations +class extension_instance; +class extension_class_base; +template class instance_holder; +template class instance_value_holder; +template class instance_ptr_holder; +template struct operand_select; + template struct choose_op; + template struct choose_rop; + template struct choose_unary_op; + template struct define_operator; + +meta_class* extension_meta_class(); +extension_instance* get_extension_instance(PyObject* p); +void report_missing_instance_data(extension_instance*, class_t*, const std::type_info&); +void report_missing_ptr_data(extension_instance*, class_t*, const std::type_info&); +void report_missing_class_object(const std::type_info&); +void report_released_smart_pointer(const std::type_info&); + +template +T* check_non_null(T* p) +{ + if (p == 0) + report_released_smart_pointer(typeid(T)); + return p; +} + +template class held_instance; + +typedef void* (*conversion_function_ptr)(void*); + +struct base_class_info +{ + base_class_info(extension_class_base* t, conversion_function_ptr f) + :class_object(t), convert(f) + {} + + extension_class_base* class_object; + conversion_function_ptr convert; +}; + +typedef base_class_info derived_class_info; + +struct add_operator_base; + +class extension_class_base : public class_t +{ + public: + extension_class_base(const char* name); + + public: + // the purpose of try_class_conversions() and its related functions + // is explained in extclass.cpp + void* try_class_conversions(instance_holder_base*) const; + void* try_base_class_conversions(instance_holder_base*) const; + void* try_derived_class_conversions(instance_holder_base*) const; + + void set_attribute(const char* name, PyObject* x); + void set_attribute(const char* name, ref x); + + private: + virtual void* extract_object_from_holder(instance_holder_base* v) const = 0; + virtual std::vector const& base_classes() const = 0; + virtual std::vector const& derived_classes() const = 0; + + protected: + friend struct add_operator_base; + void add_method(reference method, const char* name); + void add_method(function* method, const char* name); + + void add_constructor_object(function*); + void add_setter_method(function*, const char* name); + void add_getter_method(function*, const char* name); +}; + +template +class class_registry +{ + public: + static extension_class_base* class_object() + { return static_class_object; } + + // Register/unregister the Python class object corresponding to T + static void register_class(extension_class_base*); + static void unregister_class(extension_class_base*); + + // Establish C++ inheritance relationships + static void register_base_class(base_class_info const&); + static void register_derived_class(derived_class_info const&); + + // Query the C++ inheritance relationships + static std::vector const& base_classes(); + static std::vector const& derived_classes(); + private: + static extension_class_base* static_class_object; + static std::vector static_base_class_info; + static std::vector static_derived_class_info; +}; + +}} // namespace python::detail + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE + +// This class' only job is to define from_python and to_python converters for T +// and U. T is the class the user really intends to wrap. U is a class derived +// from T with some virtual function overriding boilerplate, or if there are no +// virtual functions, U = held_instance. +template > +class python_extension_class_converters +{ + public: + // Get an object which can be used to convert T to/from python. This is used + // as a kind of concept check by the global template + // + // PyObject* to_python(const T& x) + // + // below this class, to prevent the confusing messages that would otherwise + // pop up. Now, if T hasn't been wrapped as an extension class, the user + // will see an error message about the lack of an eligible + // py_extension_class_converters() function. + friend python_extension_class_converters py_extension_class_converters(python::type) + { + return python_extension_class_converters(); + } + + // This is a member function because in a conforming implementation, friend + // funcitons defined inline in the class body are all instantiated as soon + // as the enclosing class is instantiated. If T is not copyable, that causes + // a compiler error. Instead, we access this function through the global + // template + // + // PyObject* to_python(const T& x) + // + // defined below this class. Since template functions are instantiated only + // on demand, errors will be avoided unless T is noncopyable and the user + // writes code which causes us to try to copy a T. + PyObject* to_python(const T& x) const + { + python::reference result(create_instance()); + result->add_implementation( + std::auto_ptr( + new python::detail::instance_value_holder(result.get(), x))); + return result.release(); + } + + // Convert to T* + friend T* from_python(PyObject* obj, python::type) + { + // downcast to an extension_instance, then find the actual T + python::detail::extension_instance* self = python::detail::get_extension_instance(obj); + typedef std::vector::const_iterator iterator; + for (iterator p = self->wrapped_objects().begin(); + p != self->wrapped_objects().end(); ++p) + { + python::detail::instance_holder* held = dynamic_cast*>(*p); + if (held != 0) + return held->target(); + + // see extclass.cpp for an explanation of try_class_conversions() + void* target = python::detail::class_registry::class_object()->try_class_conversions(*p); + if(target) + return static_cast(target); + } + python::detail::report_missing_instance_data(self, python::detail::class_registry::class_object(), typeid(T)); + throw python::argument_error(); + } + + // Convert to PtrType, where PtrType can be dereferenced to obtain a T. + template + static PtrType& ptr_from_python(PyObject* obj, python::type) + { + // downcast to an extension_instance, then find the actual T + python::detail::extension_instance* self = python::detail::get_extension_instance(obj); + typedef std::vector::const_iterator iterator; + for (iterator p = self->wrapped_objects().begin(); + p != self->wrapped_objects().end(); ++p) + { + python::detail::instance_ptr_holder* held = + dynamic_cast*>(*p); + if (held != 0) + return held->ptr(); + } + python::detail::report_missing_ptr_data(self, python::detail::class_registry::class_object(), typeid(T)); + throw python::argument_error(); + } + + template + static PyObject* ptr_to_python(PtrType x) + { + python::reference result(create_instance()); + result->add_implementation( + std::auto_ptr( + new python::detail::instance_ptr_holder(x))); + return result.release(); + } + + static python::reference create_instance() + { + PyTypeObject* class_object = python::detail::class_registry::class_object(); + if (class_object == 0) + python::detail::report_missing_class_object(typeid(T)); + + return python::reference( + new python::detail::extension_instance(class_object)); + } + + // Convert to const T* + friend const T* from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + // Convert to const T* const& + friend const T* from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + // Convert to T* const& + friend T* from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + // Convert to T& + friend T& from_python(PyObject* p, python::type) + { return *python::detail::check_non_null(from_python(p, python::type())); } + + // Convert to const T& + friend const T& from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + // Convert to T + friend const T& from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + friend std::auto_ptr& from_python(PyObject* p, python::type&>) + { return ptr_from_python(p, python::type >()); } + + friend std::auto_ptr& from_python(PyObject* p, python::type >) + { return ptr_from_python(p, python::type >()); } + + friend const std::auto_ptr& from_python(PyObject* p, python::type&>) + { return ptr_from_python(p, python::type >()); } + + friend PyObject* to_python(std::auto_ptr x) + { return ptr_to_python(x); } + + friend boost::shared_ptr& from_python(PyObject* p, python::type&>) + { return ptr_from_python(p, python::type >()); } + + friend boost::shared_ptr& from_python(PyObject* p, python::type >) + { return ptr_from_python(p, python::type >()); } + + friend const boost::shared_ptr& from_python(PyObject* p, python::type&>) + { return ptr_from_python(p, python::type >()); } + + friend PyObject* to_python(boost::shared_ptr x) + { return ptr_to_python(x); } +}; + +// Convert T to_python, instantiated on demand and only if there isn't a +// non-template overload for this function. This version is the one invoked when +// T is a wrapped class. See the first 2 functions declared in +// python_extension_class_converters above for more info. +template +PyObject* to_python(const T& x) +{ + return py_extension_class_converters(python::type()).to_python(x); +} + +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +namespace python { + +BOOST_PYTHON_IMPORT_CONVERSION(python_extension_class_converters); + +namespace detail { + +template class instance_holder; + +class read_only_setattr_function : public function +{ + public: + read_only_setattr_function(const char* name); + PyObject* do_call(PyObject* args, PyObject* keywords) const; + const char* description() const; + private: + string m_name; +}; + + template + struct define_conversion + { + static void* upcast_ptr(void* v) + { + return static_cast(static_cast(v)); + } + + static void* downcast_ptr(void* v) + { + return dynamic_cast(static_cast(v)); + } + }; + +// An easy way to make an extension base class which wraps T. Note that Python +// subclasses of this class will simply be class_t objects. +// +// U should be a class derived from T which overrides virtual functions with +// boilerplate code to call back into Python. See extclass_demo.h for examples. +// +// U is optional, but you won't be able to override any member functions in +// Python which are called from C++ if you don't supply it. If you just want to +// be able to use T in python without overriding member functions, you can omit +// U. +template > +class extension_class + : public python_extension_class_converters, // This generates the to_python/from_python functions + public extension_class_base +{ + public: + typedef T wrapped_type; + typedef U callback_type; + + // Construct with a name that comes from typeid(T).name(). The name only + // affects the objects of this class are represented through repr() + extension_class(); + + // Construct with the given name. The name only affects the objects of this + // class are represented through repr() + extension_class(const char* name); + + ~extension_class(); + + // define constructors + template + inline void def(constructor) + // The following incantation builds a signature1, signature2,... object. It + // should _all_ get optimized away. + { add_constructor( + prepend(type::id(), + prepend(type::id(), + prepend(type::id(), + prepend(type::id(), + prepend(type::id(), + signature0())))))); + } + + + // export homogeneous operators (type of both lhs and rhs is 'operator') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub), Foo>()); + + // export homogeneous operators (type of both lhs and rhs is 'T const&') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub)>()); + template + inline void def(operators) + { + typedef typename operand_select::template wrapped::type true_operand; + def_operators(operators()); + } + + // export heterogeneous operators (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub), Foo>(), + // python::right_operand()); + + // export heterogeneous operators (type of lhs: 'T const&', of rhs: 'right') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub)>(), + // python::right_operand()); + template + inline void def(operators, right_operand r) + { + typedef typename operand_select::template wrapped::type true_left; + def_operators(operators(), r); + } + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub), Foo>(), + // python::left_operand()); + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'T const&') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub)>(), + // python::left_operand()); + template + inline void def(operators, left_operand l) + { + typedef typename operand_select::template wrapped::type true_right; + def_operators(operators(), l); + } + + // define a function that passes Python arguments and keywords + // to C++ verbatim (as a 'tuple const&' and 'dictionary const&' + // respectively). This is useful for manual argument passing. + // It's also the only possibility to pass keyword arguments to C++. + // Fn must have a signatur that is compatible to + // PyObject* (*)(PyObject* aTuple, PyObject* aDictionary) + template + inline void def_raw(Fn fn, const char* name) + { + this->add_method(new_raw_arguments_function(fn), name); + } + + // define member functions. In fact this works for free functions, too - + // they act like static member functions, or if they start with the + // appropriate self argument (as a pointer), they can be used just like + // ordinary member functions -- just like Python! + template + inline void def(Fn fn, const char* name) + { + this->add_method(new_wrapped_function(fn), name); + } + + // Define a virtual member function with a default implementation. + // default_fn should be a function which provides the default implementation. + // Be careful that default_fn does not in fact call fn virtually! + template + inline void def(Fn fn, const char* name, DefaultFn default_fn) + { + this->add_method(new_virtual_function(type(), fn, default_fn), name); + } + + // Provide a function which implements x., reading from the given + // member (pm) of the T obj + template + inline void def_getter(MemberType T::*pm, const char* name) + { + this->add_getter_method(new getter_function(pm), name); + } + + // Provide a function which implements assignment to x., writing to + // the given member (pm) of the T obj + template + inline void def_setter(MemberType T::*pm, const char* name) + { + this->add_setter_method(new setter_function(pm), name); + } + + // Expose the given member (pm) of the T obj as a read-only attribute + template + inline void def_readonly(MemberType T::*pm, const char* name) + { + this->add_setter_method(new read_only_setattr_function(name), name); + this->def_getter(pm, name); + } + + // Expose the given member (pm) of the T obj as a read/write attribute + template + inline void def_read_write(MemberType T::*pm, const char* name) + { + this->def_getter(pm, name); + this->def_setter(pm, name); + } + + // define the standard coercion needed for operator overloading + void def_standard_coerce(); + + // declare the given class a base class of this one and register + // up and down conversion functions + template + void declare_base(extension_class* base) + { + // see extclass.cpp for an explanation of why we need to register + // conversion functions + base_class_info baseInfo(base, + &define_conversion::downcast_ptr); + class_registry::register_base_class(baseInfo); + add_base(ref(as_object(base), ref::increment_count)); + + derived_class_info derivedInfo(this, + &define_conversion::upcast_ptr); + class_registry::register_derived_class(derivedInfo); + } + + // declare the given class a base class of this one and register + // only up conversion function + template + void declare_base(extension_class* base, without_downcast_t) + { + // see extclass.cpp for an explanation of why we need to register + // conversion functions + base_class_info baseInfo(base, 0); + class_registry::register_base_class(baseInfo); + add_base(ref(as_object(base), ref::increment_count)); + + derived_class_info derivedInfo(this, + &define_conversion::upcast_ptr); + class_registry::register_derived_class(derivedInfo); + } + + private: // types + typedef instance_value_holder holder; + + private: // extension_class_base virtual function implementations + std::vector const& base_classes() const; + std::vector const& derived_classes() const; + void* extract_object_from_holder(instance_holder_base* v) const; + + private: // Utility functions + template + inline void def_operators(operators) + { + def_standard_coerce(); + + // for some strange reason, this prevents MSVC from having an + // "unrecoverable block scoping error"! + typedef choose_op<(which & op_add)> choose_add; + + choose_op<(which & op_add)>::template args::add(this); + choose_op<(which & op_sub)>::template args::add(this); + choose_op<(which & op_mul)>::template args::add(this); + choose_op<(which & op_div)>::template args::add(this); + choose_op<(which & op_mod)>::template args::add(this); + choose_op<(which & op_divmod)>::template args::add(this); + choose_op<(which & op_pow)>::template args::add(this); + choose_op<(which & op_lshift)>::template args::add(this); + choose_op<(which & op_rshift)>::template args::add(this); + choose_op<(which & op_and)>::template args::add(this); + choose_op<(which & op_xor)>::template args::add(this); + choose_op<(which & op_or)>::template args::add(this); + choose_unary_op<(which & op_neg)>::template args::add(this); + choose_unary_op<(which & op_pos)>::template args::add(this); + choose_unary_op<(which & op_abs)>::template args::add(this); + choose_unary_op<(which & op_invert)>::template args::add(this); + choose_unary_op<(which & op_int)>::template args::add(this); + choose_unary_op<(which & op_long)>::template args::add(this); + choose_unary_op<(which & op_float)>::template args::add(this); + choose_op<(which & op_cmp)>::template args::add(this); + choose_unary_op<(which & op_str)>::template args::add(this); + } + + template + inline void def_operators(operators, right_operand) + { + def_standard_coerce(); + + choose_op<(which & op_add)>::template args::add(this); + choose_op<(which & op_sub)>::template args::add(this); + choose_op<(which & op_mul)>::template args::add(this); + choose_op<(which & op_div)>::template args::add(this); + choose_op<(which & op_mod)>::template args::add(this); + choose_op<(which & op_divmod)>::template args::add(this); + choose_op<(which & op_pow)>::template args::add(this); + choose_op<(which & op_lshift)>::template args::add(this); + choose_op<(which & op_rshift)>::template args::add(this); + choose_op<(which & op_and)>::template args::add(this); + choose_op<(which & op_xor)>::template args::add(this); + choose_op<(which & op_or)>::template args::add(this); + choose_op<(which & op_cmp)>::template args::add(this); + } + + template + inline void def_operators(operators, left_operand) + { + def_standard_coerce(); + + choose_rop<(which & op_add)>::template args::add(this); + choose_rop<(which & op_sub)>::template args::add(this); + choose_rop<(which & op_mul)>::template args::add(this); + choose_rop<(which & op_div)>::template args::add(this); + choose_rop<(which & op_mod)>::template args::add(this); + choose_rop<(which & op_divmod)>::template args::add(this); + choose_rop<(which & op_pow)>::template args::add(this); + choose_rop<(which & op_lshift)>::template args::add(this); + choose_rop<(which & op_rshift)>::template args::add(this); + choose_rop<(which & op_and)>::template args::add(this); + choose_rop<(which & op_xor)>::template args::add(this); + choose_rop<(which & op_or)>::template args::add(this); + choose_rop<(which & op_cmp)>::template args::add(this); + } + + template + void add_constructor(signature sig) + { + this->add_constructor_object(init_function::create(sig)); + } +}; + +// A simple wrapper over a T which allows us to use extension_class with a +// single template parameter only. See extension_class, above. +template +class held_instance : public T +{ + // There are no member functions: we want to avoid inadvertently overriding + // any virtual functions in T. +public: + held_instance(PyObject*) : T() {} + template + held_instance(PyObject*, A1 a1) : T(a1) {} + template + held_instance(PyObject*, A1 a1, A2 a2) : T(a1, a2) {} + template + held_instance(PyObject*, A1 a1, A2 a2, A3 a3) : T(a1, a2, a3) {} + template + held_instance(PyObject*, A1 a1, A2 a2, A3 a3, A4 a4) : T(a1, a2, a3, a4) {} + template + held_instance(PyObject*, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) : T(a1, a2, a3, a4, a5) {} +}; + +// Abstract base class for all obj holders. Base for template class +// instance_holder<>, below. +class instance_holder_base +{ +public: + virtual ~instance_holder_base() {} + virtual bool held_by_value() = 0; +}; + +// Abstract base class which holds a Held, somehow. Provides a uniform way to +// get a pointer to the held object +template +class instance_holder : public instance_holder_base +{ +public: + virtual Held*target() = 0; +}; + +// Concrete class which holds a Held by way of a wrapper class Wrapper. If Held +// can be constructed with arguments (A1...An), Wrapper must have a +// corresponding constructor for arguments (PyObject*, A1...An). Wrapper is +// neccessary to implement virtual function callbacks (there must be a +// back-pointer to the actual Python object so that we can call any +// overrides). held_instance (above) is used as a default Wrapper class when +// there are no virtual functions. +template +class instance_value_holder : public instance_holder +{ +public: + Held* target() { return &m_held; } + Wrapper* value_target() { return &m_held; } + + instance_value_holder(extension_instance* p) : + m_held(p) {} + template + instance_value_holder(extension_instance* p, A1 a1) : + m_held(p, a1) {} + template + instance_value_holder(extension_instance* p, A1 a1, A2 a2) : + m_held(p, a1, a2) {} + template + instance_value_holder(extension_instance* p, A1 a1, A2 a2, A3 a3) : + m_held(p, a1, a2, a3) {} + template + instance_value_holder(extension_instance* p, A1 a1, A2 a2, A3 a3, A4 a4) : + m_held(p, a1, a2, a3, a4) {} + template + instance_value_holder(extension_instance* p, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) : + m_held(p, a1, a2, a3, a4, a5) {} + + public: // implementation of instance_holder_base required interface + bool held_by_value() { return true; } + + private: + Wrapper m_held; +}; + +// Concrete class which holds a HeldType by way of a (possibly smart) pointer +// PtrType. By default, these are only generated for PtrType == +// std::auto_ptr and PtrType == boost::shared_ptr. +template +class instance_ptr_holder : public instance_holder +{ + public: + HeldType* target() { return &*m_ptr; } + PtrType& ptr() { return m_ptr; } + + instance_ptr_holder(PtrType ptr) : m_ptr(ptr) {} + + public: // implementation of instance_holder_base required interface + bool held_by_value() { return false; } + private: + PtrType m_ptr; +}; + +class extension_instance : public instance +{ + public: + extension_instance(PyTypeObject* class_); + ~extension_instance(); + + void add_implementation(std::auto_ptr holder); + + typedef std::vector held_objects; + const held_objects& wrapped_objects() const + { return m_wrapped_objects; } + private: + held_objects m_wrapped_objects; +}; + +// +// Template function implementations +// + +tuple extension_class_coerce(ref l, ref r); + +template +extension_class::extension_class() + : extension_class_base(typeid(T).name()) +{ + class_registry::register_class(this); +} + +template +extension_class::extension_class(const char* name) + : extension_class_base(name) +{ + class_registry::register_class(this); +} + +template +void extension_class::def_standard_coerce() +{ + ref coerce_fct = dict().get_item(string("__coerce__")); + + if(coerce_fct.get() == 0) // not yet defined + this->def(&extension_class_coerce, "__coerce__"); +} + +template +inline +std::vector const& +extension_class::base_classes() const +{ + return class_registry::base_classes(); +} + +template +inline +std::vector const& +extension_class::derived_classes() const +{ + return class_registry::derived_classes(); +} + +template +void* extension_class::extract_object_from_holder(instance_holder_base* v) const +{ + instance_holder* held = dynamic_cast*>(v); + if(held) + return held->target(); + return 0; +} + +template +extension_class::~extension_class() +{ + class_registry::unregister_class(this); +} + +template +inline void class_registry::register_class(extension_class_base* p) +{ + // You're not expected to create more than one of these! + assert(static_class_object == 0); + static_class_object = p; +} + +template +inline void class_registry::unregister_class(extension_class_base* p) +{ + // The user should be destroying the same object they created. + assert(static_class_object == p); + (void)p; // unused in shipping version + static_class_object = 0; +} + +template +void class_registry::register_base_class(base_class_info const& i) +{ + static_base_class_info.push_back(i); +} + +template +void class_registry::register_derived_class(derived_class_info const& i) +{ + static_derived_class_info.push_back(i); +} + +template +std::vector const& class_registry::base_classes() +{ + return static_base_class_info; +} + +template +std::vector const& class_registry::derived_classes() +{ + return static_derived_class_info; +} + +// +// Static data member declaration. +// +template +extension_class_base* class_registry::static_class_object; +template +std::vector class_registry::static_base_class_info; +template +std::vector class_registry::static_derived_class_info; + +}} // namespace python::detail + +#endif // EXTENSION_CLASS_DWA052000_H_ + diff --git a/extclass_demo.cpp b/extclass_demo.cpp new file mode 100644 index 00000000..b8e90c02 --- /dev/null +++ b/extclass_demo.cpp @@ -0,0 +1,1131 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +#include "extclass_demo.h" +#include "class_wrapper.h" +#include // used for portability on broken compilers +#include // for pow() +#include + +namespace extclass_demo { + +FooCallback::FooCallback(PyObject* self, int x) + : Foo(x), m_self(self) +{ +} + +int FooCallback::add_len(const char* x) const +{ + // Try to call the "add_len" method on the corresponding Python object. + return python::callback::call_method(m_self, "add_len", x); +} + +// A function which Python can call in case bar is not overridden from +// Python. In true Python style, we use a free function taking an initial self +// parameter. This function anywhere needn't be a static member of the callback +// class. The only reason to do it this way is that Foo::add_len is private, and +// FooCallback is already a friend of Foo. +int FooCallback::default_add_len(const Foo* self, const char* x) +{ + // Don't forget the Foo:: qualification, or you'll get an infinite + // recursion! + return self->Foo::add_len(x); +} + +// Since Foo::pure() is pure virtual, we don't need a corresponding +// default_pure(). A failure to override it in Python will result in an +// exception at runtime when pure() is called. +std::string FooCallback::pure() const +{ + return python::callback::call_method(m_self, "pure"); +} + +Foo::PythonClass::PythonClass(python::module_builder& m) + : python::class_builder(m, "Foo") +{ + def(python::constructor()); + def(&Foo::mumble, "mumble"); + def(&Foo::set, "set"); + def(&Foo::call_pure, "call_pure"); + def(&Foo::call_add_len, "call_add_len"); + + // This is the way we add a virtual function that has a default implementation. + def(&Foo::add_len, "add_len", &FooCallback::default_add_len); + + // Since pure() is pure virtual, we are leaving it undefined. +} + +BarPythonClass::BarPythonClass(python::module_builder& m) + : python::class_builder(m, "Bar") +{ + def(python::constructor()); + def(&Bar::first, "first"); + def(&Bar::second, "second"); + def(&Bar::pass_baz, "pass_baz"); +} + +BazPythonClass::BazPythonClass(python::module_builder& m) + : python::class_builder(m, "Baz") // optional +{ + def(python::constructor<>()); + def(&Baz::pass_bar, "pass_bar"); + def(&Baz::clone, "clone"); + def(&Baz::create_foo, "create_foo"); + def(&Baz::get_foo_value, "get_foo_value"); + def(&Baz::eat_baz, "eat_baz"); +} + +StringMapPythonClass::StringMapPythonClass(python::module_builder& m) + : python::class_builder(m, "StringMap") +{ + def(python::constructor<>()); + def(&StringMap::size, "__len__"); + def(&get_item, "__getitem__"); + def(&set_item, "__setitem__"); + def(&del_item, "__delitem__"); +} + +int get_first(const IntPair& p) +{ + return p.first; +} + +void set_first(IntPair& p, int value) +{ + p.first = -value; +} + +void del_first(const IntPair&) +{ + PyErr_SetString(PyExc_AttributeError, "first can't be deleted!"); + throw python::error_already_set(); +} + +IntPairPythonClass::IntPairPythonClass(python::module_builder& m) + : python::class_builder(m, "IntPair") +{ + def(python::constructor()); + def(&getattr, "__getattr__"); + def(&setattr, "__setattr__"); + def(&delattr, "__delattr__"); + def(&get_first, "__getattr__first__"); + def(&set_first, "__setattr__first__"); + def(&del_first, "__delattr__first__"); +} + +void IntPairPythonClass::setattr(IntPair& x, const std::string& name, int value) +{ + if (name == "second") + { + x.second = value; + } + else + { + PyErr_SetString(PyExc_AttributeError, name.c_str()); + throw python::error_already_set(); + } +} + +void IntPairPythonClass::delattr(IntPair&, const char*) +{ + PyErr_SetString(PyExc_AttributeError, "Attributes can't be deleted!"); + throw python::error_already_set(); +} + +int IntPairPythonClass::getattr(const IntPair& p, const std::string& s) +{ + if (s == "second") + { + return p.second; + } + else + { + PyErr_SetString(PyExc_AttributeError, s.c_str()); + throw python::error_already_set(); + } +#if defined(__MWERKS__) && __MWERKS__ <= 0x2400 + return 0; +#endif +} + +namespace { namespace file_local { +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, BOOST_PYTHON_CONVERSION::to_python(key)); + throw python::error_already_set(); + } +} +}} // namespace ::file_local + +const std::string& StringMapPythonClass::get_item(const StringMap& m, std::size_t key) +{ + const StringMap::const_iterator p = m.find(key); + file_local::throw_key_error_if_end(m, p, key); + return p->second; +} + +void StringMapPythonClass::set_item(StringMap& m, std::size_t key, const std::string& value) +{ + m[key] = value; +} + +void StringMapPythonClass::del_item(StringMap& m, std::size_t key) +{ + const StringMap::iterator p = m.find(key); + file_local::throw_key_error_if_end(m, p, key); + m.erase(p); +} + +// +// Show that polymorphism can work. a DerivedFromFoo object will be passed to +// Python in a smart pointer object. +// +class DerivedFromFoo : public Foo +{ +public: + DerivedFromFoo(int x) : Foo(x) {} + +private: + std::string pure() const + { return "this was never pure!"; } + + int add_len(const char*) const + { return 1000; } +}; + +// +// function implementations +// + +IntPair make_pair(int x, int y) +{ + return std::make_pair(x, y); +} + +const char* Foo::mumble() +{ + return "mumble"; +} + +void Foo::set(long x) +{ + m_x = x; +} + +std::string Foo::call_pure() +{ + return this->pure(); +} + +int Foo::call_add_len(const char* s) const +{ + return this->add_len(s); +} + +int Foo::add_len(const char* s) const // sum the held value and the length of s +{ + return BOOST_CSTD_::strlen(s) + static_cast(m_x); +} + +boost::shared_ptr Baz::create_foo() +{ + return boost::shared_ptr(new DerivedFromFoo(0)); +} + +// We can accept smart pointer parameters +int Baz::get_foo_value(boost::shared_ptr foo) +{ + return foo->call_add_len(""); +} + +// Show what happens in python when we take ownership from an auto_ptr +void Baz::eat_baz(std::auto_ptr baz) +{ + baz->clone(); // just do something to show that it is valid. +} + +Baz Bar::pass_baz(Baz b) +{ + return b; +} + +std::string stringpair_repr(const StringPair& sp) +{ + return "('" + sp.first + "', '" + sp.second + "')"; +} + +int stringpair_compare(const StringPair& sp1, const StringPair& sp2) +{ + return sp1 < sp2 ? -1 : sp2 < sp1 ? 1 : 0; +} + +python::string range_str(const Range& r) +{ + char buf[200]; + sprintf(buf, "(%d, %d)", r.m_start, r.m_finish); + return python::string(buf); +} + +int range_compare(const Range& r1, const Range& r2) +{ + int d = r1.m_start - r2.m_start; + if (d == 0) + d = r1.m_finish - r2.m_finish; + return d; +} + +long range_hash(const Range& r) +{ + return r.m_start * 123 + r.m_finish; +} + +/************************************************************/ +/* */ +/* some functions to test overloading */ +/* */ +/************************************************************/ + +static std::string testVoid() +{ + return std::string("Hello world!"); +} + +static int testInt(int i) +{ + return i; +} + +static std::string testString(std::string i) +{ + return i; +} + +static int test2(int i1, int i2) +{ + return i1+i2; +} + +static int test3(int i1, int i2, int i3) +{ + return i1+i2+i3; +} + +static int test4(int i1, int i2, int i3, int i4) +{ + return i1+i2+i3+i4; +} + +static int test5(int i1, int i2, int i3, int i4, int i5) +{ + return i1+i2+i3+i4+i5; +} + +/************************************************************/ +/* */ +/* a class to test overloading */ +/* */ +/************************************************************/ + +struct OverloadTest +{ + OverloadTest(): x_(1000) {} + OverloadTest(int x): x_(x) {} + OverloadTest(int x,int y): x_(x+y) { } + OverloadTest(int x,int y,int z): x_(x+y+z) {} + OverloadTest(int x,int y,int z, int a): x_(x+y+z+a) {} + OverloadTest(int x,int y,int z, int a, int b): x_(x+y+z+a+b) {} + + int x() const { return x_; } + void setX(int x) { x_ = x; } + + int p1(int x) { return x; } + int p2(int x, int y) { return x + y; } + int p3(int x, int y, int z) { return x + y + z; } + int p4(int x, int y, int z, int a) { return x + y + z + a; } + int p5(int x, int y, int z, int a, int b) { return x + y + z + a + b; } + private: + int x_; +}; + +static int getX(OverloadTest* u) +{ + return u->x(); +} + + +/************************************************************/ +/* */ +/* classes to test base declarations and conversions */ +/* */ +/************************************************************/ + +struct Dummy +{ + virtual ~Dummy() {} + int dummy_; +}; + +struct Base +{ + virtual int x() const { return 999; }; + virtual ~Base() {} +}; + +// inherit Dummy so that the Base part of Concrete starts at an offset +// otherwise, typecast tests wouldn't be very meaningful +struct Derived1 : public Dummy, public Base +{ + Derived1(int x): x_(x) {} + virtual int x() const { return x_; } + + private: + int x_; +}; + +struct Derived2 : public Dummy, public Base +{ + Derived2(int x): x_(x) {} + virtual int x() const { return x_; } + + private: + int x_; +}; + +static int testUpcast(Base* b) +{ + return b->x(); +} + +static std::auto_ptr derived1Factory(int i) +{ + return std::auto_ptr(new Derived1(i)); +} + +static std::auto_ptr derived2Factory(int i) +{ + return std::auto_ptr(new Derived2(i)); +} + +static int testDowncast1(Derived1* d) +{ + return d->x(); +} + +static int testDowncast2(Derived2* d) +{ + return d->x(); +} + +/************************************************************/ +/* */ +/* test classes for interaction of overloading, */ +/* base declarations, and callbacks */ +/* */ +/************************************************************/ + +struct CallbackTestBase +{ + virtual int testCallback(int i) { return callback(i); } + virtual int callback(int i) = 0; + virtual ~CallbackTestBase() {} +}; + +struct CallbackTest : public CallbackTestBase +{ + virtual int callback(int i) { return i + 1; } + virtual std::string callbackString(std::string const & i) { return i + " 1"; } +}; + +struct CallbackTestCallback : public CallbackTest +{ + CallbackTestCallback(PyObject* self) + : m_self(self) + {} + + int callback(int x) + { + return python::callback::call_method(m_self, "callback", x); + } + std::string callbackString(std::string const & x) + { + return python::callback::call_method(m_self, "callback", x); + } + + static int default_callback(CallbackTest* self, int x) + { + return self->CallbackTest::callback(x); + } + static std::string default_callbackString(CallbackTest* self, std::string x) + { + return self->CallbackTest::callbackString(x); + } + + PyObject* m_self; +}; + +int testCallback(CallbackTestBase* b, int i) +{ + return b->testCallback(i); +} + +/************************************************************/ +/* */ +/* test classes for interaction of method lookup */ +/* in the context of inheritance */ +/* */ +/************************************************************/ + +struct A1 { + virtual ~A1() {} + virtual std::string overrideA1() const { return "A1::overrideA1"; } + virtual std::string inheritA1() const { return "A1::inheritA1"; } +}; + +struct A2 { + virtual ~A2() {} + virtual std::string inheritA2() const { return "A2::inheritA2"; } +}; + +struct B1 : A1, A2 { + std::string overrideA1() const { return "B1::overrideA1"; } + virtual std::string overrideB1() const { return "B1::overrideB1"; } +}; + +struct B2 : A1, A2 { + std::string overrideA1() const { return "B2::overrideA1"; } + virtual std::string inheritB2() const { return "B2::inheritB2"; } +}; + +struct C : B1 { + std::string overrideB1() const { return "C::overrideB1"; } +}; + +std::string call_overrideA1(const A1& a) { return a.overrideA1(); } +std::string call_overrideB1(const B1& b) { return b.overrideB1(); } +std::string call_inheritA1(const A1& a) { return a.inheritA1(); } + +std::auto_ptr factoryA1asA1() { return std::auto_ptr(new A1); } +std::auto_ptr factoryB1asA1() { return std::auto_ptr(new B1); } +std::auto_ptr factoryB2asA1() { return std::auto_ptr(new B2); } +std::auto_ptr factoryCasA1() { return std::auto_ptr(new C); } +std::auto_ptr factoryA2asA2() { return std::auto_ptr(new A2); } +std::auto_ptr factoryB1asA2() { return std::auto_ptr(new B1); } +std::auto_ptr factoryB1asB1() { return std::auto_ptr(new B1); } +std::auto_ptr factoryCasB1() { return std::auto_ptr(new C); } + +struct B_callback : B1 { + B_callback(PyObject* self) : m_self(self) {} + + std::string overrideA1() const { return python::callback::call_method(m_self, "overrideA1"); } + std::string overrideB1() const { return python::callback::call_method(m_self, "overrideB1"); } + + static std::string default_overrideA1(B1& x) { return x.B1::overrideA1(); } + static std::string default_overrideB1(B1& x) { return x.B1::overrideB1(); } + + PyObject* m_self; +}; + +struct A_callback : A1 { + A_callback(PyObject* self) : m_self(self) {} + + std::string overrideA1() const { return python::callback::call_method(m_self, "overrideA1"); } + std::string inheritA1() const { return python::callback::call_method(m_self, "inheritA1"); } + + static std::string default_overrideA1(A1& x) { return x.A1::overrideA1(); } + static std::string default_inheritA1(A1& x) { return x.A1::inheritA1(); } + + PyObject* m_self; +}; + +/************************************************************/ +/* */ +/* RawTest */ +/* (test passing of raw arguments to C++) */ +/* */ +/************************************************************/ + +struct RawTest +{ + RawTest(int i) : i_(i) {} + + int i_; +}; + +PyObject* raw(python::tuple const & args, python::dictionary const & keywords); + +int raw1(PyObject* args, PyObject* keywords) +{ + return PyTuple_Size(args) + PyDict_Size(keywords); +} + +int raw2(python::ref args, python::ref keywords) +{ + return PyTuple_Size(args.get()) + PyDict_Size(keywords.get()); +} + + + +/************************************************************/ +/* */ +/* Ratio */ +/* */ +/************************************************************/ + +typedef boost::rational Ratio; + +python::string ratio_str(const Ratio& r) +{ + char buf[200]; + + if (r.denominator() == 1) + sprintf(buf, "%d", r.numerator()); + else + sprintf(buf, "%d/%d", r.numerator(), r.denominator()); + + return python::string(buf); +} + +python::string ratio_repr(const Ratio& r) +{ + char buf[200]; + sprintf(buf, "Rational(%d, %d)", r.numerator(), r.denominator()); + return python::string(buf); +} + +python::tuple ratio_coerce(const Ratio& r1, int r2) +{ + return python::tuple(r1, Ratio(r2)); +} + +// The most reliable way, across compilers, to grab the particular abs function +// we're interested in. +Ratio ratio_abs(const Ratio& r) +{ + return boost::abs(r); +} + +// An experiment, to be integrated into the py_cpp library at some point. +template +struct StandardOps +{ + static T add(const T& x, const T& y) { return x + y; } + static T sub(const T& x, const T& y) { return x - y; } + static T mul(const T& x, const T& y) { return x * y; } + static T div(const T& x, const T& y) { return x / y; } + static T cmp(const T& x, const T& y) { return std::less()(x, y) ? -1 : std::less()(y, x) ? 1 : 0; } +}; + +// This helps us prove that we can now pass non-const reference parameters to constructors +struct Fubar { + Fubar(Foo&) {} + Fubar(int) {} +}; + +/************************************************************/ +/* */ +/* Int */ +/* this class tests operator export */ +/* */ +/************************************************************/ + +#ifndef NDEBUG +int total_Ints = 0; +#endif + +struct Int +{ + explicit Int(int i) : i_(i) { +#ifndef NDEBUG + ++total_Ints; +#endif + } + +#ifndef NDEBUG + ~Int() { --total_Ints; } + Int(const Int& rhs) : i_(rhs.i_) { ++total_Ints; } +#endif + + int i() const { return i_; } + + int i_; +}; + +Int operator+(Int const & l, Int const & r) { return Int(l.i_ + r.i_); } +Int operator+(Int const & l, int const & r) { return Int(l.i_ + r); } +Int operator+(int const & l, Int const & r) { return Int(l + r.i_); } + +Int operator-(Int const & l, Int const & r) { return Int(l.i_ - r.i_); } +Int operator-(Int const & l, int const & r) { return Int(l.i_ - r); } +Int operator-(int const & l, Int const & r) { return Int(l - r.i_); } +Int operator-(Int const & r) { return Int(- r.i_); } + +Int mul(Int const & l, Int const & r) { return Int(l.i_ * r.i_); } +Int imul(Int const & l, int const & r) { return Int(l.i_ * r); } +Int rmul(Int const & r, int const & l) { return Int(l * r.i_); } + +Int operator/(Int const & l, Int const & r) { return Int(l.i_ / r.i_); } + +Int operator%(Int const & l, Int const & r) { return Int(l.i_ % r.i_); } + +bool operator<(Int const & l, Int const & r) { return l.i_ < r.i_; } +bool operator<(Int const & l, int const & r) { return l.i_ < r; } +bool operator<(int const & l, Int const & r) { return l < r.i_; } + +Int pow(Int const & l, Int const & r) { return Int(static_cast(::pow(l.i_, r.i_))); } +Int powmod(Int const & l, Int const & r, Int const & m) { return Int((int)::pow(l.i_, r.i_) % m.i_); } +Int pow(Int const & l, int const & r) { return Int(static_cast(::pow(l.i_, r))); } + +std::ostream & operator<<(std::ostream & o, Int const & r) { return (o << r.i_); } + +/************************************************************/ +/* */ +/* double tests from Mark Evans() */ +/* */ +/************************************************************/ +double sizelist(python::list list) { return list.size(); } +void vd_push_back(std::vector& vd, const double& x) +{ + vd.push_back(x); +} + +/************************************************************/ +/* */ +/* What if I want to return a pointer? */ +/* */ +/************************************************************/ + +// +// This example exposes the pointer by copying its referent +// +struct Record { + Record(int x) : value(x){} + int value; +}; + +const Record* get_record() +{ + static Record v(1234); + return &v; +} + +template class python::class_builder; // explicitly instantiate + +} // namespace extclass_demo + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE +inline PyObject* to_python(const extclass_demo::Record* p) +{ + return to_python(*p); +} +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +/************************************************************/ +/* */ +/* Enums and non-method class attributes */ +/* */ +/************************************************************/ + +namespace extclass_demo { + +struct EnumOwner +{ + public: + enum enum_type { one = 1, two = 2, three = 3 }; + + EnumOwner(enum_type a1, const enum_type& a2) + : m_first(a1), m_second(a2) {} + + void set_first(const enum_type& x) { m_first = x; } + void set_second(const enum_type& x) { m_second = x; } + + enum_type first() { return m_first; } + enum_type second() { return m_second; } + private: + enum_type m_first, m_second; +}; + +} + +namespace python { + template class enum_as_int_converters; + using extclass_demo::pow; +} + +// This is just a way of getting the converters instantiated +//struct EnumOwner_enum_type_Converters +// : python::py_enum_as_int_converters +//{ +//}; + +namespace extclass_demo { + +/************************************************************/ +/* */ +/* pickling support */ +/* */ +/************************************************************/ + class world + { + private: + std::string country; + int secret_number; + public: + world(const std::string& country) : secret_number(0) { + this->country = country; + } + std::string greet() const { return "Hello from " + country + "!"; } + std::string get_country() const { return country; } + void set_secret_number(int number) { secret_number = number; } + int get_secret_number() const { return secret_number; } + }; + + // Support for pickle. + python::tuple world_getinitargs(const world& w) + { + python::tuple result(1); + result.set_item(0, w.get_country()); + return result; + } + + python::tuple world_getstate(const world& w) + { + python::tuple result(1); + result.set_item(0, w.get_secret_number()); + return result; + } + + void world_setstate(world& w, python::tuple state) + { + if (state.size() != 1) { + PyErr_SetString(PyExc_ValueError, + "Unexpected argument in call to __setstate__."); + throw python::error_already_set(); + } + + const int number = BOOST_PYTHON_CONVERSION::from_python(state[0].get(), python::type()); + if (number != 42) + w.set_secret_number(number); + } + +/************************************************************/ +/* */ +/* init the module */ +/* */ +/************************************************************/ + +void init_module(python::module_builder& m) +{ + m.def(get_record, "get_record"); + python::class_builder record_class(m, "Record"); + record_class.def_readonly(&Record::value, "value"); + + m.def(sizelist, "sizelist"); + + python::class_builder > vector_double(m, "vector_double"); + vector_double.def(python::constructor<>()); + vector_double.def(vd_push_back, "push_back"); + + python::class_builder fubar(m, "Fubar"); + fubar.def(python::constructor()); + fubar.def(python::constructor()); + + Foo::PythonClass foo(m); + BarPythonClass bar(m); + BazPythonClass baz(m); + StringMapPythonClass string_map(m); + IntPairPythonClass int_pair(m); + m.def(make_pair, "make_pair"); + CompareIntPairPythonClass compare_int_pair(m); + + python::class_builder string_pair(m, "StringPair"); + string_pair.def(python::constructor()); + string_pair.def_readonly(&StringPair::first, "first"); + string_pair.def_read_write(&StringPair::second, "second"); + string_pair.def(&stringpair_repr, "__repr__"); + string_pair.def(&stringpair_compare, "__cmp__"); + m.def(first_string, "first_string"); + m.def(second_string, "second_string"); + + // This shows the wrapping of a 3rd-party numeric type. + python::class_builder > rational(m, "Rational"); + rational.def(python::constructor()); + rational.def(python::constructor()); + rational.def(python::constructor<>()); + rational.def(StandardOps::add, "__add__"); + rational.def(StandardOps::sub, "__sub__"); + rational.def(StandardOps::mul, "__mul__"); + rational.def(StandardOps::div, "__div__"); + rational.def(StandardOps::cmp, "__cmp__"); + rational.def(ratio_coerce, "__coerce__"); + rational.def(ratio_str, "__str__"); + rational.def(ratio_repr, "__repr__"); + rational.def(ratio_abs, "__abs__"); + + python::class_builder range(m, "Range"); + range.def(python::constructor()); + range.def(python::constructor()); + range.def((void (Range::*)(std::size_t))&Range::length, "__len__"); + range.def((std::size_t (Range::*)() const)&Range::length, "__len__"); + range.def(&Range::operator[], "__getitem__"); + range.def(&Range::slice, "__getslice__"); + range.def(&range_str, "__str__"); + range.def(&range_compare, "__cmp__"); + range.def(&range_hash, "__hash__"); + range.def_readonly(&Range::m_start, "start"); + range.def_readonly(&Range::m_finish, "finish"); + + m.def(&testVoid, "overloaded"); + m.def(&testInt, "overloaded"); + m.def(&testString, "overloaded"); + m.def(&test2, "overloaded"); + m.def(&test3, "overloaded"); + m.def(&test4, "overloaded"); + m.def(&test5, "overloaded"); + + python::class_builder over(m, "OverloadTest"); + over.def(python::constructor<>()); + over.def(python::constructor()); + over.def(python::constructor()); + over.def(python::constructor()); + over.def(python::constructor()); + over.def(python::constructor()); + over.def(python::constructor()); + over.def(&getX, "getX"); + over.def(&OverloadTest::setX, "setX"); + over.def(&OverloadTest::x, "overloaded"); + over.def(&OverloadTest::p1, "overloaded"); + over.def(&OverloadTest::p2, "overloaded"); + over.def(&OverloadTest::p3, "overloaded"); + over.def(&OverloadTest::p4, "overloaded"); + over.def(&OverloadTest::p5, "overloaded"); + + python::class_builder base(m, "Base"); + base.def(&Base::x, "x"); + + python::class_builder derived1(m, "Derived1"); + // this enables conversions between Base and Derived1 + // and makes wrapped methods of Base available + derived1.declare_base(base); + derived1.def(python::constructor()); + + python::class_builder derived2(m, "Derived2"); + // don't enable downcast from Base to Derived2 + derived2.declare_base(base, python::without_downcast); + derived2.def(python::constructor()); + + m.def(&testUpcast, "testUpcast"); + m.def(&derived1Factory, "derived1Factory"); + m.def(&derived2Factory, "derived2Factory"); + m.def(&testDowncast1, "testDowncast1"); + m.def(&testDowncast2, "testDowncast2"); + + python::class_builder callbackTestBase(m, "CallbackTestBase"); + callbackTestBase.def(&CallbackTestBase::testCallback, "testCallback"); + m.def(&testCallback, "testCallback"); + + python::class_builder callbackTest(m, "CallbackTest"); + callbackTest.def(python::constructor<>()); + callbackTest.def(&CallbackTest::callback, "callback", + &CallbackTestCallback::default_callback); + callbackTest.def(&CallbackTest::callbackString, "callback", + &CallbackTestCallback::default_callbackString); + + callbackTest.declare_base(callbackTestBase); + + python::class_builder a1_class(m, "A1"); + a1_class.def(python::constructor<>()); + a1_class.def(&A1::overrideA1, "overrideA1", &A_callback::default_overrideA1); + a1_class.def(&A1::inheritA1, "inheritA1", &A_callback::default_inheritA1); + + python::class_builder a2_class(m, "A2"); + a2_class.def(python::constructor<>()); + a2_class.def(&A2::inheritA2, "inheritA2"); + + python::class_builder b1_class(m, "B1"); + b1_class.declare_base(a1_class); + b1_class.declare_base(a2_class); + + b1_class.def(python::constructor<>()); + b1_class.def(&B1::overrideA1, "overrideA1", &B_callback::default_overrideA1); + b1_class.def(&B1::overrideB1, "overrideB1", &B_callback::default_overrideB1); + + python::class_builder b2_class(m, "B2"); + b2_class.declare_base(a1_class); + b2_class.declare_base(a2_class); + + b2_class.def(python::constructor<>()); + b2_class.def(&B2::overrideA1, "overrideA1"); + b2_class.def(&B2::inheritB2, "inheritB2"); + + m.def(call_overrideA1, "call_overrideA1"); + m.def(call_overrideB1, "call_overrideB1"); + m.def(call_inheritA1, "call_inheritA1"); + + m.def(factoryA1asA1, "factoryA1asA1"); + m.def(factoryB1asA1, "factoryB1asA1"); + m.def(factoryB2asA1, "factoryB2asA1"); + m.def(factoryCasA1, "factoryCasA1"); + m.def(factoryA2asA2, "factoryA2asA2"); + m.def(factoryB1asA2, "factoryB1asA2"); + m.def(factoryB1asB1, "factoryB1asB1"); + m.def(factoryCasB1, "factoryCasB1"); + + python::class_builder rawtest_class(m, "RawTest"); + rawtest_class.def(python::constructor()); + rawtest_class.def_raw(&raw, "raw"); + + m.def_raw(&raw, "raw"); + m.def_raw(&raw1, "raw1"); + m.def_raw(&raw2, "raw2"); + + python::class_builder int_class(m, "Int"); + int_class.def(python::constructor()); + int_class.def(&Int::i, "i"); + + // wrap homogeneous operators + int_class.def(python::operators<(python::op_add | python::op_sub | python::op_neg | + python::op_cmp | python::op_str | python::op_divmod | python::op_pow )>()); + // export non-operator functions as homogeneous operators + int_class.def(&mul, "__mul__"); + int_class.def(&powmod, "__pow__"); + + // wrap heterogeneous operators (lhs: Int const &, rhs: int const &) + int_class.def(python::operators<(python::op_add | python::op_sub | python::op_cmp | python::op_pow)>(), + python::right_operand()); + // export non-operator function as heterogeneous operator + int_class.def(&imul, "__mul__"); + + // wrap heterogeneous operators (lhs: int const &, rhs: Int const &) + int_class.def(python::operators<(python::op_add | python::op_sub | python::op_cmp)>(), + python::left_operand()); + // export non-operator function as heterogeneous reverse-argument operator + int_class.def(&rmul, "__rmul__"); + + + python::class_builder enum_owner(m, "EnumOwner"); + enum_owner.def(python::constructor()); + enum_owner.def(&EnumOwner::set_first, "__setattr__first__"); + enum_owner.def(&EnumOwner::set_second, "__setattr__second__"); + enum_owner.def(&EnumOwner::first, "__getattr__first__"); + enum_owner.def(&EnumOwner::second, "__getattr__second__"); + enum_owner.add(PyInt_FromLong(EnumOwner::one), "one"); + enum_owner.add(PyInt_FromLong(EnumOwner::two), "two"); + enum_owner.add(PyInt_FromLong(EnumOwner::three), "three"); + + // pickling support + + // Create the Python type object for our extension class. + python::class_builder world_class(m, "world"); + + // Add the __init__ function. + world_class.def(python::constructor()); + // Add a regular member function. + world_class.def(&world::greet, "greet"); + world_class.def(&world::get_secret_number, "get_secret_number"); + world_class.def(&world::set_secret_number, "set_secret_number"); + + // Support for pickle. + world_class.def(world_getinitargs, "__getinitargs__"); + world_class.def(world_getstate, "__getstate__"); + world_class.def(world_setstate, "__setstate__"); +} + +PyObject* raw(python::tuple const& args, python::dictionary const& keywords) +{ + if(args.size() != 2 || keywords.size() != 2) + { + PyErr_SetString(PyExc_TypeError, "wrong number of arguments"); + throw python::argument_error(); + } + + RawTest* first = BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()); + int second = BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()); + + int third = BOOST_PYTHON_CONVERSION::from_python(keywords[python::string("third")].get(), python::type()); + int fourth = BOOST_PYTHON_CONVERSION::from_python(keywords[python::string("fourth")].get(), python::type()); + + return BOOST_PYTHON_CONVERSION::to_python(first->i_ + second + third + fourth); +} + +void init_module() +{ + python::module_builder demo("demo"); + init_module(demo); + + // Just for giggles, add a raw metaclass. + demo.add(new python::meta_class); +} + +extern "C" +#ifdef _WIN32 +__declspec(dllexport) +#endif +void initdemo() +{ + try { + extclass_demo::init_module(); + } + catch(...) { + python::handle_exception(); + } // Need a way to report other errors here +} + +CompareIntPairPythonClass::CompareIntPairPythonClass(python::module_builder& m) + : python::class_builder(m, "CompareIntPair") +{ + def(python::constructor<>()); + def(&CompareIntPair::operator(), "__call__"); +} + +} // namespace extclass_demo + + +#if defined(_WIN32) +# ifdef __MWERKS__ +# pragma ANSI_strict off +# endif +# include +# ifdef __MWERKS__ +# pragma ANSI_strict reset +# endif +extern "C" BOOL WINAPI DllMain ( HINSTANCE hInst, DWORD wDataSeg, LPVOID lpvReserved ); + +# ifdef BOOST_MSVC +extern "C" void structured_exception_translator(unsigned int, EXCEPTION_POINTERS*) +{ + throw; +} +# endif + +#ifndef NDEBUG +namespace python { namespace detail { extern int total_Dispatchers; }} +#endif + +BOOL WINAPI DllMain( + HINSTANCE, //hDllInst + DWORD fdwReason, + LPVOID // lpvReserved + ) +{ +# ifdef BOOST_MSVC + _set_se_translator(structured_exception_translator); +#endif + (void)fdwReason; // warning suppression. + +#ifndef NDEBUG + switch(fdwReason) + { + case DLL_PROCESS_DETACH: + assert(extclass_demo::total_Ints == 0); + } +#endif + + return 1; +} +#endif // _WIN32 diff --git a/extclass_demo.h b/extclass_demo.h new file mode 100644 index 00000000..fd21dfb6 --- /dev/null +++ b/extclass_demo.h @@ -0,0 +1,231 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef EXTCLASS_DEMO_DWA052200_H_ +# define EXTCLASS_DEMO_DWA052200_H_ +// +// Example code demonstrating extension class usage +// + +# include "class_wrapper.h" +# include "callback.h" +# include +# include +# include +# include +# include +# include + +namespace extclass_demo { + +// +// example: Foo, Bar, and Baz are C++ classes we want to wrap. +// + +class Foo // prohibit copying, proving that it doesn't choke + : boost::noncopyable // our generation of to_python(). +{ + public: // constructor/destructor + Foo(int x) : m_x(x) {} + virtual ~Foo() {} + + public: // non-virtual functions + const char* mumble(); // mumble something + void set(long x); // change the held value + + // These two call virtual functions + std::string call_pure(); // call a pure virtual fuction + int call_add_len(const char* s) const; // virtual function with a default implementation + + private: + // by default, sum the held value and the length of s + virtual int add_len(const char* s) const; + + // Derived classes can do whatever they want here, but they must do something! + virtual std::string pure() const = 0; + + public: // friend declarations + // If you have private virtual functions such as add_len which you want to + // override in Python and have default implementations, they must be + // accessible by the thing making the def() call on the extension_class (in + // this case, the nested PythonClass itself), and by the C++ derived class + // which is used to cause the Python callbacks (in this case, + // FooCallback). See the definition of FooCallback::add_len() + struct PythonClass; + friend struct PythonClass; + friend class FooCallback; + + private: + int m_x; // the held value +}; + +// +// Bar and Baz have mutually-recursive type conversion dependencies (see +// pass_xxx functions). I've done this to prove that it doesn't cause a +// problem for Python class definitions, which happen later. +// +// Bar and Baz functions are only virtual to increase the likelihood of a crash +// if I inadvertently use a pointer to garbage memory (a likely thing to test +// for considering the amount of type casting needed to translate to and from +// Python). +struct Baz; +struct Bar +{ + Bar(int x, int y) : m_first(x), m_second(y) {} + virtual int first() const { return m_first; } + virtual int second() const { return m_second; } + virtual Baz pass_baz(Baz x); + + int m_first, m_second; +}; + +struct Baz +{ + virtual Bar pass_bar(const Bar& x) { return x; } + + // We can return smart pointers + virtual std::auto_ptr clone() { return std::auto_ptr(new Baz(*this)); } + + // This illustrates creating a polymorphic derived class of Foo + virtual boost::shared_ptr create_foo(); + + // We can accept smart pointer parameters + virtual int get_foo_value(boost::shared_ptr); + + // Show what happens in python when we take ownership from an auto_ptr + virtual void eat_baz(std::auto_ptr); +}; + +typedef std::map StringMap; +typedef std::pair IntPair; + +IntPair make_pair(int, int); + +typedef std::less CompareIntPair; +typedef std::pair StringPair; + +inline std::string first_string(const StringPair& x) +{ + return x.first; +} + +inline std::string second_string(const StringPair& x) +{ + return x.second; +} + +struct Range +{ + Range(int x) + : m_start(x), m_finish(x) {} + + Range(int start, int finish) + : m_start(start), m_finish(finish) {} + + std::size_t length() const + { return m_finish < m_start ? 0 : m_finish - m_start; } + + void length(std::size_t new_length) + { m_finish = m_start + new_length; } + + int operator[](std::size_t n) + { return m_start + n; } + + Range slice(std::size_t start, std::size_t end) + { + if (start > length()) + start = length(); + if (end > length()) + end = length(); + return Range(m_start + start, m_start + end); + } + + int m_start, m_finish; +}; + +//////////////////////////////////////////////////////////////////////// +// // +// Begin wrapping code. Usually this would live in a separate header. // +// // +//////////////////////////////////////////////////////////////////////// + +// Since Foo has virtual functions which we want overriden in Python, we must +// derive FooCallback. +class FooCallback : public Foo +{ + public: + // Note the additional constructor parameter "self", which is needed to + // allow function overriding from Python. + FooCallback(PyObject* self, int x); + + friend struct PythonClass; // give it access to the functions below + + private: // implementations of Foo virtual functions that are overridable in python. + int add_len(const char* x) const; + + // A function which Python can call in case bar is not overridden from + // Python. In true Python style, we use a free function taking an initial + // self parameter. You can put this function anywhere; it needn't be a + // static member of the wrapping class. + static int default_add_len(const Foo* self, const char* x); + + // Since Foo::pure() is pure virtual, we don't need a corresponding + // default_pure(). A failure to override it in Python will result in an + // exception at runtime when pure() is called. + std::string pure() const; + + private: // Required boilerplate if functions will be overridden + PyObject* m_self; // No, we don't want a python::ref here, or we'd get an ownership cycle. +}; + +// Define the Python base class +struct Foo::PythonClass : python::class_builder { PythonClass(python::module_builder&); }; + +// No virtual functions on Bar or Baz which are actually supposed to behave +// virtually from C++, so we'll rely on the library to define a wrapper for +// us. Even so, Python class_t types for each type we're wrapping should be +// _defined_ here in a header where they can be seen by other extension class +// definitions, since it is the definition of the python::class_builder<> that +// causes to_python/from_python conversion functions to be generated. +struct BarPythonClass : python::class_builder { BarPythonClass(python::module_builder&); }; +struct BazPythonClass : python::class_builder { BazPythonClass(python::module_builder&); }; + +struct StringMapPythonClass + : python::class_builder +{ + StringMapPythonClass(python::module_builder&); + + // These static functions implement the right argument protocols for + // implementing the Python "special member functions" for mapping on + // StringMap. Could just as easily be global functions. + static const std::string& get_item(const StringMap& m, std::size_t key); + static void set_item(StringMap& m, std::size_t key, const std::string& value); + static void del_item(StringMap& m, std::size_t key); +}; + +struct IntPairPythonClass + : python::class_builder +{ + IntPairPythonClass(python::module_builder&); + + // The following could just as well be a free function; it implements the + // getattr functionality for IntPair. + static int getattr(const IntPair&, const std::string& s); + static void setattr(IntPair&, const std::string& name, int value); + static void delattr(IntPair&, const char* name); +}; + +struct CompareIntPairPythonClass + : python::class_builder +{ + CompareIntPairPythonClass(python::module_builder&); +}; + +} // namespace extclass_demo + +#endif // EXTCLASS_DEMO_DWA052200_H_ diff --git a/extclass_demo.py b/extclass_demo.py new file mode 100644 index 00000000..df882bed --- /dev/null +++ b/extclass_demo.py @@ -0,0 +1,23 @@ +# A stand-in for the real extension module. We're just using this one to +# simulate it while we have all the parts linked together into cpp.dll + +# using "import*-safe" imports +import sys +_sys = sys +del sys + +import os +_os = os +del os + +_savecwd = _os.getcwd() + +if len(_sys.argv) > 1: + _os.chdir(_sys.argv[1]) + +try: + from cpp import * +finally: + if len(_sys.argv) > 1: + _os.chdir(_savecwd) + diff --git a/extending.html b/extending.html new file mode 100644 index 00000000..a3a2da98 --- /dev/null +++ b/extending.html @@ -0,0 +1,73 @@ + + + + A Brief Introduction to writing Python extension modules + +

+ c++boost.gif (8819 bytes) +

+

+ A Brief Introduction to writing Python extension modules +

+

+ Interfacing any language to Python involves building a module which can + be loaded by the Python interpreter, but which isn't written in Python. + This is known as an extension module. Many of the built-in Python + libraries are constructed in 'C' this way; Python even supplies its + fundamental + types using the same mechanism. An extension module can be statically + linked with the Python interpreter, but it more commonly resides in a + shared library or DLL. +

+ As you can see from The Python Extending + and Embedding Tutorial, writing an extension module normally means + worrying about +

+ This last item typically occupies a great deal of code in an extension + module. Remember that Python is a completely dynamic language. A callable + object receives its arguments in a tuple; it is up to that object to + extract those arguments from the tuple, check their types, and raise + appropriate exceptions. There are numerous other tedious details that need + to be managed; too many to mention here. Py_cpp is designed to lift most of + that burden.
+
+ +

+ Another obstacle that most people run into eventually when extending + Python is that there's no way to make a true Python class in an extension + module. The typical solution is to create a new Python type in the + extension module, and then write an additional module in 100% Python. The + Python module defines a Python class which dispatches to an instance of + the extension type, which it contains. This allows users to write + subclasses of the class in the Python module, almost as though they were + sublcassing the extension type. Aside from being tedious, it's not really + the same as having a true class, because there's no way for the user to + override a method of the extension type which is called from the + extension module. Py_cpp solves this problem by taking advantage of Python's metaclass + feature to provide objects which look, walk, and hiss almost exactly + like regular Python classes. Py_cpp classes are actually cleaner than + Python classes in some subtle ways; a more detailed discussion will + follow (someday).

+

Next: Comparisons with Other Systems 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.

+ diff --git a/functions.cpp b/functions.cpp new file mode 100644 index 00000000..0cd24306 --- /dev/null +++ b/functions.cpp @@ -0,0 +1,167 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "functions.h" +#include "newtypes.h" +#include "singleton.h" +#include "objects.h" +#include "errors.h" + +namespace python { namespace detail { + +struct function::type_object : + singleton > > +{ + type_object() : singleton_base(&PyType_Type) {} +}; + + +void function::add_to_namespace(reference new_function, const char* name, PyObject* dict) +{ + dictionary d(ref(dict, ref::increment_count)); + string key(name); + + ref existing_object = d.get_item(key.reference()); + if (existing_object.get() == 0) + { + d[key] = ref(new_function.get(), ref::increment_count); + } + else + { + if (existing_object->ob_type == type_object::instance()) + { + function* f = static_cast(existing_object.get()); + while (f->m_overloads.get() != 0) + f = f->m_overloads.get(); + f->m_overloads = new_function; + } + else + { + PyErr_SetObject(PyExc_RuntimeError, + (string("Attempt to overload ") + name + + " failed. The existing attribute has type " + + existing_object->ob_type->tp_name).get()); + throw error_already_set(); + } + } +} + +function::function() + : python_object(type_object::instance()) +{ +} + +PyObject* function::call(PyObject* args, PyObject* keywords) const +{ + for (const function* f = this; f != 0; f = f->m_overloads.get()) + { + PyErr_Clear(); + try + { + PyObject* const result = f->do_call(args, keywords); + if (result != 0) + return result; + } + catch(const argument_error&) + { + } + } + + if (m_overloads.get() == 0) + return 0; + + PyErr_Clear(); + string message("No overloaded functions match ("); + tuple arguments(ref(args, ref::increment_count)); + for (std::size_t i = 0; i < arguments.size(); ++i) + { + if (i != 0) + message += ", "; + message += arguments[i]->ob_type->tp_name; + } + + message += "). Candidates are:\n"; + for (const function* f1 = this; f1 != 0; f1 = f1->m_overloads.get()) + { + if (f1 != this) + message += "\n"; + message += f1->description(); + } + + PyErr_SetObject(PyExc_TypeError, message.get()); + return 0; +} + +bound_function* bound_function::create(const ref& target, const ref& fn) +{ + bound_function* const result = free_list; + if (result == 0) + return new bound_function(target, fn); + + free_list = result->m_free_list_link; + result->m_target = target; + result->m_unbound_function = fn; + Py_INCREF(result); + return result; +} + +// The instance class whose obj represents the type of bound_function +// objects in Python. bound_functions must be GetAttrable so the __doc__ +// attribute of built-in Python functions can be accessed when bound. +struct bound_function::type_object : + singleton > > > +{ + type_object() : singleton_base(&PyType_Type) {} + +private: // type_object hook override + void dealloc(bound_function*) const; +}; + +bound_function::bound_function(const ref& target, const ref& fn) + : python_object(type_object::instance()), + m_target(target), + m_unbound_function(fn), + m_free_list_link(0) +{ +} + +PyObject* +bound_function::call(PyObject* args, PyObject* keywords) const +{ + // Build a new tuple which prepends the target to the arguments + tuple tail_arguments(ref(args, ref::increment_count)); + ref all_arguments(PyTuple_New(tail_arguments.size() + 1)); + + PyTuple_SET_ITEM(all_arguments.get(), 0, m_target.get()); + Py_INCREF(m_target.get()); + for (std::size_t i = 0; i < tail_arguments.size(); ++i) + { + PyTuple_SET_ITEM(all_arguments.get(), i + 1, tail_arguments[i].get()); + Py_INCREF(tail_arguments[i].get()); + } + + return PyEval_CallObjectWithKeywords(m_unbound_function.get(), all_arguments.get(), keywords); +} + +PyObject* bound_function::getattr(const char* name) const +{ + return PyObject_GetAttrString(m_unbound_function.get(), const_cast(name)); +} + +void bound_function::type_object::dealloc(bound_function* obj) const +{ + obj->m_free_list_link = free_list; + free_list = obj; + obj->m_target.reset(); + obj->m_unbound_function.reset(); +} + +bound_function* bound_function::free_list; + +}} // namespace python::detail diff --git a/functions.h b/functions.h new file mode 100644 index 00000000..ec982f53 --- /dev/null +++ b/functions.h @@ -0,0 +1,306 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef FUNCTIONS_DWA051400_H_ +# define FUNCTIONS_DWA051400_H_ + +# include "pyconfig.h" +# include "wrap_python.h" +# include "pyptr.h" +# include "signatures.h" +# include "caller.h" +# include +# include "objects.h" +# include "base_object.h" +# include +# include + +namespace python { namespace detail { + +// forward declaration +class extension_instance; + + +// function -- +// the common base class for all overloadable function and method objects +// supplied by the library. +class function : public python_object +{ + public: + function(); + // function objects are reasonably rare, so we guess we can afford a virtual table. + // This cuts down on the number of distinct type objects which need to be defined. + virtual ~function() {} + + PyObject* call(PyObject* args, PyObject* keywords) const; + static void add_to_namespace(reference f, const char* name, PyObject* dict); + + private: + virtual PyObject* do_call(PyObject* args, PyObject* keywords) const = 0; + virtual const char* description() const = 0; + private: + struct type_object; + private: + reference m_overloads; +}; + +// wrapped_function_pointer<> -- +// A single function or member function pointer wrapped and presented to +// Python as a callable object. +// +// Template parameters: +// R - the return type of the function pointer +// F - the complete type of the wrapped function pointer +template +struct wrapped_function_pointer : function +{ + typedef F ptr_fun; // pointer-to--function or pointer-to-member-function + + wrapped_function_pointer(ptr_fun pf) + : m_pf(pf) {} + + private: + PyObject* do_call(PyObject* args, PyObject* keywords) const + { return caller::call(m_pf, args, keywords); } + + const char* description() const + { return typeid(F).name(); } + + private: + const ptr_fun m_pf; +}; + +// raw_arguments_function +// A function that passes the Python argument tuple and keyword dictionary +// verbatim to C++ (useful for customized argument parsing and variable +// argument lists) +template +struct raw_arguments_function : function +{ + typedef Ret (*ptr_fun)(Args, Keywords); + + raw_arguments_function(ptr_fun pf) + : m_pf(pf) {} + + private: + PyObject* do_call(PyObject* args, PyObject* keywords) const + { + ref dict(keywords ? + ref(keywords, ref::increment_count) : + ref(PyDict_New())); + + return to_python( + (*m_pf)(from_python(args, python::type()), + from_python(dict.get(), python::type()))); + } + + const char* description() const + { return typeid(ptr_fun).name(); } + + private: + const ptr_fun m_pf; +}; + +// virtual_function<> -- +// A virtual function with a default implementation wrapped and presented +// to Python as a callable object. +// +// Template parameters: +// T - the type of the target class +// R - the return type of the function pointer +// V - the virtual function pointer being wrapped +// (should be of the form R(T::*)(), or R (*)(T, )) +// D - a function which takes a T&, const T&, T*, or const T* first +// parameter and calls T::f on it /non-virtually/, where V +// approximates &T::f. +template +class virtual_function : public function +{ + public: + virtual_function(V virtual_function_ptr, D default_implementation) + : m_virtual_function_ptr(virtual_function_ptr), + m_default_implementation(default_implementation) + {} + + private: + PyObject* do_call(PyObject* args, PyObject* keywords) const; + + const char* description() const + { return typeid(V).name(); } + + private: + const V m_virtual_function_ptr; + const D m_default_implementation; +}; + +// A helper function for new_member_function(), below. Implements the core +// functionality once the return type has already been deduced. R is expected to +// be type, where X is the actual return type of pmf. +template +function* new_wrapped_function_aux(R, F pmf) +{ + // We can't just use "typename R::Type" below because MSVC (incorrectly) pukes. + typedef typename R::type return_type; + return new wrapped_function_pointer(pmf); +} + +// Create and return a new member function object wrapping the given +// pointer-to-member function +template +inline function* new_wrapped_function(F pmf) +{ + // Deduce the return type and pass it off to the helper function above + return new_wrapped_function_aux(return_value(pmf), pmf); +} + +template +function* new_raw_arguments_function(R (*pmf)(Args, keywords)) +{ + return new raw_arguments_function(pmf); +} + + +// A helper function for new_virtual_function(), below. Implements the core +// functionality once the return type has already been deduced. R is expected to +// be type, where X is the actual return type of V. +template +inline function* new_virtual_function_aux( + type, R, V virtual_function_ptr, D default_implementation + ) +{ + // We can't just use "typename R::Type" below because MSVC (incorrectly) pukes. + typedef typename R::type return_type; + return new virtual_function( + virtual_function_ptr, default_implementation); +} + +// Create and return a new virtual_function object wrapping the given +// virtual_function_ptr and default_implementation +template +inline function* new_virtual_function( + type, V virtual_function_ptr, D default_implementation + ) +{ + // Deduce the return type and pass it off to the helper function above + return new_virtual_function_aux( + type(), return_value(virtual_function_ptr), + virtual_function_ptr, default_implementation); +} + +// A function with a bundled "bound target" object. This is what is produced by +// the expression a.b where a is an instance or extension_instance object and b +// is a callable object not found in the obj namespace but on its class or +// a base class. +class bound_function : public python_object +{ + public: + static bound_function* create(const ref& target, const ref& fn); + + bound_function(const ref& target, const ref& fn); + PyObject* call(PyObject*args, PyObject* keywords) const; + PyObject* getattr(const char* name) const; + + private: + struct type_object; + friend struct type_object; + + ref m_target; + ref m_unbound_function; + + private: // data members for allocation/deallocation optimization + bound_function* m_free_list_link; + + static bound_function* free_list; +}; + +// Special functions designed to access data members of a wrapped C++ object. +template +class getter_function : public function +{ + public: + typedef MemberType ClassType::* pointer_to_member; + + getter_function(pointer_to_member pm) + : m_pm(pm) {} + + private: + PyObject* do_call(PyObject* args, PyObject* keywords) const; + + const char* description() const + { return typeid(MemberType (*)(const ClassType&)).name(); } + private: + pointer_to_member m_pm; +}; + +template +class setter_function : public function +{ + public: + typedef MemberType ClassType::* pointer_to_member; + + setter_function(pointer_to_member pm) + : m_pm(pm) {} + + private: + PyObject* do_call(PyObject* args, PyObject* keywords) const; + + const char* description() const + { return typeid(void (*)(const ClassType&, const MemberType&)).name(); } + private: + pointer_to_member m_pm; +}; + +template +PyObject* getter_function::do_call( + PyObject* args, PyObject* /* keywords */) const +{ + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + + return to_python( + from_python(self, type())->*m_pm); +} + +template +PyObject* setter_function::do_call( + PyObject* args, PyObject* /* keywords */) const +{ + PyObject* self; + PyObject* value; + if (!PyArg_ParseTuple(args, const_cast("OO"), &self, &value)) + return 0; + + typedef typename boost::call_traits::const_reference extract_type; + from_python(self, type())->*m_pm + = from_python(value, type()); + + return none(); +} + +template +PyObject* virtual_function::do_call(PyObject* args, PyObject* keywords) const +{ + // If the target object is held by pointer, we must call through the virtual + // function pointer to the most-derived override. + PyObject* target = PyTuple_GetItem(args, 0); + if (target != 0) + { + extension_instance* self = get_extension_instance(target); + if (self->wrapped_objects().size() == 1 + && !self->wrapped_objects()[0]->held_by_value()) + { + return caller::call(m_virtual_function_ptr, args, keywords); + } + } + return caller::call(m_default_implementation, args, keywords); +} + +}} // namespace python::detail + +#endif // FUNCTIONS_DWA051400_H_ diff --git a/gcc.mak b/gcc.mak new file mode 100644 index 00000000..cec872cf --- /dev/null +++ b/gcc.mak @@ -0,0 +1,48 @@ +LIBSRC = \ + extclass.cpp \ + init_function.cpp \ + py.cpp \ + module.cpp \ + subclass.cpp \ + functions.cpp \ + newtypes.cpp \ + objects.cpp + +LIBOBJ = $(LIBSRC:.cpp=.o) +OBJ = $(LIBOBJ) extclass_demo.o + + +ifeq "$(OS)" "Windows_NT" +PYTHON_LIB=c:/tools/python/libs/python15.lib +INC = -Ic:/cygnus/usr/include/g++-3 -Ic:/cygnus/usr/include -Ic:/boost -Ic:/tools/python/include +MODULE_EXTENSION=dll +else +INC = -I/home/koethe/include -I/home/koethe/C++/boost -I/home/koethe/python/include/python1.5 +MODULE_EXTENSION=so +endif + +%.o: %.cpp + g++ -fPIC $(INC) -c $*.cpp + +%.d: %.cpp + @echo creating $@ + @set -e; g++ -M $(INC) -c $*.cpp \ + | sed 's/\($*\)\.o[ :]*/\1.o $@ : /g' > $@; \ + [ -s $@ ] || rm -f $@ + +demo: extclass_demo.o libpycpp.a + g++ -shared -o demomodule.$(MODULE_EXTENSION) $(PYTHON_LIB) extclass_demo.o -L. -lpycpp + python test_extclass.py + +clean: + rm -rf *.o *.$(MODULE_EXTENSION) *.a *.d *.pyc *.bak a.out + +libpycpp.a: $(LIBOBJ) + rm -f libpycpp.a + ar cq libpycpp.a $(LIBOBJ) + +DEP = $(OBJ:.o=.d) + +ifneq "$(MAKECMDGOALS)" "clean" +include $(DEP) +endif diff --git a/gen_all.py b/gen_all.py new file mode 100644 index 00000000..fd8d78cc --- /dev/null +++ b/gen_all.py @@ -0,0 +1,26 @@ +from gen_callback import * +from gen_caller import * +from gen_init_function import * +from gen_signatures import * +from gen_singleton import * +from gen_extclass import * + +def gen_all(args): + open('callback.h', 'w').write(gen_callback(args)) + open('caller.h', 'w').write(gen_caller(args)) + open('init_function.h', 'w').write(gen_init_function(args)) + open('signatures.h', 'w').write(gen_signatures(args)) + open('instance.h', 'w').write(gen_singleton(args)) + open('extclass.h', 'w').write(gen_extclass(args)) + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 10 + else: + args = int(sys.argv[1]) + + print gen_all(args) + + diff --git a/gen_callback.py b/gen_callback.py new file mode 100644 index 00000000..c907e612 --- /dev/null +++ b/gen_callback.py @@ -0,0 +1,124 @@ +from gen_function import * +import string + +def gen_callback(args): + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file was generated for %d-argument python callbacks by gen_callback.python + +#ifndef CALLBACK_DWA_052100_H_ +# define CALLBACK_DWA_052100_H_ + +# include "pyconfig.h" +# include "py.h" + +namespace python { + +namespace detail { + template + inline void callback_adjust_refcount(PyObject*, type) {} + + inline void callback_adjust_refcount(PyObject* p, type) + { Py_INCREF(p); } +} + +// Calling Python from C++ +template +struct callback +{""" % args + + + gen_functions(''' +%{ template <%(class A%n%:, %)> +%} static R call_method(PyObject* self, const char* name%(, const A%n& a%n%)) + {%( + ref p%n(to_python(a%n));%) + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(%(O%))")%(, + p%n.get()%))); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } + +%{ template <%(class A%n%:, %)> +%} static R call(PyObject* self%(, const A%n& a%n%)) + {%( + ref p%n(to_python(a%n));%) + ref result(PyEval_CallFunction(self, const_cast("(%(O%))")%(, + p%n.get()%))); + detail::callback_adjust_refcount(result.get(), type()); + return from_python(result.get(), type()); + } +''', args) + + +"""}; + +// This specialization wouldn't be needed, but MSVC6 doesn't correctly allow the following: +// void g(); +// void f() { return g(); } +template <> +struct callback +{ +""" + + gen_functions(''' +%{ template <%(class A%n%:, %)> +%} static void call_method(PyObject* self, const char* name%(, const A%n& a%n%)) + {%( + ref p%n(to_python(a%n));%) + ref result(PyEval_CallMethod(self, const_cast(name), + const_cast("(%(O%))")%(, + p%n.get()%))); + } + +%{ template <%(class A%n%:, %)> +%} static void call(PyObject* self%(, const A%n& a%n%)) + {%( + ref p%n(to_python(a%n));%) + ref result(PyEval_CallFunction(self, const_cast("(%(O%))")%(, + p%n.get()%))); + } +''', args) + + +"""}; + +// Make it a compile-time error to try to return a const char* from a virtual +// function. The standard conversion +// +// from_python(PyObject* string, python::type) +// +// returns a pointer to the character array which is internal to string. The +// problem with trying to do this in a standard callback function is that the +// Python string would likely be destroyed upon return from the calling function +// (python::callback::call[_method]) when its reference count is +// decremented. If you absolutely need to do this and you're sure it's safe (it +// usually isn't), you can use +// +// python::string result(python::callback::call[_method](...args...)); +// ...result.c_str()... // access the char* array +template <> +struct callback +{ + // Try hard to generate a readable error message + typedef struct unsafe_since_python_string_may_be_destroyed {} call, call_method; +}; + +} // namespace python + +#endif // CALLBACK_DWA_052100_H_ +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_callback(args) diff --git a/gen_caller.py b/gen_caller.py new file mode 100644 index 00000000..f0e65d49 --- /dev/null +++ b/gen_caller.py @@ -0,0 +1,138 @@ +# (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +# distribute this software is granted provided this copyright notice appears +# in all copies. This software is provided "as is" without express or implied +# warranty, and with no claim as to its suitability for any purpose. +# +# The author gratefully acknowleges the support of Dragon Systems, Inc., in +# producing this work. + +from gen_function import * +import string + +header = '''// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file generated for %d-argument member functions and %d-argument free +// functions by gen_caller.python +''' + +body_sections = ( +''' +#ifndef CALLER_DWA05090_H_ +# define CALLER_DWA05090_H_ + +# include "pyconfig.h" +# include "wrap_python.h" +# include +# include "signatures.h" +# include "none.h" + +namespace python { + +// Calling C++ from Python +template +struct caller +{ +''', +''' +''', +''' // Free functions +''', +'''}; + +template <> +struct caller +{ +''', +''' +''', +''' + // Free functions +''', +'''}; + +} + +#endif +''') + +#' + +member_function = ''' template + static PyObject* call(%1 (T::*pmf)(%(A%n%:, %))%2, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; +%( PyObject* a%n; +%) if (!PyArg_ParseTuple(args, const_cast("O%(O%)"), &self%(, &a%n%))) + return 0; + T& target = from_python(self, type()); + %3(target.*pmf)(%(from_python(a%n, type())%:, + %))%4 + } + +''' + +free_function = '''%{ template <%(class A%n%:, %)> +%} static PyObject* call(%1 (*f)(%(A%n%:, %)), PyObject* args, PyObject* /* keywords */ ) { +%( PyObject* a%n; +%) if (!PyArg_ParseTuple(args, const_cast("%(O%)")%(, &a%n%))) + return 0; + %2f(%(from_python(a%n, type())%:, + %))%3 + } + +''' + +def gen_caller(member_function_args, free_function_args = None): + if free_function_args is None: + free_function_args = member_function_args + 1 + + return_none = '''; + return detail::none();''' + + return (header % (member_function_args, free_function_args) + + body_sections[0] + + gen_functions(member_function, member_function_args, + 'R', '', 'return to_python(', ');') + + body_sections[1] + + gen_functions(member_function, member_function_args, + 'R', ' const', 'return to_python(', ');') + + body_sections[2] + + + gen_functions(free_function, free_function_args, + 'R', 'return to_python(', ');') + + body_sections[3] + + # specialized part for void return values begins here + + gen_functions(member_function, member_function_args, + 'void', '', '', return_none) + + body_sections[4] + + gen_functions(member_function, member_function_args, + 'void', ' const', '', return_none) + + body_sections[5] + + + gen_functions(free_function, free_function_args, + 'void', '', return_none) + + body_sections[6] + ) + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + member_function_args = 5 + free_function_args = 6 + else: + member_function_args = int(sys.argv[1]) + if len(sys.argv) > 2: + free_function_args = int(sys.argv[2]) + else: + free_function_args = member_function_args + + print gen_caller(member_function_args, free_function_args) + + diff --git a/gen_extclass.py b/gen_extclass.py new file mode 100644 index 00000000..81ea4942 --- /dev/null +++ b/gen_extclass.py @@ -0,0 +1,830 @@ +from gen_function import * +import string + +def gen_extclass(args): + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file automatically generated for %d-argument constructors by +// gen_extclass.python + +#ifndef EXTENSION_CLASS_DWA052000_H_ +# define EXTENSION_CLASS_DWA052000_H_ + +# include "pyconfig.h" +# include "subclass.h" +# include +# include "none.h" +# include "objects.h" +# include "functions.h" +# include +# include "init_function.h" +# include +# include + +namespace python { + +// forward declarations +template struct operators; +template struct left_operand; +template struct right_operand; + +enum without_downcast_t { without_downcast }; + +namespace detail { + +// forward declarations +class extension_instance; +class extension_class_base; +template class instance_holder; +template class instance_value_holder; +template class instance_ptr_holder; +template struct operand_select; + template struct choose_op; + template struct choose_rop; + template struct choose_unary_op; + template struct define_operator; + +meta_class* extension_meta_class(); +extension_instance* get_extension_instance(PyObject* p); +void report_missing_instance_data(extension_instance*, class_t*, const std::type_info&); +void report_missing_ptr_data(extension_instance*, class_t*, const std::type_info&); +void report_missing_class_object(const std::type_info&); +void report_released_smart_pointer(const std::type_info&); + +template +T* check_non_null(T* p) +{ + if (p == 0) + report_released_smart_pointer(typeid(T)); + return p; +} + +template class held_instance; + +typedef void* (*conversion_function_ptr)(void*); + +struct base_class_info +{ + base_class_info(extension_class_base* t, conversion_function_ptr f) + :class_object(t), convert(f) + {} + + extension_class_base* class_object; + conversion_function_ptr convert; +}; + +typedef base_class_info derived_class_info; + +struct add_operator_base; + +class extension_class_base : public class_t +{ + public: + extension_class_base(const char* name); + + public: + // the purpose of try_class_conversions() and its related functions + // is explained in extclass.cpp + void* try_class_conversions(instance_holder_base*) const; + void* try_base_class_conversions(instance_holder_base*) const; + void* try_derived_class_conversions(instance_holder_base*) const; + + void set_attribute(const char* name, PyObject* x); + void set_attribute(const char* name, ref x); + + private: + virtual void* extract_object_from_holder(instance_holder_base* v) const = 0; + virtual std::vector const& base_classes() const = 0; + virtual std::vector const& derived_classes() const = 0; + + protected: + friend struct add_operator_base; + void add_method(reference method, const char* name); + void add_method(function* method, const char* name); + + void add_constructor_object(function*); + void add_setter_method(function*, const char* name); + void add_getter_method(function*, const char* name); +}; + +template +class class_registry +{ + public: + static extension_class_base* class_object() + { return static_class_object; } + + // Register/unregister the Python class object corresponding to T + static void register_class(extension_class_base*); + static void unregister_class(extension_class_base*); + + // Establish C++ inheritance relationships + static void register_base_class(base_class_info const&); + static void register_derived_class(derived_class_info const&); + + // Query the C++ inheritance relationships + static std::vector const& base_classes(); + static std::vector const& derived_classes(); + private: + static extension_class_base* static_class_object; + static std::vector static_base_class_info; + static std::vector static_derived_class_info; +}; + +}} // namespace python::detail + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE + +// This class' only job is to define from_python and to_python converters for T +// and U. T is the class the user really intends to wrap. U is a class derived +// from T with some virtual function overriding boilerplate, or if there are no +// virtual functions, U = held_instance. +template > +class python_extension_class_converters +{ + public: + // Get an object which can be used to convert T to/from python. This is used + // as a kind of concept check by the global template + // + // PyObject* to_python(const T& x) + // + // below this class, to prevent the confusing messages that would otherwise + // pop up. Now, if T hasn't been wrapped as an extension class, the user + // will see an error message about the lack of an eligible + // py_extension_class_converters() function. + friend python_extension_class_converters py_extension_class_converters(python::type) + { + return python_extension_class_converters(); + } + + // This is a member function because in a conforming implementation, friend + // funcitons defined inline in the class body are all instantiated as soon + // as the enclosing class is instantiated. If T is not copyable, that causes + // a compiler error. Instead, we access this function through the global + // template + // + // PyObject* to_python(const T& x) + // + // defined below this class. Since template functions are instantiated only + // on demand, errors will be avoided unless T is noncopyable and the user + // writes code which causes us to try to copy a T. + PyObject* to_python(const T& x) const + { + python::reference result(create_instance()); + result->add_implementation( + std::auto_ptr( + new python::detail::instance_value_holder(result.get(), x))); + return result.release(); + } + + // Convert to T* + friend T* from_python(PyObject* obj, python::type) + { + // downcast to an extension_instance, then find the actual T + python::detail::extension_instance* self = python::detail::get_extension_instance(obj); + typedef std::vector::const_iterator iterator; + for (iterator p = self->wrapped_objects().begin(); + p != self->wrapped_objects().end(); ++p) + { + python::detail::instance_holder* held = dynamic_cast*>(*p); + if (held != 0) + return held->target(); + + // see extclass.cpp for an explanation of try_class_conversions() + void* target = python::detail::class_registry::class_object()->try_class_conversions(*p); + if(target) + return static_cast(target); + } + python::detail::report_missing_instance_data(self, python::detail::class_registry::class_object(), typeid(T)); + throw python::argument_error(); + } + + // Convert to PtrType, where PtrType can be dereferenced to obtain a T. + template + static PtrType& ptr_from_python(PyObject* obj, python::type) + { + // downcast to an extension_instance, then find the actual T + python::detail::extension_instance* self = python::detail::get_extension_instance(obj); + typedef std::vector::const_iterator iterator; + for (iterator p = self->wrapped_objects().begin(); + p != self->wrapped_objects().end(); ++p) + { + python::detail::instance_ptr_holder* held = + dynamic_cast*>(*p); + if (held != 0) + return held->ptr(); + } + python::detail::report_missing_ptr_data(self, python::detail::class_registry::class_object(), typeid(T)); + throw python::argument_error(); + } + + template + static PyObject* ptr_to_python(PtrType x) + { + python::reference result(create_instance()); + result->add_implementation( + std::auto_ptr( + new python::detail::instance_ptr_holder(x))); + return result.release(); + } + + static python::reference create_instance() + { + PyTypeObject* class_object = python::detail::class_registry::class_object(); + if (class_object == 0) + python::detail::report_missing_class_object(typeid(T)); + + return python::reference( + new python::detail::extension_instance(class_object)); + } + + // Convert to const T* + friend const T* from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + // Convert to const T* const& + friend const T* from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + // Convert to T* const& + friend T* from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + // Convert to T& + friend T& from_python(PyObject* p, python::type) + { return *python::detail::check_non_null(from_python(p, python::type())); } + + // Convert to const T& + friend const T& from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + // Convert to T + friend const T& from_python(PyObject* p, python::type) + { return from_python(p, python::type()); } + + friend std::auto_ptr& from_python(PyObject* p, python::type&>) + { return ptr_from_python(p, python::type >()); } + + friend std::auto_ptr& from_python(PyObject* p, python::type >) + { return ptr_from_python(p, python::type >()); } + + friend const std::auto_ptr& from_python(PyObject* p, python::type&>) + { return ptr_from_python(p, python::type >()); } + + friend PyObject* to_python(std::auto_ptr x) + { return ptr_to_python(x); } + + friend boost::shared_ptr& from_python(PyObject* p, python::type&>) + { return ptr_from_python(p, python::type >()); } + + friend boost::shared_ptr& from_python(PyObject* p, python::type >) + { return ptr_from_python(p, python::type >()); } + + friend const boost::shared_ptr& from_python(PyObject* p, python::type&>) + { return ptr_from_python(p, python::type >()); } + + friend PyObject* to_python(boost::shared_ptr x) + { return ptr_to_python(x); } +}; + +// Convert T to_python, instantiated on demand and only if there isn't a +// non-template overload for this function. This version is the one invoked when +// T is a wrapped class. See the first 2 functions declared in +// python_extension_class_converters above for more info. +template +PyObject* to_python(const T& x) +{ + return py_extension_class_converters(python::type()).to_python(x); +} + +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +namespace python { + +BOOST_PYTHON_IMPORT_CONVERSION(python_extension_class_converters); + +namespace detail { + +template class instance_holder; + +class read_only_setattr_function : public function +{ + public: + read_only_setattr_function(const char* name); + PyObject* do_call(PyObject* args, PyObject* keywords) const; + const char* description() const; + private: + string m_name; +}; + + template + struct define_conversion + { + static void* upcast_ptr(void* v) + { + return static_cast(static_cast(v)); + } + + static void* downcast_ptr(void* v) + { + return dynamic_cast(static_cast(v)); + } + }; + +// An easy way to make an extension base class which wraps T. Note that Python +// subclasses of this class will simply be class_t objects. +// +// U should be a class derived from T which overrides virtual functions with +// boilerplate code to call back into Python. See extclass_demo.h for examples. +// +// U is optional, but you won't be able to override any member functions in +// Python which are called from C++ if you don't supply it. If you just want to +// be able to use T in python without overriding member functions, you can omit +// U. +template > +class extension_class + : public python_extension_class_converters, // This generates the to_python/from_python functions + public extension_class_base +{ + public: + typedef T wrapped_type; + typedef U callback_type; + + // Construct with a name that comes from typeid(T).name(). The name only + // affects the objects of this class are represented through repr() + extension_class(); + + // Construct with the given name. The name only affects the objects of this + // class are represented through repr() + extension_class(const char* name); + + ~extension_class(); + + // define constructors +""" % args + + gen_function( +""" template <%(class A%n%:, %)> + inline void def(constructor<%(A%n%:, %)>) + // The following incantation builds a signature1, signature2,... object. It + // should _all_ get optimized away. + { add_constructor( + %(prepend(type::id(), + %) signature0()%()%)); + } +""", args) + + +""" + + // export homogeneous operators (type of both lhs and rhs is 'operator') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub), Foo>()); + + // export homogeneous operators (type of both lhs and rhs is 'T const&') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub)>()); + template + inline void def(operators) + { + typedef typename operand_select::template wrapped::type true_operand; + def_operators(operators()); + } + + // export heterogeneous operators (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub), Foo>(), + // python::right_operand()); + + // export heterogeneous operators (type of lhs: 'T const&', of rhs: 'right') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub)>(), + // python::right_operand()); + template + inline void def(operators, right_operand r) + { + typedef typename operand_select::template wrapped::type true_left; + def_operators(operators(), r); + } + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'right') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub), Foo>(), + // python::left_operand()); + + // export heterogeneous reverse-argument operators + // (type of lhs: 'left', of rhs: 'T const&') + // usage: foo_class.def(python::operators<(python::op_add | python::op_sub)>(), + // python::left_operand()); + template + inline void def(operators, left_operand l) + { + typedef typename operand_select::template wrapped::type true_right; + def_operators(operators(), l); + } + + // define a function that passes Python arguments and keywords + // to C++ verbatim (as a 'tuple const&' and 'dictionary const&' + // respectively). This is useful for manual argument passing. + // It's also the only possibility to pass keyword arguments to C++. + // Fn must have a signatur that is compatible to + // PyObject* (*)(PyObject* aTuple, PyObject* aDictionary) + template + inline void def_raw(Fn fn, const char* name) + { + this->add_method(new_raw_arguments_function(fn), name); + } + + // define member functions. In fact this works for free functions, too - + // they act like static member functions, or if they start with the + // appropriate self argument (as a pointer), they can be used just like + // ordinary member functions -- just like Python! + template + inline void def(Fn fn, const char* name) + { + this->add_method(new_wrapped_function(fn), name); + } + + // Define a virtual member function with a default implementation. + // default_fn should be a function which provides the default implementation. + // Be careful that default_fn does not in fact call fn virtually! + template + inline void def(Fn fn, const char* name, DefaultFn default_fn) + { + this->add_method(new_virtual_function(type(), fn, default_fn), name); + } + + // Provide a function which implements x., reading from the given + // member (pm) of the T obj + template + inline void def_getter(MemberType T::*pm, const char* name) + { + this->add_getter_method(new getter_function(pm), name); + } + + // Provide a function which implements assignment to x., writing to + // the given member (pm) of the T obj + template + inline void def_setter(MemberType T::*pm, const char* name) + { + this->add_setter_method(new setter_function(pm), name); + } + + // Expose the given member (pm) of the T obj as a read-only attribute + template + inline void def_readonly(MemberType T::*pm, const char* name) + { + this->add_setter_method(new read_only_setattr_function(name), name); + this->def_getter(pm, name); + } + + // Expose the given member (pm) of the T obj as a read/write attribute + template + inline void def_read_write(MemberType T::*pm, const char* name) + { + this->def_getter(pm, name); + this->def_setter(pm, name); + } + + // define the standard coercion needed for operator overloading + void def_standard_coerce(); + + // declare the given class a base class of this one and register + // up and down conversion functions + template + void declare_base(extension_class* base) + { + // see extclass.cpp for an explanation of why we need to register + // conversion functions + base_class_info baseInfo(base, + &define_conversion::downcast_ptr); + class_registry::register_base_class(baseInfo); + add_base(ref(as_object(base), ref::increment_count)); + + derived_class_info derivedInfo(this, + &define_conversion::upcast_ptr); + class_registry::register_derived_class(derivedInfo); + } + + // declare the given class a base class of this one and register + // only up conversion function + template + void declare_base(extension_class* base, without_downcast_t) + { + // see extclass.cpp for an explanation of why we need to register + // conversion functions + base_class_info baseInfo(base, 0); + class_registry::register_base_class(baseInfo); + add_base(ref(as_object(base), ref::increment_count)); + + derived_class_info derivedInfo(this, + &define_conversion::upcast_ptr); + class_registry::register_derived_class(derivedInfo); + } + + private: // types + typedef instance_value_holder holder; + + private: // extension_class_base virtual function implementations + std::vector const& base_classes() const; + std::vector const& derived_classes() const; + void* extract_object_from_holder(instance_holder_base* v) const; + + private: // Utility functions + template + inline void def_operators(operators) + { + def_standard_coerce(); + + // for some strange reason, this prevents MSVC from having an + // "unrecoverable block scoping error"! + typedef choose_op<(which & op_add)> choose_add; + + choose_op<(which & op_add)>::template args::add(this); + choose_op<(which & op_sub)>::template args::add(this); + choose_op<(which & op_mul)>::template args::add(this); + choose_op<(which & op_div)>::template args::add(this); + choose_op<(which & op_mod)>::template args::add(this); + choose_op<(which & op_divmod)>::template args::add(this); + choose_op<(which & op_pow)>::template args::add(this); + choose_op<(which & op_lshift)>::template args::add(this); + choose_op<(which & op_rshift)>::template args::add(this); + choose_op<(which & op_and)>::template args::add(this); + choose_op<(which & op_xor)>::template args::add(this); + choose_op<(which & op_or)>::template args::add(this); + choose_unary_op<(which & op_neg)>::template args::add(this); + choose_unary_op<(which & op_pos)>::template args::add(this); + choose_unary_op<(which & op_abs)>::template args::add(this); + choose_unary_op<(which & op_invert)>::template args::add(this); + choose_unary_op<(which & op_int)>::template args::add(this); + choose_unary_op<(which & op_long)>::template args::add(this); + choose_unary_op<(which & op_float)>::template args::add(this); + choose_op<(which & op_cmp)>::template args::add(this); + choose_unary_op<(which & op_str)>::template args::add(this); + } + + template + inline void def_operators(operators, right_operand) + { + def_standard_coerce(); + + choose_op<(which & op_add)>::template args::add(this); + choose_op<(which & op_sub)>::template args::add(this); + choose_op<(which & op_mul)>::template args::add(this); + choose_op<(which & op_div)>::template args::add(this); + choose_op<(which & op_mod)>::template args::add(this); + choose_op<(which & op_divmod)>::template args::add(this); + choose_op<(which & op_pow)>::template args::add(this); + choose_op<(which & op_lshift)>::template args::add(this); + choose_op<(which & op_rshift)>::template args::add(this); + choose_op<(which & op_and)>::template args::add(this); + choose_op<(which & op_xor)>::template args::add(this); + choose_op<(which & op_or)>::template args::add(this); + choose_op<(which & op_cmp)>::template args::add(this); + } + + template + inline void def_operators(operators, left_operand) + { + def_standard_coerce(); + + choose_rop<(which & op_add)>::template args::add(this); + choose_rop<(which & op_sub)>::template args::add(this); + choose_rop<(which & op_mul)>::template args::add(this); + choose_rop<(which & op_div)>::template args::add(this); + choose_rop<(which & op_mod)>::template args::add(this); + choose_rop<(which & op_divmod)>::template args::add(this); + choose_rop<(which & op_pow)>::template args::add(this); + choose_rop<(which & op_lshift)>::template args::add(this); + choose_rop<(which & op_rshift)>::template args::add(this); + choose_rop<(which & op_and)>::template args::add(this); + choose_rop<(which & op_xor)>::template args::add(this); + choose_rop<(which & op_or)>::template args::add(this); + choose_rop<(which & op_cmp)>::template args::add(this); + } + + template + void add_constructor(signature sig) + { + this->add_constructor_object(init_function::create(sig)); + } +}; + +// A simple wrapper over a T which allows us to use extension_class with a +// single template parameter only. See extension_class, above. +template +class held_instance : public T +{ + // There are no member functions: we want to avoid inadvertently overriding + // any virtual functions in T. +public:""" + + gen_functions("""%{ + template <%(class A%n%:, %)>%} + held_instance(PyObject*%(, A%n% a%n%)) : T(%(a%n%:, %)) {}""", args) + + """ +}; + +// Abstract base class for all obj holders. Base for template class +// instance_holder<>, below. +class instance_holder_base +{ +public: + virtual ~instance_holder_base() {} + virtual bool held_by_value() = 0; +}; + +// Abstract base class which holds a Held, somehow. Provides a uniform way to +// get a pointer to the held object +template +class instance_holder : public instance_holder_base +{ +public: + virtual Held*target() = 0; +}; + +// Concrete class which holds a Held by way of a wrapper class Wrapper. If Held +// can be constructed with arguments (A1...An), Wrapper must have a +// corresponding constructor for arguments (PyObject*, A1...An). Wrapper is +// neccessary to implement virtual function callbacks (there must be a +// back-pointer to the actual Python object so that we can call any +// overrides). held_instance (above) is used as a default Wrapper class when +// there are no virtual functions. +template +class instance_value_holder : public instance_holder +{ +public: + Held* target() { return &m_held; } + Wrapper* value_target() { return &m_held; } +""" + + gen_functions("""%{ + template <%(class A%n%:, %)>%} + instance_value_holder(extension_instance* p%(, A%n a%n%)) : + m_held(p%(, a%n%)) {}""", args) + + """ + + public: // implementation of instance_holder_base required interface + bool held_by_value() { return true; } + + private: + Wrapper m_held; +}; + +// Concrete class which holds a HeldType by way of a (possibly smart) pointer +// PtrType. By default, these are only generated for PtrType == +// std::auto_ptr and PtrType == boost::shared_ptr. +template +class instance_ptr_holder : public instance_holder +{ + public: + HeldType* target() { return &*m_ptr; } + PtrType& ptr() { return m_ptr; } + + instance_ptr_holder(PtrType ptr) : m_ptr(ptr) {} + + public: // implementation of instance_holder_base required interface + bool held_by_value() { return false; } + private: + PtrType m_ptr; +}; + +class extension_instance : public instance +{ + public: + extension_instance(PyTypeObject* class_); + ~extension_instance(); + + void add_implementation(std::auto_ptr holder); + + typedef std::vector held_objects; + const held_objects& wrapped_objects() const + { return m_wrapped_objects; } + private: + held_objects m_wrapped_objects; +}; + +// +// Template function implementations +// + +tuple extension_class_coerce(ref l, ref r); + +template +extension_class::extension_class() + : extension_class_base(typeid(T).name()) +{ + class_registry::register_class(this); +} + +template +extension_class::extension_class(const char* name) + : extension_class_base(name) +{ + class_registry::register_class(this); +} + +template +void extension_class::def_standard_coerce() +{ + ref coerce_fct = dict().get_item(string("__coerce__")); + + if(coerce_fct.get() == 0) // not yet defined + this->def(&extension_class_coerce, "__coerce__"); +} + +template +inline +std::vector const& +extension_class::base_classes() const +{ + return class_registry::base_classes(); +} + +template +inline +std::vector const& +extension_class::derived_classes() const +{ + return class_registry::derived_classes(); +} + +template +void* extension_class::extract_object_from_holder(instance_holder_base* v) const +{ + instance_holder* held = dynamic_cast*>(v); + if(held) + return held->target(); + return 0; +} + +template +extension_class::~extension_class() +{ + class_registry::unregister_class(this); +} + +template +inline void class_registry::register_class(extension_class_base* p) +{ + // You're not expected to create more than one of these! + assert(static_class_object == 0); + static_class_object = p; +} + +template +inline void class_registry::unregister_class(extension_class_base* p) +{ + // The user should be destroying the same object they created. + assert(static_class_object == p); + (void)p; // unused in shipping version + static_class_object = 0; +} + +template +void class_registry::register_base_class(base_class_info const& i) +{ + static_base_class_info.push_back(i); +} + +template +void class_registry::register_derived_class(derived_class_info const& i) +{ + static_derived_class_info.push_back(i); +} + +template +std::vector const& class_registry::base_classes() +{ + return static_base_class_info; +} + +template +std::vector const& class_registry::derived_classes() +{ + return static_derived_class_info; +} + +// +// Static data member declaration. +// +template +extension_class_base* class_registry::static_class_object; +template +std::vector class_registry::static_base_class_info; +template +std::vector class_registry::static_derived_class_info; + +}} // namespace python::detail + +#endif // EXTENSION_CLASS_DWA052000_H_ +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_extclass(args) diff --git a/gen_function.py b/gen_function.py new file mode 100644 index 00000000..bebbc818 --- /dev/null +++ b/gen_function.py @@ -0,0 +1,184 @@ +import string + +def _find(s, sub, start=0, end=None): + """Just like string.find, except it returns end or len(s) when not found. + """ + if end == None: + end = len(s) + + pos = string.find(s, sub, start, end) + if pos < 0: + return end + else: + return pos + +def _gen_common_key(key, n, args): + if len(key) > 0 and key in '123456789': + return str(args[int(key) - 1]) + elif key == 'x': + return str(n) + else: + return key + +def _gen_arg(template, n, args, delimiter = '%'): + result = '' + i = 0 + while i < len(template): # until the template is consumed + # consume everything up to the first delimiter + delimiter_pos = _find(template, delimiter, i) + result = result + template[i:delimiter_pos] + + # The start position of whatever comes after the delimiter+key + start = delimiter_pos + 2 + key = template[start - 1 : start] # the key character. If there were no + # delimiters left, key will be empty + + if key == 'n': + result = result + `n` + else: + result = result + _gen_common_key(key, n, args) + + i = start + + return result + +def gen_function(template, n, *args, **keywords): + r"""gen_function(template, n, [args...] ) -> string + + Generate a function declaration based on the given template. + + Sections of the template between '%(', '%)' pairs are repeated n times. If '%:' + appears in the middle, it denotes the beginning of a delimiter. + + Sections of the template between '%{', '%}' pairs are ommitted if n == 0. + + %n is transformed into the string representation of 1..n for each repetition + of n. + + %x, where x is a digit, is transformed into the corresponding additional + argument. + + for example, + + >>> gen_function('%1 abc(%(int a%n%:, %));%{ // all args are ints%}', 2, 'void') + 'void abc(int a1, int a2); // all args are ints' + >>> gen_function('%1 abc(%(int a%n%:, %));%{ // all args are ints%}', 0, 'x') + 'x abc();' + + + >>> template = ''' template + ... static PyObject* call( %1(T::*pmf)(%(A%n%:, %))%2, PyObject* args, PyObject* /* keywords */ ) { + ... PyObject* self; + ... %( PyObject* a%n; + ... %) if (!PyArg_ParseTuple(args, const_cast("O%(O%)"), &self%(, &a%n%))) + ... return 0; + ... T& target = from_python(self, type()); + ... %3to_python((target.*pmf)(%( + ... from_python(a%n, type())%:,%) + ... ));%4 + ... }''' + + >>> print gen_function(template, 0, 'R ', '', 'return ', '') + template + static PyObject* call( R (T::*pmf)(), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + if (!PyArg_ParseTuple(args, const_cast("O"), &self)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)( + )); + } + + >>> print gen_function(template, 2, 'R ', '', 'return ', '') + template + static PyObject* call( R (T::*pmf)(A1, A2), PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &self, &a1, &a2)) + return 0; + T& target = from_python(self, type()); + return to_python((target.*pmf)( + from_python(a1, type()), + from_python(a2, type()) + )); + } + + >>> print gen_function(template, 3, 'void ', ' const', '', '\n'+8*' ' + 'return none();') + template + static PyObject* call( void (T::*pmf)(A1, A2, A3) const, PyObject* args, PyObject* /* keywords */ ) { + PyObject* self; + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &self, &a1, &a2, &a3)) + return 0; + T& target = from_python(self, type()); + to_python((target.*pmf)( + from_python(a1, type()), + from_python(a2, type()), + from_python(a3, type()) + )); + return none(); + } +""" + delimiter = keywords.get('delimiter', '%') + result = '' + i = 0 + while i < len(template): # until the template is consumed + # consume everything up to the first delimiter + delimiter_pos = _find(template, delimiter, i) + result = result + template[i:delimiter_pos] + + # The start position of whatever comes after the delimiter+key + start = delimiter_pos + 2 + key = template[start - 1 : start] # the key character. If there were no + # delimiters left, key will be empty + + pairs = { '(':')', '{':'}' } + + if key in pairs.keys(): + end = string.find(template, delimiter + pairs[key], start) + assert end >= 0, "Matching '" + delimiter + pairs[key] +"' not found!" + delimiter_pos = end + + if key == '{': + if n > 0: + result = result + gen_function(template[start:end], n, args, delimiter) + else: + separator_pos = _find(template, delimiter + ':', start, end) + separator = template[separator_pos+2 : end] + + for x in range(1, n + 1): + result = result + _gen_arg(template[start:separator_pos], x, args, + delimiter) + if x != n: + result = result + separator + + else: + result = result + _gen_common_key(key, n, args) + + i = delimiter_pos + 2 + + return result + +def gen_functions(template, n, *args): + r"""gen_functions(template, n, [args...]) -> string + + Call gen_function repeatedly with from 0..n and the given optional + arguments. + + >>> print gen_functions('%1 abc(%(int a%n%:, %));%{ // all args are ints%}\n', 2, 'void'), + void abc(); + void abc(int a1); // all args are ints + void abc(int a1, int a2); // all args are ints + + """ + result = '' + for x in range(n + 1): + result = result + apply(gen_function, (template, x) + args) + return result + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/gen_init_function.py b/gen_init_function.py new file mode 100644 index 00000000..9fedec2c --- /dev/null +++ b/gen_init_function.py @@ -0,0 +1,166 @@ +from gen_function import * +import string + +def gen_init_function(args): + + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file was generated for %d-argument constructors by gen_init_function.python + +#ifndef INIT_FUNCTION_DWA052000_H_ +# define INIT_FUNCTION_DWA052000_H_ + +# include "pyconfig.h" +# include "functions.h" +# include "signatures.h" +# include + +namespace python { + +namespace detail { + + // parameter_traits - so far, this is a way to pass a const T& when we can be + // sure T is not a reference type, and a raw T otherwise. This should be + // rolled into boost::call_traits. Ordinarily, parameter_traits would be + // written: + // + // template struct parameter_traits + // { + // typedef const T& const_reference; + // }; + // + // template struct parameter_traits + // { + // typedef T& const_reference; + // }; + // + // template <> struct parameter_traits + // { + // typedef void const_reference; + // }; + // + // ...but since we can't partially specialize on reference types, we need this + // long-winded but equivalent incantation. + + // const_ref_selector -- an implementation detail of parameter_traits (below). This uses + // the usual "poor man's partial specialization" hack for MSVC. + template + struct const_ref_selector + { + template + struct const_ref + { + typedef const T& type; + }; + }; + + template <> + struct const_ref_selector + { + template + struct const_ref + { + typedef T type; + }; + }; + +# ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable: 4181) +# endif // BOOST_MSVC + template + struct parameter_traits + { + private: + typedef const_ref_selector::value> selector; + public: + typedef typename selector::template const_ref::type const_reference; + }; +# ifdef BOOST_MSVC +# pragma warning(pop) +# endif // BOOST_MSVC + + // Full spcialization for void + template <> + struct parameter_traits + { + typedef void const_reference; + }; + + template + class reference_parameter + { + typedef typename parameter_traits::const_reference const_reference; + public: + reference_parameter(const_reference value) + : value(value) {} + operator const_reference() { return value; } + private: + const_reference value; + }; + +class extension_instance; +class instance_holder_base; + +class init; +""" + + gen_functions('template struct init%x;\n', args) + + """ +template +struct init_function +{ +""" + gen_functions("""%{ + template <%(class A%n%:, %)> +%} static init* create(signature%x%{<%(A%n%:, %)>%}) { + return new init%x::const_reference%)>; + } +""", args)+"""}; + +class init : public function +{ +private: // override function hook + PyObject* do_call(PyObject* args, PyObject* keywords) const; +private: + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* tail_args, PyObject* keywords) const = 0; +}; +""" + gen_functions(""" + +template +struct init%x : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + %(PyObject* a%n; + %)if (!PyArg_ParseTuple(args, const_cast("%(O%)")%(, &a%n%))) + throw argument_error(); + return new T(self%(, + python::detail::reference_parameter(from_python(a%n, type()))%) + ); + } + const char* description() const + { return typeid(void (*)(T&%(, A%n%%))).name(); } +};""", args) + """ + +}} // namespace python::detail + +#endif // INIT_FUNCTION_DWA052000_H_ +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_init_function(args) + diff --git a/gen_signatures.py b/gen_signatures.py new file mode 100644 index 00000000..612f7a53 --- /dev/null +++ b/gen_signatures.py @@ -0,0 +1,158 @@ +from gen_function import * +import string + +def gen_struct_signatures(args): + result = '' + for n in range(args, -1, -1): + result = ( + result + gen_function("""%{template <%(class T%n%:, %)> +%}struct signature%x {}; + +""", n) +# + ((n == args) and [""] or +# [gen_function(""" +# template +# static inline signature%1 prepend(type) +# { return signature%1(); }""", +# n, (str(n+1),)) +# ] +# )[0] +# +# + ((n != 0) and [""] or +# [""" +# // This one terminates the chain. Prepending void_t to the head of a void_t +# // signature results in a void_t signature again. +# static inline signature0 prepend(void_t) { return signature0(); }"""] +# )[0] +# + """ +#}; +# +#""" + + ((n == args) and [""] or + [gen_function( +"""template <%(class T%n%, %)class X> +inline signature%1 prepend(type, signature%x%{<%(T%n%:, %)>%}) + { return signature%1(); } + +""", n, str(n+1)) + ] + )[0] + ) + return result + +def gen_signatures(args): + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file automatically generated by gen_signatures.python for %d arguments. +#ifndef SIGNATURES_DWA050900_H_ +# define SIGNATURES_DWA050900_H_ + +# include "pyconfig.h" + +namespace python { + +namespace detail { +// A stand-in for the built-in void. This one can be passed to functions and +// (under MSVC, which has a bug, be used as a default template type parameter). +struct void_t {}; +} + +// An envelope in which type information can be delivered for the purposes +// of selecting an overloaded from_python() function. This is needed to work +// around MSVC's lack of partial specialiation/ordering. Where normally we'd +// want to form a function call like void f(), We instead pass +// type as one of the function parameters to select a particular +// overload. +// +// The id typedef helps us deal with the lack of partial ordering by generating +// unique types for constructor signatures. In general, type::id is type, +// but type::id is just void_t. +template +struct type +{ + typedef type id; +}; + +template <> +struct type +{ + typedef python::detail::void_t id; +}; + +namespace detail { +// These basically encapsulate a chain of types, , used to make the syntax of +// add(constructor()) work. We need to produce a unique type for each number +// of non-default parameters to constructor<>. Q: why not use a recursive +// formulation for infinite extensibility? A: MSVC6 seems to choke on constructs +// that involve recursive template nesting. +// +// signature chaining +""" % args + + gen_struct_signatures(args) + + """ +// This one terminates the chain. Prepending void_t to the head of a void_t +// signature results in a void_t signature again. +inline signature0 prepend(void_t, signature0) { return signature0(); } + +} // namespace detail +""" + + gen_function(""" +template <%(class A%n% = detail::void_t%:, %)> +struct constructor +{ +}; +""", args) + + """ +namespace detail { +// Return value extraction: + +// This is just another little envelope for carrying a typedef (see type, +// above). I could have re-used type, but that has a very specific purpose. I +// thought this would be clearer. +template +struct return_value_select { typedef T type; }; + +// free functions""" + + gen_functions(""" +template +return_value_select return_value(R (*)(%(A%n%:, %))) { return return_value_select(); } +""", args) + + + +""" +// TODO(?): handle 'const void' + +// member functions""" + + gen_functions(""" +template +return_value_select return_value(R (T::*)(%(A%n%:, %))) { return return_value_select(); } +""", args) + + + gen_functions(""" +template +return_value_select return_value(R (T::*)(%(A%n%:, %)) const) { return return_value_select(); } +""", args) + + + """ +}} // namespace python::detail + +#endif +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_signatures(args) + diff --git a/gen_singleton.py b/gen_singleton.py new file mode 100644 index 00000000..203d6444 --- /dev/null +++ b/gen_singleton.py @@ -0,0 +1,58 @@ +from gen_function import * +import string + +def gen_singleton(args): + return ( +"""// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef SINGLETON_DWA051900_H_ +# define SINGLETON_DWA051900_H_ + +# include "pyconfig.h" + +namespace python { namespace detail { + +struct empty {}; +template +struct singleton : Base +{ + typedef singleton singleton_base; // Convenience type for derived class constructors + + static Derived* instance(); + + // Pass-through constructors +""" + + gen_functions("""%{ + template <%(class A%n%:, %)> +%} singleton(%(const A%n& a%n%:, %)) : Base(%(a%n%:, %)) {} +""", args) + + """ +}; + +template +Derived* singleton::instance() +{ + static Derived x; + return &x; +} + +}} // namespace python::detail + +#endif +""") + +if __name__ == '__main__': + import sys + + if len(sys.argv) == 1: + args = 5 + else: + args = int(sys.argv[1]) + + print gen_singleton(args) diff --git a/inheritance.html b/inheritance.html new file mode 100644 index 00000000..385fe812 --- /dev/null +++ b/inheritance.html @@ -0,0 +1,165 @@ + + + Inheritance + +
+

+ c++boost.gif (8819 bytes)Inheritance +

+ +

Inheritance in Python

+ +

+ Py_cpp extension classes support single and multiple-inheritance in + Python, just like regular Python classes. You can mix built-in Python + classes with py_cpp extension classes in a derived class' tuple of + bases. Whenever a py_cpp extension class is among the bases for a new + class in Python, the result is an extension class: +

+
+>>> class MyPythonClass:
+...     def f(): return 'MyPythonClass.f()'
+...
+>>> import my_extension_module
+>>> class Derived(my_extension_module.MyExtensionClass, MyPythonClass):
+...     '''This is an extension class'''
+...     pass
+...
+>>> x = Derived()
+>>> x.f()
+'MyPythonClass.f()'
+>>> x.g()
+'MyExtensionClass.g()'
+
+
+ +

Reflecting C++ Inheritance Relationships

+

+ Py_cpp also allows us to represent C++ inheritance relationships so that + wrapped derived classes may be passed where values, pointers, or + references to a base class are expected as arguments. The + declare_base member function of + class_builder<> is used to establish the relationship + between base and derived classes: + +

+
+#include <memory> // for std::auto_ptr<>
+
+struct Base {
+    virtual ~Base() {}
+    virtual const char* name() const { return "Base"; }
+};
+
+struct Derived : Base {
+    Derived() : x(-1) {}
+    virtual const char* name() const { return "Derived"; }
+    int x;
+};
+
+std::auto_ptr<Base> derived_as_base() {
+    return std::auto_ptr<Base>(new Derived);
+}
+
+const char* get_name(const Base& b) {
+    return b.name();
+}
+
+int get_derived_x(const Derived& d) {
+    return d.x;
+}
+    
+#include <py_cpp/class_wrapper.h> +extern "C" +#ifdef _WIN32 +__declspec(dllexport) +#endif +void initmy_module() +{ +    try +    { +       python::module_builder my_module("my_module"); + +       python::class_builder<Base> base_class(my_module, "Base"); +       base_class.def(python::constructor<void>()); + +       python::class_builder<Derived> derived_class(my_module, "Derived"); +       derived_class.def(python::constructor<void>()); + // Establish the inheritance relationship between Base and Derived + derived_class.declare_base(base_class); + + my_module.def(derived_as_base, "derived_as_base"); + my_module.def(get_name, "get_name"); + my_module.def(get_derived_x, "get_derived_x"); +    } +    catch(...) +    { +       python::handle_exception();    // Deal with the exception for Python +    } +} +
+
+ +

+ Then, in Python: +

+
+>>> from my_module import *
+>>> base = Base()
+>>> derived = Derived()
+>>> get_name(base)
+'Base'
+
objects of wrapped class Derived may be passed where Base is expected
+>>> get_name(derived) 
+'Derived'
+
objects of wrapped class Derived can be passed where Derived is +expected but where type information has been lost.
+>>> get_derived_x(derived_as_base()) 
+-1
+
+
+ +

Inheritance Without Virtual Functions

+ +

+ If for some reason your base class has no virtual functions but you still want + to represent the inheritance relationship between base and derived classes, + pass the special symbol python::without_downcast as the 2nd parameter + to declare_base: + +

+
+struct Base2 {};
+struct Derived2 { int f(); };
+
+ ... +   python::class_builder<Base> base2_class(my_module, "Base2"); +   base2_class.def(python::constructor<void>()); + +   python::class_builder<Derived2> derived2_class(my_module, "Derived2"); +   derived2_class.def(python::constructor<void>()); + derived_class.declare_base(base_class, python::without_downcast); +
+
+ +

This approach will allow Derived2 objects to be passed where + Base2 is expected, but does not attempt to implicitly convert (downcast) + smart-pointers to Base2 into Derived2 pointers, + references, or values. + +

+ Previous: Function Overloading + Next: Special Method Names + 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 30, 2000 +

+ diff --git a/init_function.cpp b/init_function.cpp new file mode 100644 index 00000000..f29e914a --- /dev/null +++ b/init_function.cpp @@ -0,0 +1,36 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "init_function.h" +#include "objects.h" +#include "extclass.h" +#include + +namespace python { namespace detail { + + PyObject* init::do_call(PyObject* args_, PyObject* keywords) const + { + tuple args(ref(args_, ref::increment_count)); + if (args[0]->ob_type->ob_type != extension_meta_class()) + { + PyErr_SetString(PyExc_TypeError, "argument 1 to __init__ must be an ExtensionInstance"); + return 0; + } + + extension_instance *self = static_cast(args[0].get()); + + tuple ctor_args = args.slice(1, args.size()); + + std::auto_ptr result( + create_holder(self, ctor_args.get(), keywords)); + + self->add_implementation(result); + return none(); + } + +}} // namespace python::detail diff --git a/init_function.h b/init_function.h new file mode 100644 index 00000000..14da7842 --- /dev/null +++ b/init_function.h @@ -0,0 +1,507 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file was generated for %d-argument constructors by gen_init_function.python + +#ifndef INIT_FUNCTION_DWA052000_H_ +# define INIT_FUNCTION_DWA052000_H_ + +# include "pyconfig.h" +# include "functions.h" +# include "signatures.h" +# include + +namespace python { + +namespace detail { + + // parameter_traits - so far, this is a way to pass a const T& when we can be + // sure T is not a reference type, and a raw T otherwise. This should be + // rolled into boost::call_traits. Ordinarily, parameter_traits would be + // written: + // + // template struct parameter_traits + // { + // typedef const T& const_reference; + // }; + // + // template struct parameter_traits + // { + // typedef T& const_reference; + // }; + // + // template <> struct parameter_traits + // { + // typedef void const_reference; + // }; + // + // ...but since we can't partially specialize on reference types, we need this + // long-winded but equivalent incantation. + + // const_ref_selector -- an implementation detail of parameter_traits (below). This uses + // the usual "poor man's partial specialization" hack for MSVC. + template + struct const_ref_selector + { + template + struct const_ref + { + typedef const T& type; + }; + }; + + template <> + struct const_ref_selector + { + template + struct const_ref + { + typedef T type; + }; + }; + +# ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable: 4181) +# endif // BOOST_MSVC + template + struct parameter_traits + { + private: + typedef const_ref_selector::value> selector; + public: + typedef typename selector::template const_ref::type const_reference; + }; +# ifdef BOOST_MSVC +# pragma warning(pop) +# endif // BOOST_MSVC + + // Full spcialization for void + template <> + struct parameter_traits + { + typedef void const_reference; + }; + + template + class reference_parameter + { + typedef typename parameter_traits::const_reference const_reference; + public: + reference_parameter(const_reference value) + : value(value) {} + operator const_reference() { return value; } + private: + const_reference value; + }; + +class extension_instance; +class instance_holder_base; + +class init; +template struct init0; +template struct init1; +template struct init2; +template struct init3; +template struct init4; +template struct init5; +template struct Init6; +template struct Init7; +template struct Init8; +template struct Init9; +template struct Init10; + +template +struct init_function +{ + static init* create(signature0) { + return new init0; + } + + template + static init* create(signature1) { + return new init1::const_reference>; + } + + template + static init* create(signature2) { + return new init2::const_reference, + detail::parameter_traits::const_reference>; + } + + template + static init* create(signature3) { + return new init3::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference>; + } + + template + static init* create(signature4) { + return new init4::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference>; + } + + template + static init* create(signature5) { + return new init5::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference>; + } + + template + static init* create(signature6) { + return new Init6::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference>; + } + + template + static init* create(signature7) { + return new Init7::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference>; + } + + template + static init* create(signature8) { + return new Init8::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference>; + } + + template + static init* create(signature9) { + return new Init9::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference>; + } + + template + static init* create(signature10) { + return new Init10::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference, + detail::parameter_traits::const_reference>; + } +}; + +class init : public function +{ +private: // override function hook + PyObject* do_call(PyObject* args, PyObject* keywords) const; +private: + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* tail_args, PyObject* keywords) const = 0; +}; + + +template +struct init0 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + if (!PyArg_ParseTuple(args, const_cast(""))) + throw argument_error(); + return new T(self + ); + } + const char* description() const + { return typeid(void (*)(T&)).name(); } +}; + +template +struct init1 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + if (!PyArg_ParseTuple(args, const_cast("O"), &a1)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1)).name(); } +}; + +template +struct init2 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + if (!PyArg_ParseTuple(args, const_cast("OO"), &a1, &a2)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2)).name(); } +}; + +template +struct init3 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + if (!PyArg_ParseTuple(args, const_cast("OOO"), &a1, &a2, &a3)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())), + python::detail::reference_parameter(from_python(a3, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2, A3)).name(); } +}; + +template +struct init4 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + if (!PyArg_ParseTuple(args, const_cast("OOOO"), &a1, &a2, &a3, &a4)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())), + python::detail::reference_parameter(from_python(a3, type())), + python::detail::reference_parameter(from_python(a4, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2, A3, A4)).name(); } +}; + +template +struct init5 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + if (!PyArg_ParseTuple(args, const_cast("OOOOO"), &a1, &a2, &a3, &a4, &a5)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())), + python::detail::reference_parameter(from_python(a3, type())), + python::detail::reference_parameter(from_python(a4, type())), + python::detail::reference_parameter(from_python(a5, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2, A3, A4, A5)).name(); } +}; + +template +struct Init6 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + if (!PyArg_ParseTuple(args, const_cast("OOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())), + python::detail::reference_parameter(from_python(a3, type())), + python::detail::reference_parameter(from_python(a4, type())), + python::detail::reference_parameter(from_python(a5, type())), + python::detail::reference_parameter(from_python(a6, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2, A3, A4, A5, A6)).name(); } +}; + +template +struct Init7 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())), + python::detail::reference_parameter(from_python(a3, type())), + python::detail::reference_parameter(from_python(a4, type())), + python::detail::reference_parameter(from_python(a5, type())), + python::detail::reference_parameter(from_python(a6, type())), + python::detail::reference_parameter(from_python(a7, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2, A3, A4, A5, A6, A7)).name(); } +}; + +template +struct Init8 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())), + python::detail::reference_parameter(from_python(a3, type())), + python::detail::reference_parameter(from_python(a4, type())), + python::detail::reference_parameter(from_python(a5, type())), + python::detail::reference_parameter(from_python(a6, type())), + python::detail::reference_parameter(from_python(a7, type())), + python::detail::reference_parameter(from_python(a8, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2, A3, A4, A5, A6, A7, A8)).name(); } +}; + +template +struct Init9 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())), + python::detail::reference_parameter(from_python(a3, type())), + python::detail::reference_parameter(from_python(a4, type())), + python::detail::reference_parameter(from_python(a5, type())), + python::detail::reference_parameter(from_python(a6, type())), + python::detail::reference_parameter(from_python(a7, type())), + python::detail::reference_parameter(from_python(a8, type())), + python::detail::reference_parameter(from_python(a9, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2, A3, A4, A5, A6, A7, A8, A9)).name(); } +}; + +template +struct Init10 : init +{ + virtual instance_holder_base* create_holder(extension_instance* self, PyObject* args, PyObject* /*keywords*/) const + { + PyObject* a1; + PyObject* a2; + PyObject* a3; + PyObject* a4; + PyObject* a5; + PyObject* a6; + PyObject* a7; + PyObject* a8; + PyObject* a9; + PyObject* a10; + if (!PyArg_ParseTuple(args, const_cast("OOOOOOOOOO"), &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10)) + throw argument_error(); + return new T(self, + python::detail::reference_parameter(from_python(a1, type())), + python::detail::reference_parameter(from_python(a2, type())), + python::detail::reference_parameter(from_python(a3, type())), + python::detail::reference_parameter(from_python(a4, type())), + python::detail::reference_parameter(from_python(a5, type())), + python::detail::reference_parameter(from_python(a6, type())), + python::detail::reference_parameter(from_python(a7, type())), + python::detail::reference_parameter(from_python(a8, type())), + python::detail::reference_parameter(from_python(a9, type())), + python::detail::reference_parameter(from_python(a10, type())) + ); + } + const char* description() const + { return typeid(void (*)(T&, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)).name(); } +}; + +}} // namespace python::detail + +#endif // INIT_FUNCTION_DWA052000_H_ diff --git a/module.cpp b/module.cpp new file mode 100644 index 00000000..8d0b3ddb --- /dev/null +++ b/module.cpp @@ -0,0 +1,52 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "module.h" + +namespace python { + +namespace { + ref name_holder; +} + +string module_builder::name() +{ + // If this fails, you haven't created a module_builder object + assert(name_holder.get() != 0); + return string(name_holder); +} + +module_builder::module_builder(const char* name) + : m_module(Py_InitModule(const_cast(name), initial_methods)) +{ + // If this fails, you've created more than 1 module_builder object in your module + assert(name_holder.get() == 0); + name_holder = ref(PyObject_GetAttrString(m_module, const_cast("__name__"))); +} + +void +module_builder::add(detail::function* x, const char* name) +{ + reference f(x); // First take possession of the object. + detail::function::add_to_namespace(f, name, PyModule_GetDict(m_module)); +} + +void module_builder::add(ref x, const char* name) +{ + PyObject* dictionary = PyModule_GetDict(m_module); + PyDict_SetItemString(dictionary, const_cast(name), x.get()); +} + +void module_builder::add(PyTypeObject* x, const char* name /*= 0*/) +{ + this->add(ref(as_object(x)), name ? name : x->tp_name); +} + +PyMethodDef module_builder::initial_methods[] = { { 0, 0, 0, 0 } }; + +} diff --git a/module.h b/module.h new file mode 100644 index 00000000..601ef17d --- /dev/null +++ b/module.h @@ -0,0 +1,53 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef MODULE_DWA051000_H_ +# define MODULE_DWA051000_H_ + +# include "pyconfig.h" +# include "pyptr.h" +# include "objects.h" +# include "functions.h" + +namespace python { + +class module_builder +{ + typedef PyObject * (*raw_function_ptr)(python::tuple const &, python::dictionary const &); + + public: + // Create a module. REQUIRES: only one module_builder is created per module. + module_builder(const char* name); + + // Add elements to the module + void add(detail::function* x, const char* name); + void add(PyTypeObject* x, const char* name = 0); + void add(ref x, const char*name); + + template + void def_raw(Fn fn, const char* name) + { + add(detail::new_raw_arguments_function(fn), name); + } + + template + void def(Fn fn, const char* name) + { + add(detail::new_wrapped_function(fn), name); + } + + static string name(); + + private: + PyObject* m_module; + static PyMethodDef initial_methods[1]; +}; + +} + +#endif diff --git a/newtypes.cpp b/newtypes.cpp new file mode 100644 index 00000000..2bb115b8 --- /dev/null +++ b/newtypes.cpp @@ -0,0 +1,1098 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "newtypes.h" +#include "pyptr.h" // for handle_exception() +#include "module.h" +#include "none.h" +#include +#include +#include +#include +#include +#include "objects.h" +#include + +namespace python { + +namespace { + + using detail::type_object_base; + + PyObject* call(PyObject* obj, PyObject* (type_object_base::*f)(PyObject*) const) + { + try + { + return (static_cast(obj->ob_type)->*f)(obj); + } + catch(...) + { + handle_exception(); + return 0; + } + } + + // Naming this differently allows us to use it for functions returning long on + // compilers without partial ordering + template + R int_call(PyObject* obj, R (type_object_base::*f)(PyObject*) const) + { + try + { + return (static_cast(obj->ob_type)->*f)(obj); + } + catch(...) + { + handle_exception(); + return -1; + } + } + + // Implemented in terms of int_call, above + int call(PyObject* obj, int (type_object_base::*f)(PyObject*) const) + { + return int_call(obj, f); + } + + template + PyObject* call(PyObject* obj, PyObject* (type_object_base::*f)(PyObject*, A1) const, A1 a1) + { + try + { + return (static_cast(obj->ob_type)->*f)(obj, a1); + } + catch(...) + { + handle_exception(); + return 0; + } + } + + template + int call(PyObject* obj, int (type_object_base::*f)(PyObject*, A1) const, A1 a1) + { + try + { + return (static_cast(obj->ob_type)->*f)(obj, a1); + } + catch(...) + { + handle_exception(); + return -1; + } + } + + template + PyObject* call(PyObject* obj, PyObject* (type_object_base::*f)(PyObject*, A1, A2) const, A1 a1, A2 a2) + { + try + { + return (static_cast(obj->ob_type)->*f)(obj, a1, a2); + } + catch(...) + { + handle_exception(); + return 0; + } + } + + template + int call(PyObject* obj, int (type_object_base::*f)(PyObject*, A1, A2) const, A1 a1, A2 a2) + { + try + { + return (static_cast(obj->ob_type)->*f)(obj, a1, a2); + } + catch(...) + { + handle_exception(); + return -1; + } + } + + template + int call(PyObject* obj, int (type_object_base::*f)(PyObject*, A1, A2, A3) const, A1 a1, A2 a2, A3 a3) + { + try + { + return (static_cast(obj->ob_type)->*f)(obj, a1, a2, a3); + } + catch(...) + { + handle_exception(); + return -1; + } + } + + int call_length_function(PyObject* obj, int (type_object_base::*f)(PyObject*) const) + { + try + { + const int outcome = + (static_cast(obj->ob_type)->*f)(obj); + + if (outcome < 0) + { + PyErr_SetString(PyExc_ValueError, "__len__() should return >= 0"); + return -1; + } + return outcome; + } + catch(...) + { + handle_exception(); + return -1; + } + } + +} // anonymous namespace + +extern "C" { + +static PyObject* do_instance_repr(PyObject* obj) +{ + return call(obj, &type_object_base::instance_repr); +} + +static int do_instance_compare(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_compare, other); +} + +static PyObject* do_instance_str(PyObject* obj) +{ + return call(obj, &type_object_base::instance_str); +} + +static long do_instance_hash(PyObject* obj) +{ + return int_call(obj, &type_object_base::instance_hash); +} + +static PyObject* do_instance_call(PyObject* obj, PyObject* args, PyObject* keywords) +{ + return call(obj, &type_object_base::instance_call, args, keywords); +} + +static void do_instance_dealloc(PyObject* obj) +{ + try + { + static_cast(obj->ob_type) + ->instance_dealloc(obj); + } + catch(...) + { + assert(!"exception during destruction!"); + handle_exception(); + } +} + +static PyObject* do_instance_getattr(PyObject* obj, char* name) +{ + const char* name_ = name; + return call(obj, &type_object_base::instance_getattr, name_); +} + +static int do_instance_setattr(PyObject* obj, char* name, PyObject* value) +{ + const char* name_ = name; + return call(obj, &type_object_base::instance_setattr, name_, value); +} + +static int do_instance_mp_length(PyObject* obj) +{ + return call_length_function(obj, &type_object_base::instance_mapping_length); +} + +static int do_instance_sq_length(PyObject* obj) +{ + return call_length_function(obj, &type_object_base::instance_sequence_length); +} + +static PyObject* do_instance_mp_subscript(PyObject* obj, PyObject* index) +{ + return call(obj, &type_object_base::instance_mapping_subscript, index); +} + +static PyObject* do_instance_sq_item(PyObject* obj, int index) +{ + try + { + const PyTypeObject* const type = obj->ob_type; + + // This is an extension to standard class behavior. If sequence_length + // is implemented and n >= sequence_length(), raise an IndexError. That + // keeps users from having to worry about raising it themselves + if (type->tp_as_sequence != 0 && type->tp_as_sequence->sq_length != 0 + && index >= type->tp_as_sequence->sq_length(obj)) + { + PyErr_SetString(PyExc_IndexError, type->tp_name); + return 0; + } + + return static_cast(obj->ob_type) + ->instance_sequence_item(obj, index); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +static int do_instance_mp_ass_subscript(PyObject* obj, PyObject* index, PyObject* value) +{ + return call(obj, &type_object_base::instance_mapping_ass_subscript, index, value); +} + +static int do_instance_sq_ass_item(PyObject* obj, int index, PyObject* value) +{ + return call(obj, &type_object_base::instance_sequence_ass_item, index, value); +} + +static PyObject* do_instance_sq_concat(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_sequence_concat, other); +} + +static PyObject* do_instance_sq_repeat(PyObject* obj, int n) +{ + return call(obj, &type_object_base::instance_sequence_repeat, n); +} + +static PyObject* do_instance_sq_slice( + PyObject* obj, int start, int finish) +{ + return call(obj, &type_object_base::instance_sequence_slice, start, finish); +} + +static int do_instance_sq_ass_slice( + PyObject* obj, int start, int finish, PyObject* value) +{ + return call(obj, &type_object_base::instance_sequence_ass_slice, start, finish, value); +} + +static PyObject* do_instance_nb_add(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_add, other); +} + +static PyObject* do_instance_nb_subtract(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_subtract, other); +} + +static PyObject* do_instance_nb_multiply(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_multiply, other); +} + +static PyObject* do_instance_nb_divide(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_divide, other); +} + +static PyObject* do_instance_nb_remainder(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_remainder, other); +} + +static PyObject* do_instance_nb_divmod(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_divmod, other); +} + +static PyObject* do_instance_nb_power(PyObject* obj, PyObject* exponent, PyObject* modulus) +{ + return call(obj, &type_object_base::instance_number_power, exponent, modulus); +} + +static PyObject* do_instance_nb_negative(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_negative); +} + +static PyObject* do_instance_nb_positive(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_positive); +} + +static PyObject* do_instance_nb_absolute(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_absolute); +} + +static int do_instance_nb_nonzero(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_nonzero); +} + +static PyObject* do_instance_nb_invert(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_invert); +} + + +static PyObject* do_instance_nb_lshift(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_lshift, other); +} + +static PyObject* do_instance_nb_rshift(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_rshift, other); +} + +static PyObject* do_instance_nb_and(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_and, other); +} + +static PyObject* do_instance_nb_xor(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_xor, other); +} + +static PyObject* do_instance_nb_or(PyObject* obj, PyObject* other) +{ + return call(obj, &type_object_base::instance_number_or, other); +} + +static int do_instance_nb_coerce(PyObject**obj, PyObject**other) +{ + return call(*obj, &type_object_base::instance_number_coerce, obj, other); +} +static PyObject* do_instance_nb_int(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_int); +} + +static PyObject* do_instance_nb_long(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_long); +} + +static PyObject* do_instance_nb_float(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_float); +} + +static PyObject* do_instance_nb_oct(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_oct); +} + +static PyObject* do_instance_nb_hex(PyObject* obj) +{ + return call(obj, &type_object_base::instance_number_hex); +} + +} // extern "C" + +namespace +{ + +#define ENABLE_GENERAL_CAPABILITY(field) \ + case type_object_base::field: \ + dest->tp_##field = &do_instance_##field; \ + return true + +bool add_capability_general(type_object_base::capability capability, PyTypeObject* dest) +{ + assert(dest != 0); + + switch(capability) + { + ENABLE_GENERAL_CAPABILITY(hash); + ENABLE_GENERAL_CAPABILITY(call); + ENABLE_GENERAL_CAPABILITY(str); + ENABLE_GENERAL_CAPABILITY(getattr); + ENABLE_GENERAL_CAPABILITY(setattr); + ENABLE_GENERAL_CAPABILITY(compare); + ENABLE_GENERAL_CAPABILITY(repr); + default: + return false; + } +} + + +template +void create_method_table_if_null(T*& table) +{ + if(table == 0) + { + detail::shared_pod_manager::create(table); + } + else + { + detail::shared_pod_manager::make_unique_copy(table); + } +} + +#define ENABLE_MAPPING_CAPABILITY(field) \ + case type_object_base::mapping_##field: \ + create_method_table_if_null(dest); \ + dest->mp_##field = &do_instance_mp_##field; \ + detail::shared_pod_manager::replace_if_equal(dest); \ + return true + +bool add_capability_mapping(type_object_base::capability capability, PyMappingMethods*& dest) +{ + switch(capability) + { + ENABLE_MAPPING_CAPABILITY(length); + ENABLE_MAPPING_CAPABILITY(subscript); + ENABLE_MAPPING_CAPABILITY(ass_subscript); + default: + return false; + } +} + +#define ENABLE_SEQUENCE_CAPABILITY(field) \ + case type_object_base::sequence_##field: \ + create_method_table_if_null(dest); \ + dest->sq_##field = &do_instance_sq_##field; \ + detail::shared_pod_manager::replace_if_equal(dest); \ + return true + +bool add_capability_sequence(type_object_base::capability capability, PySequenceMethods*& dest) +{ + switch(capability) + { + ENABLE_SEQUENCE_CAPABILITY(length); + ENABLE_SEQUENCE_CAPABILITY(item); + ENABLE_SEQUENCE_CAPABILITY(ass_item); + ENABLE_SEQUENCE_CAPABILITY(concat); + ENABLE_SEQUENCE_CAPABILITY(repeat); + ENABLE_SEQUENCE_CAPABILITY(slice); + ENABLE_SEQUENCE_CAPABILITY(ass_slice); + default: + return false; + } +} + +#define ENABLE_NUMBER_CAPABILITY(field) \ + case type_object_base::number_##field: \ + create_method_table_if_null(dest); \ + dest->nb_##field = &do_instance_nb_##field; \ + detail::shared_pod_manager::replace_if_equal(dest); \ + return true + +bool add_capability_number(type_object_base::capability capability, PyNumberMethods*& dest) +{ + switch(capability) + { + ENABLE_NUMBER_CAPABILITY(add); + ENABLE_NUMBER_CAPABILITY(subtract); + ENABLE_NUMBER_CAPABILITY(multiply); + ENABLE_NUMBER_CAPABILITY(divide); + ENABLE_NUMBER_CAPABILITY(remainder); + ENABLE_NUMBER_CAPABILITY(divmod); + ENABLE_NUMBER_CAPABILITY(power); + ENABLE_NUMBER_CAPABILITY(negative); + ENABLE_NUMBER_CAPABILITY(positive); + ENABLE_NUMBER_CAPABILITY(absolute); + ENABLE_NUMBER_CAPABILITY(nonzero); + ENABLE_NUMBER_CAPABILITY(invert); + ENABLE_NUMBER_CAPABILITY(lshift); + ENABLE_NUMBER_CAPABILITY(rshift); + ENABLE_NUMBER_CAPABILITY(and); + ENABLE_NUMBER_CAPABILITY(xor); + ENABLE_NUMBER_CAPABILITY(or); + ENABLE_NUMBER_CAPABILITY(coerce); + ENABLE_NUMBER_CAPABILITY(int); + ENABLE_NUMBER_CAPABILITY(long); + ENABLE_NUMBER_CAPABILITY(float); + ENABLE_NUMBER_CAPABILITY(oct); + ENABLE_NUMBER_CAPABILITY(hex); + default: + return false; + } +} + +#define ENABLE_BUFFER_CAPABILITY(field) \ + case type_object_base::buffer_##field: \ + create_method_table_if_null(dest); \ + dest->bf_##field = &do_instance_bf_##field; \ + detail::shared_pod_manager::replace_if_equal(dest); \ + return true + +bool add_capability_buffer(type_object_base::capability capability, PyBufferProcs*& dest) +{ + (void)dest; // suppress unused argument warning + (void)capability; // likwise +#if 0 + switch(capability) + { + // nothing defined yet + default: + return false; + } +#endif + return false; +} + +} // anonymous namespace + +namespace detail { + + void add_capability( + type_object_base::capability capability, + PyTypeObject* dest_) + { + if(add_capability_general(capability, dest_)) + return; + if(add_capability_mapping(capability, dest_->tp_as_mapping)) + return; + if(add_capability_sequence(capability, dest_->tp_as_sequence)) + return; + if(add_capability_number(capability, dest_->tp_as_number)) + return; + if(add_capability_buffer(capability, dest_->tp_as_buffer)) + return; + + // no one recognized the capability + throw std::runtime_error("py::detail::add_capability(): unknown capability"); + } +} // namespace detail + +type_object_base::~type_object_base() +{ + detail::shared_pod_manager::dispose(tp_as_mapping); + detail::shared_pod_manager::dispose(tp_as_sequence); + detail::shared_pod_manager::dispose(tp_as_number); + detail::shared_pod_manager::dispose(tp_as_buffer); +} + +void type_object_base::enable(type_object_base::capability capability) +{ + detail::add_capability(capability, this); +} + +type_object_base::type_object_base(PyTypeObject* t) + : python_type(t) +{ + this->tp_dealloc = do_instance_dealloc; +} + +namespace +{ + typedef long pod_refcount; + + inline pod_refcount pod_refcount_offset(std::size_t size) + { + const std::size_t alignment = boost::alignment_of::value; + return (size + alignment - 1) / alignment * alignment; + } + + inline pod_refcount* counted_pod_refcount(char* pod, std::size_t size) + { + if(pod == 0) + return 0; + + return reinterpret_cast(pod + pod_refcount_offset(size)); + } + + #ifdef TYPE_OBJECT_BASE_STANDALONE_TEST + int pod_instance_counter = 0; + #endif + + inline pod_refcount counted_pod_getref(char* pod, std::size_t size) + { + pod_refcount* ref_count = counted_pod_refcount(pod, size); + return ref_count == 0 ? -1 : *ref_count; + } + + inline pod_refcount counted_pod_decref(char* pod, std::size_t size) + { + pod_refcount* const ref_count = counted_pod_refcount(pod, size); + if (ref_count == 0) + return -1; + --(*ref_count); + if (*ref_count <= 0) + { + #ifdef TYPE_OBJECT_BASE_STANDALONE_TEST + --pod_instance_counter; + #endif + ::operator delete(pod); + return 0; + } + return *ref_count; + } + + pod_refcount counted_pod_incref(char* pod, std::size_t size) + { + pod_refcount* ref_count = counted_pod_refcount(pod, size); + return ref_count == 0 ? -1 : ++(*ref_count); + } + +} // anonymous namespace + +namespace detail +{ + struct shared_pod_manager::compare + { + bool operator()(const std::pair& x1, + const std::pair& x2) const + { + const std::size_t n1 = x1.second; + const std::size_t n2 = x2.second; + return n1 < n2 || n1 == n2 && BOOST_CSTD_::memcmp(x1.first, x2.first, n1) < 0; + } + }; + + struct shared_pod_manager::identical + { + identical(char* p) : pod(p) {} + + bool operator()(const std::pair& x) const + { + return pod == x.first; + } + + char* pod; + }; + + shared_pod_manager& shared_pod_manager::obj() + { + static shared_pod_manager spm; + return spm; + } + + shared_pod_manager::~shared_pod_manager() + { + } + + void* shared_pod_manager::replace_if_equal(void* pod, std::size_t size) + { + if(pod == 0) + return 0; + + const holder element(static_cast(pod), size); + + const storage::iterator found + = std::lower_bound(m_storage.begin(), m_storage.end(), element, compare()); + + if (found != m_storage.end() && pod == found->first) + { + // pod already in list => do nothing + return pod; + } + else if (found != m_storage.end() && !compare()(element, *found)) + { + // equal element in list => replace + void* replacement = found->first; + counted_pod_incref(found->first, size); + dec_ref(element.first, size); // invalidates iterator 'found' + return replacement; + } + else + { + // new element => insert + m_storage.insert(found, element); + return pod; + } + } + + void* shared_pod_manager::make_unique_copy(void* pod, std::size_t size) + { + if(pod == 0) + return 0; + if(counted_pod_getref(static_cast(pod), size) == 1) + { + erase_from_list(pod); + return pod; + } + else + { + void* copy = create(size); + memmove(copy, pod, size); + dec_ref(pod, size); + return copy; + } + } + + void* shared_pod_manager::create(std::size_t size) + { + std::size_t total_size = pod_refcount_offset(size) + sizeof(pod_refcount); + char* pod = static_cast(::operator new(total_size)); + #ifdef TYPE_OBJECT_BASE_STANDALONE_TEST + ++pod_instance_counter; + #endif + memset(pod, 0, total_size); + + *counted_pod_refcount(pod, size) = 1; + + return pod; + } + + void shared_pod_manager::dec_ref(void* pod, std::size_t size) + { + if(pod == 0) + return; + + int ref_count = counted_pod_decref(static_cast(pod), size); + + if(ref_count <= 0) + erase_from_list(pod); + } + + void shared_pod_manager::erase_from_list(void* pod) + { + if(pod == 0) + return; + + const storage::iterator found = + std::find_if(m_storage.begin(), m_storage.end(), + identical(static_cast(pod))); + + if(found != m_storage.end()) + { + m_storage.erase(found); + } + } +} // namespace detail + +namespace { + struct error_type { + operator PyObject*() const { return 0; } + operator int() const { return -1; } + }; + + error_type unimplemented(const char* name) + { + assert(!"Control should never reach here"); + string s("Unimplemented "); + s += string(name); + PyErr_SetObject(PyExc_RuntimeError, s.get()); + return error_type(); + } +} + +PyObject* type_object_base::instance_repr(PyObject*) const +{ + return unimplemented("instance_repr"); +} + +int type_object_base::instance_compare(PyObject*, PyObject*) const +{ + return unimplemented("instance_compare"); +} + +PyObject* type_object_base::instance_str(PyObject*) const +{ + return unimplemented("instance_str"); +} + +long type_object_base::instance_hash(PyObject* /* obj */) const +{ + return unimplemented("instance_hash"); +} + +PyObject* type_object_base::instance_call(PyObject* /*obj*/, PyObject* /*args*/, PyObject* /*kw*/) const +{ + return unimplemented("instance_call"); +} + +PyObject* type_object_base::instance_getattr(PyObject* /*obj*/, const char* /*name*/) const +{ + return unimplemented("instance_getattr"); +} + +int type_object_base::instance_setattr(PyObject* /*obj*/, const char* /*name*/, PyObject* /*value*/) const +{ + return unimplemented("instance_setattr"); +} + +int type_object_base::instance_mapping_length(PyObject*) const +{ + return unimplemented("instance_mapping_length"); +} + +int type_object_base::instance_sequence_length(PyObject*) const +{ + return unimplemented("instance_sequence_length"); +} + +PyObject* type_object_base::instance_mapping_subscript(PyObject*, PyObject*) const +{ + return unimplemented("instance_mapping_subscript"); +} + +PyObject* type_object_base::instance_sequence_item(PyObject*, int) const +{ + return unimplemented("instance_sequence_item"); +} + +int type_object_base::instance_mapping_ass_subscript(PyObject*, PyObject*, PyObject*) const +{ + return unimplemented("instance_mapping_ass_subscript"); +} + +int type_object_base::instance_sequence_ass_item(PyObject*, int, PyObject*) const +{ + return unimplemented("instance_sequence_ass_item"); +} + +PyObject* type_object_base::instance_sequence_concat(PyObject*, PyObject*) const +{ + return unimplemented("instance_sequence_concat"); +} + +PyObject* type_object_base::instance_sequence_repeat(PyObject*, int) const +{ + return unimplemented("instance_sequence_repeat"); +} + +PyObject* type_object_base::instance_sequence_slice(PyObject*, int, int) const +{ + return unimplemented("instance_sequence_slice"); +} + +int type_object_base::instance_sequence_ass_slice(PyObject*, int, int, PyObject*) const +{ + return unimplemented("instance_sequence_ass_slice"); +} + +PyObject* type_object_base::instance_number_add(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_add"); +} + +PyObject* type_object_base::instance_number_subtract(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_subtract"); +} + +PyObject* type_object_base::instance_number_multiply(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_multiply"); +} + +PyObject* type_object_base::instance_number_divide(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_divide"); +} + +PyObject* type_object_base::instance_number_remainder(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_remainder"); +} + +PyObject* type_object_base::instance_number_divmod(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_divmod"); +} + +PyObject* type_object_base::instance_number_power(PyObject*, PyObject*, PyObject*) const +{ + return unimplemented("instance_number_divmod"); +} + +PyObject* type_object_base::instance_number_negative(PyObject*) const +{ + return unimplemented("instance_number_negative"); +} + +PyObject* type_object_base::instance_number_positive(PyObject*) const +{ + return unimplemented("instance_number_positive"); +} + +PyObject* type_object_base::instance_number_absolute(PyObject*) const +{ + return unimplemented("instance_number_absolute"); +} + +int type_object_base::instance_number_nonzero(PyObject*) const +{ + return unimplemented("instance_number_nonzero"); +} + +PyObject* type_object_base::instance_number_invert(PyObject*) const +{ + return unimplemented("instance_number_invert"); +} + +PyObject* type_object_base::instance_number_lshift(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_lshift"); +} + +PyObject* type_object_base::instance_number_rshift(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_rshift"); +} + +PyObject* type_object_base::instance_number_and(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_and"); +} + +PyObject* type_object_base::instance_number_xor(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_xor"); +} + +PyObject* type_object_base::instance_number_or(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_or"); +} + +int type_object_base::instance_number_coerce(PyObject*, PyObject**, PyObject**) const +{ + return unimplemented("instance_number_coerce"); +} + +PyObject* type_object_base::instance_number_int(PyObject*) const +{ + return unimplemented("instance_number_int"); +} + +PyObject* type_object_base::instance_number_long(PyObject*) const +{ + return unimplemented("instance_number_long"); +} + +PyObject* type_object_base::instance_number_float(PyObject*) const +{ + return unimplemented("instance_number_float"); +} + +PyObject* type_object_base::instance_number_oct(PyObject*) const +{ + return unimplemented("instance_number_oct"); +} + +PyObject* type_object_base::instance_number_hex(PyObject*) const +{ + return unimplemented("instance_number_hex"); +} + + +} + +#ifdef TYPE_OBJECT_BASE_STANDALONE_TEST + +struct TestTypeObject : python::type_object_base +{ + TestTypeObject() + : python::type_object_base(Py_None->ob_type->ob_type) + {} + + void instance_dealloc(PyObject*) const {} +}; + +struct POD1 +{ + unsigned char data; +}; + +int main() +{ + python::type_object_base *o1, *o2, *o3; + +// POD1 * pod1; +// python::detail::shared_pod_manager::create(pod1); + + o1 = new TestTypeObject; + o2 = new TestTypeObject; + o3 = new TestTypeObject; + + assert(python::pod_instance_counter == 0); + + o1->enable(python::type_object_base::number_add); + o1->enable(python::type_object_base::compare); + + o2->enable(python::type_object_base::number_add); + o2->enable(python::type_object_base::mapping_length); + + o3->enable(python::type_object_base::number_add); + o3->enable(python::type_object_base::sequence_length); + + assert(python::pod_instance_counter == 3); + assert(o1->tp_as_number && !o1->tp_as_mapping && !o1->tp_as_sequence); + assert(o2->tp_as_number && o2->tp_as_mapping && !o2->tp_as_sequence); + assert(o3->tp_as_number && !o3->tp_as_mapping && o3->tp_as_sequence); + assert(o1->tp_as_number == o2->tp_as_number); + assert(o1->tp_as_number == o3->tp_as_number); + assert((void*)o2->tp_as_number != o2->tp_as_mapping); + assert((void*)o2->tp_as_mapping != o3->tp_as_sequence); + + o1->enable(python::type_object_base::number_subtract); + + assert(python::pod_instance_counter == 4); + assert(o1->tp_as_number != o2->tp_as_number); + assert(o2->tp_as_number == o3->tp_as_number); + + o3->enable(python::type_object_base::mapping_subscript); + + assert(python::pod_instance_counter == 5); + assert(o3->tp_as_number && o3->tp_as_mapping && o3->tp_as_sequence); + assert(o2->tp_as_mapping != o3->tp_as_mapping); + + o2->enable(python::type_object_base::mapping_subscript); + o3->enable(python::type_object_base::mapping_length); + + assert(python::pod_instance_counter == 4); + assert(o2->tp_as_number && o2->tp_as_mapping && !o2->tp_as_sequence); + assert(o3->tp_as_number && o3->tp_as_mapping && o3->tp_as_sequence); + assert(o2->tp_as_mapping == o3->tp_as_mapping); + + python::type_object_base *o4 = new TestTypeObject; + + assert(python::pod_instance_counter == 4); + + o4->enable(python::type_object_base::number_add); + + assert(python::pod_instance_counter == 4); + assert(o4->tp_as_number && !o4->tp_as_mapping && !o4->tp_as_sequence); + assert(o4->tp_as_number == o3->tp_as_number); + + delete o3; + + assert(python::pod_instance_counter == 3); + assert(o1->tp_as_number && !o1->tp_as_mapping && !o1->tp_as_sequence); + assert(o2->tp_as_number && o2->tp_as_mapping && !o2->tp_as_sequence); + assert(o4->tp_as_number && !o4->tp_as_mapping && !o4->tp_as_sequence); + assert(o4->tp_as_number == o2->tp_as_number); + + o3 = new TestTypeObject; + + assert(python::pod_instance_counter == 3); + + o3->enable(python::type_object_base::number_add); + o3->enable(python::type_object_base::sequence_length); + + assert(python::pod_instance_counter == 4); + assert(o3->tp_as_number && !o3->tp_as_mapping && o3->tp_as_sequence); + assert(o1->tp_as_number != o3->tp_as_number); + assert(o2->tp_as_number == o3->tp_as_number); + + delete o1; + + assert(python::pod_instance_counter == 3); + + delete o4; + + assert(python::pod_instance_counter == 3); + + delete o3; + + assert(python::pod_instance_counter == 2); + + delete o2; + + assert(python::pod_instance_counter == 0); + + assert(python::detail::shared_pod_manager::obj().m_storage.size() == 0); +} + +#endif + diff --git a/newtypes.h b/newtypes.h new file mode 100644 index 00000000..423e4533 --- /dev/null +++ b/newtypes.h @@ -0,0 +1,389 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef TYPES_DWA051800_H_ +# define TYPES_DWA051800_H_ + +// Usage: +// class X : public +// python::callable< +// python::getattrable < +// python::setattrable > > +// { +// public: +// ref call(args, kw); +// ref getattr(args, kw); +// ref setattr(args, kw); +// }; + +# include "pyconfig.h" +# include "signatures.h" // really just for type<> +# include "cast.h" +# include "base_object.h" +# include +# include +# include + +namespace python { + +class string; + +namespace detail { + +class instance_holder_base; + +class type_object_base : public python_type +{ + public: + explicit type_object_base(PyTypeObject* type_type); + virtual ~type_object_base(); + + public: + enum capability { + hash, call, str, getattr, setattr, compare, repr, + + mapping_length, mapping_subscript, mapping_ass_subscript, + + sequence_length, sequence_item, sequence_ass_item, + sequence_concat, sequence_repeat, sequence_slice, sequence_ass_slice, + + number_add, number_subtract, number_multiply, number_divide, + number_remainder, number_divmod, number_power, number_negative, + number_positive, number_absolute, number_nonzero, number_invert, + number_lshift, number_rshift, number_and, number_xor, number_or, + number_coerce, number_int, number_long, number_float, number_oct, + number_hex + }; + + void enable(capability); + + // + // type behaviors + // + public: // Callbacks for basic type functionality. + virtual PyObject* instance_repr(PyObject*) const; + virtual int instance_compare(PyObject*, PyObject* other) const; + virtual PyObject* instance_str(PyObject*) const; + virtual long instance_hash(PyObject*) const; + virtual PyObject* instance_call(PyObject* obj, PyObject* args, PyObject* kw) const; + virtual PyObject* instance_getattr(PyObject* obj, const char* name) const; + virtual int instance_setattr(PyObject* obj, const char* name, PyObject* value) const; + + // Dealloc is a special case, since every type needs a nonzero tp_dealloc slot. + virtual void instance_dealloc(PyObject*) const = 0; + + public: // Callbacks for mapping methods + virtual int instance_mapping_length(PyObject*) const; + virtual PyObject* instance_mapping_subscript(PyObject*, PyObject*) const ; + virtual int instance_mapping_ass_subscript(PyObject*, PyObject*, PyObject*) const; + + public: // Callbacks for sequence methods + virtual int instance_sequence_length(PyObject* obj) const; + virtual PyObject* instance_sequence_concat(PyObject* obj, PyObject* other) const; + virtual PyObject* instance_sequence_repeat(PyObject* obj, int n) const; + virtual PyObject* instance_sequence_item(PyObject* obj, int n) const; + virtual PyObject* instance_sequence_slice(PyObject* obj, int start, int finish) const; + virtual int instance_sequence_ass_item(PyObject* obj, int n, PyObject* value) const; + virtual int instance_sequence_ass_slice(PyObject* obj, int start, int finish, PyObject* value) const; + + public: // Callbacks for number methods + virtual PyObject* instance_number_add(PyObject*, PyObject*) const; + virtual PyObject* instance_number_subtract(PyObject*, PyObject*) const; + virtual PyObject* instance_number_multiply(PyObject*, PyObject*) const; + virtual PyObject* instance_number_divide(PyObject*, PyObject*) const; + virtual PyObject* instance_number_remainder(PyObject*, PyObject*) const; + virtual PyObject* instance_number_divmod(PyObject*, PyObject*) const; + virtual PyObject* instance_number_power(PyObject*, PyObject*, PyObject*) const; + virtual PyObject* instance_number_negative(PyObject*) const; + virtual PyObject* instance_number_positive(PyObject*) const; + virtual PyObject* instance_number_absolute(PyObject*) const; + virtual int instance_number_nonzero(PyObject*) const; + virtual PyObject* instance_number_invert(PyObject*) const; + virtual PyObject* instance_number_lshift(PyObject*, PyObject*) const; + virtual PyObject* instance_number_rshift(PyObject*, PyObject*) const; + virtual PyObject* instance_number_and(PyObject*, PyObject*) const; + virtual PyObject* instance_number_xor(PyObject*, PyObject*) const; + virtual PyObject* instance_number_or(PyObject*, PyObject*) const; + virtual int instance_number_coerce(PyObject*, PyObject**, PyObject**) const; + virtual PyObject* instance_number_int(PyObject*) const; + virtual PyObject* instance_number_long(PyObject*) const; + virtual PyObject* instance_number_float(PyObject*) const; + virtual PyObject* instance_number_oct(PyObject*) const; + virtual PyObject* instance_number_hex(PyObject*) const; +}; + +template +class type_object : public type_object_base +{ + public: + typedef T instance; + + type_object(PyTypeObject* type_type, const char* name) + : type_object_base(type_type) + { + assert(name != 0); + this->tp_name = const_cast(name); + } + + type_object(PyTypeObject* type_type) + : type_object_base(type_type) + { + this->tp_name = const_cast(typeid(instance).name()); + } + + private: // Overridable behaviors. + // Called when the reference count goes to zero. The default implementation + // is "delete p". If you have not allocated your object with operator new or + // you have other constraints, you'll need to override this + virtual void dealloc(T* p) const; + + private: // Implementation of type_object_base hooks. Do not reimplement in derived classes. + void instance_dealloc(PyObject*) const; +}; + +// +// type objects +// +template +class callable : public Base +{ + public: + typedef callable properties; // Convenience for derived class construction + typedef typename Base::instance instance; + callable(PyTypeObject* type_type, const char* name); + callable(PyTypeObject* type_type); + private: + PyObject* instance_call(PyObject* obj, PyObject* args, PyObject* kw) const; +}; + +template +class getattrable : public Base +{ + public: + typedef getattrable properties; // Convenience for derived class construction + typedef typename Base::instance instance; + getattrable(PyTypeObject* type_type, const char* name); + getattrable(PyTypeObject* type_type); + private: + PyObject* instance_getattr(PyObject* obj, const char* name) const; +}; + +template +class setattrable : public Base +{ + public: + typedef setattrable properties; // Convenience for derived class construction + typedef typename Base::instance instance; + setattrable(PyTypeObject* type_type, const char* name); + setattrable(PyTypeObject* type_type); + private: + int instance_setattr(PyObject* obj, const char* name, PyObject* value) const; +}; + +template +class reprable : public Base +{ + public: + typedef reprable properties; // Convenience for derived class construction + typedef typename Base::instance instance; + reprable(PyTypeObject* type_type, const char* name); + reprable(PyTypeObject* type_type); + private: + PyObject* instance_repr(PyObject* obj) const; +}; + +// +// Member function definitions +// + +// type_object<> +template +void type_object::instance_dealloc(PyObject* obj) const +{ + this->dealloc(downcast(obj).get()); +} + +template +void type_object::dealloc(T* obj) const +{ + delete obj; +} + +// callable +template +callable::callable(PyTypeObject* type_type, const char* name) + : Base(type_type, name) +{ + this->enable(call); +} + +template +callable::callable(PyTypeObject* type_type) + : Base(type_type) +{ + this->enable(call); +} + +template +PyObject* callable::instance_call(PyObject* obj, PyObject* args, PyObject* kw) const +{ + return downcast(obj)->call(args, kw); +} + +// getattrable +template +getattrable::getattrable(PyTypeObject* type_type, const char* name) + : Base(type_type, name) +{ + this->enable(getattr); +} + +template +getattrable::getattrable(PyTypeObject* type_type) + : Base(type_type) +{ + this->enable(getattr); +} + +template +PyObject* getattrable::instance_getattr(PyObject* obj, const char* name) const +{ + return downcast(obj)->getattr(name); +} + +// setattrable +template +setattrable::setattrable(PyTypeObject* type_type, const char* name) + : Base(type_type, name) +{ + this->enable(setattr); +} + +template +setattrable::setattrable(PyTypeObject* type_type) + : Base(type_type) +{ + this->enable(setattr); +} + +template +int setattrable::instance_setattr(PyObject* obj, const char* name, PyObject* value) const +{ + return downcast(obj)->setattr(name, value); +} + +// reprable +template +reprable::reprable(PyTypeObject* type_type, const char* name) + : Base(type_type, name) +{ + this->enable(repr); +} + +template +reprable::reprable(PyTypeObject* type_type) + : Base(type_type) +{ + this->enable(repr); +} + +template +PyObject* reprable::instance_repr(PyObject* obj) const +{ + return downcast(obj)->repr(); +} + + // Helper class for optimized allocation of PODs: If two PODs + // happen to contain identical byte patterns, they may share their + // memory. Reference counting is used to free unused memory. + // This is useful because method tables of related extension classes tend + // to be identical, so less memory is needed for them. + class shared_pod_manager + { + typedef std::pair holder; + typedef std::vector storage; + + public: + static shared_pod_manager& obj(); + ~shared_pod_manager(); + + // Allocate memory for POD T and fill it with zeros. + // This memory is initially not shared. + template + static void create(T*& t) + { + t = reinterpret_cast(obj().create(sizeof(T))); + } + + // Decrement the refcount for the memory t points to. If the count + // goes to zero, the memory is freed. + template + static void dispose(T* t) + { + obj().dec_ref(t, sizeof(T)); + } + + // Attempt to share the memory t points to. If memory with the same + // contents already exists, t is replaced by a pointer to this memory, + // and t's old memory is disposed. Otherwise, t will be registered for + // potential future sharing. + template + static void replace_if_equal(T*& t) + { + t = reinterpret_cast(obj().replace_if_equal(t, sizeof(T))); + } + + // Create a copy of t's memory that is guaranteed to be private to t. + // Afterwards t points to the new memory, unless it was already private, in + // which case there is no change (except that t's memory will no longer + // be considered for future sharing - see raplade_if_equal()) + // This function *must* be called before the contents of (*t) can + // be overwritten. Otherwise, inconsistencies and crashes may result. + template + static void make_unique_copy(T*& t) + { + t = reinterpret_cast(obj().make_unique_copy(t, sizeof(T))); + } + + private: + void* replace_if_equal(void* pod, std::size_t size); + void* make_unique_copy(void* pod, std::size_t size); + void* create(std::size_t size); + void dec_ref(void* pod, std::size_t size); + void erase_from_list(void* pod); + + struct compare; + struct identical; + + private: + shared_pod_manager() {} // instance + +#ifdef TYPE_OBJECT_BASE_STANDALONE_TEST + public: +#endif + storage m_storage; + }; + + + void add_capability(type_object_base::capability capability, + PyTypeObject* dest); + +// This macro gets the length of an array as a compile-time constant, and will +// fail to compile if the parameter is a pointer. +# define PY_ARRAY_LENGTH(a) \ + (sizeof(::python::detail::countof_validate(a, &(a))) ? sizeof(a) / sizeof((a)[0]) : 0) + + template + inline void countof_validate(T* const, T* const*); + + template + inline int countof_validate(const void*, T); + +}} // namespace python::detail + +#endif // TYPES_DWA051800_H_ diff --git a/none.h b/none.h new file mode 100644 index 00000000..d13877f5 --- /dev/null +++ b/none.h @@ -0,0 +1,21 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef NONE_DWA_052000_H_ +# define NONE_DWA_052000_H_ + +# include "pyconfig.h" +# include "wrap_python.h" + +namespace python { namespace detail { + +inline PyObject* none() { Py_INCREF(Py_None); return Py_None; } + +}} // namespace python::detail + +#endif // NONE_DWA_052000_H_ diff --git a/objects.cpp b/objects.cpp new file mode 100644 index 00000000..bf4dc46e --- /dev/null +++ b/objects.cpp @@ -0,0 +1,485 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +// TODO: Move inline implementations from objects.cpp here + +#include "objects.h" +#include "none.h" + +namespace python { + +template +T object_from_python(PyObject* p, type) +{ + ref x(p, ref::increment_count); + if (!T::accepts(x)) + { + PyErr_SetString(PyExc_TypeError, p->ob_type->tp_name); + throw error_already_set(); + } + return T(x); +} + +inline PyObject* object_to_python(const object& x) +{ + return x.reference().release(); +} + +object::object(ref p) + : m_p(p) {} + +// Return a reference to the held object +ref object::reference() const +{ + return m_p; +} + +// Return a raw pointer to the held object +PyObject* object::get() const +{ + return m_p.get(); +} + +} // namespace python + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE + +PyObject* to_python(const python::tuple& x) +{ + return object_to_python(x); +} + +python::tuple from_python(PyObject* p, python::type type) +{ + return python::object_from_python(p, type); +} + +PyObject* to_python(const python::list& x) +{ + return object_to_python(x); +} + +python::list from_python(PyObject* p, python::type type) +{ + return python::object_from_python(p, type); +} + +PyObject* to_python(const python::dictionary& x) +{ + return object_to_python(x); +} + +python::dictionary from_python(PyObject* p, python::type type) +{ + return python::object_from_python(p, type); +} + +PyObject* to_python(const python::string& x) +{ + return object_to_python(x); +} + +python::string from_python(PyObject* p, python::type type) +{ + return python::object_from_python(p, type); +} + +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +namespace python { + +tuple::tuple(std::size_t n) + : object(ref(PyTuple_New(n))) +{ + for (std::size_t i = 0; i < n; ++i) + PyTuple_SET_ITEM(get(), i, detail::none()); +} + +tuple::tuple(ref p) + : object(p) +{ + assert(accepts(p)); + if (!accepts(p)) + { + PyErr_SetString(PyExc_TypeError, p->ob_type->tp_name); + throw error_already_set(); + } +} + +PyTypeObject* tuple::type_obj() +{ + return &PyTuple_Type; +} + +bool tuple::accepts(ref p) +{ + return PyTuple_Check(p.get()); +} + +std::size_t tuple::size() const +{ + return PyTuple_Size(get()); +} + +ref tuple::operator[](std::size_t pos) const +{ + return ref(PyTuple_GetItem(get(), static_cast(pos)), + ref::increment_count); +} + +void tuple::set_item(std::size_t pos, const ref& rhs) +{ + int failed = PyTuple_SetItem( + get(), static_cast(pos), ref(rhs).release()); // A reference is stolen here. + (void)failed; + assert(failed == 0); +} + +tuple tuple::slice(int low, int high) const +{ + return tuple(ref(PyTuple_GetSlice(get(), low, high))); +} + +tuple& tuple::operator+=(const tuple& rhs) +{ + return *this = *this + rhs; +} + + +// Construct from an owned PyObject*. +// Precondition: p must point to a python string. +string::string(ref p) + : object(p) +{ + assert(accepts(p)); + if (!accepts(p)) + { + PyErr_SetString(PyExc_TypeError, p->ob_type->tp_name); + throw error_already_set(); + } +} + +string::string(const char* s) + : object(ref(PyString_FromString(s))) {} + +string::string(const char* s, std::size_t length) + : object(ref(PyString_FromStringAndSize(s, length))) {} + +string::string(const char* s, interned_t) + : object(ref(PyString_InternFromString(s))) {} + +#if 0 +string::string(const char* s, std::size_t length, interned_t) + : object(ref(PyString_InternFromStringAndSize(s, length))) {} +#endif + +string::string(const string& rhs) + : object(rhs.reference()) {} + +// Get the type object for Strings +PyTypeObject* string::type_obj() +{ return &PyString_Type; } + +// Return true if the given object is a python string +bool string::accepts(ref o) +{ return PyString_Check(o.get()); } + +// Return the length of the string. +std::size_t string::size() const +{ + int size = PyString_GET_SIZE(get()); + assert(size >= 0); + return static_cast(size); +} + +// Returns a null-terminated representation of the contents of string. +// The pointer refers to the internal buffer of string, not a copy. +// The data must not be modified in any way. It must not be de-allocated. +const char* string::c_str() const +{ return PyString_AS_STRING(get()); } + +void string::intern() +{ // UNTESTED!! + *this = string(ref(PyString_InternFromString(c_str()), ref::increment_count)); +} + +string& string::operator*=(unsigned int repeat_count) +{ + *this = string(ref(PySequence_Repeat(get(), repeat_count))); + return *this; +} + +dictionary::dictionary(ref p) + : object(p) +{ + assert(accepts(p)); + if (!accepts(p)) + { + PyErr_SetString(PyExc_TypeError, p->ob_type->tp_name); + throw error_already_set(); + } +} + +dictionary::dictionary() + : object(ref(PyDict_New())) {} + +PyTypeObject* dictionary::type_obj() +{ return &PyDict_Type; } + +bool dictionary::accepts(ref p) +{ return PyDict_Check(p.get()); } + +void dictionary::clear() +{ PyDict_Clear(get()); } + +const ref& dictionary::proxy::operator=(const ref& rhs) +{ + if (PyDict_SetItem(m_dict.get(), m_key.get(), rhs.get()) == -1) + throw error_already_set(); + return rhs; +} + +dictionary::proxy::operator ref() const +{ + return ref(m_dict->ob_type->tp_as_mapping->mp_subscript(m_dict.get(), m_key.get()), + ref::increment_count); +} + +dictionary::proxy::proxy(const ref& dict, const ref& key) + : m_dict(dict), m_key(key) {} + +dictionary::proxy dictionary::operator[](ref key) +{ return proxy(reference(), key); } + +ref dictionary::operator[](ref key) const { + // An odd MSVC bug causes the ".operator Ptr()" to be needed + return proxy(reference(), key).operator ref(); +} + + +ref dictionary::get_item(const ref& key) const +{ + return get_item(key, ref()); +} + +ref dictionary::get_item(const ref& key, const ref& default_) const +{ + PyObject* value_or_null = PyDict_GetItem(get(), key.get()); + if (value_or_null == 0 && !PyErr_Occurred()) + return default_; + else + return ref(value_or_null, ref::increment_count); // Will throw if there was another error +} + +void dictionary::set_item(const ref& key, const ref& value) +{ + if (PyDict_SetItem(get(), key.get(), value.get()) == -1) + throw error_already_set(); +} + +void dictionary::erase(ref key) { + if (PyDict_DelItem(get(), key.get()) == -1) + throw error_already_set(); +} + +list dictionary::items() const { return list(ref(PyDict_Items(get()))); } +list dictionary::keys() const { return list(ref(PyDict_Keys(get()))); } +list dictionary::values() const { return list(ref(PyDict_Values(get()))); } + +std::size_t dictionary::size() const { return static_cast(PyDict_Size(get())); } + +string operator+(string x, string y) +{ + PyObject* io_string = x.reference().release(); + PyString_Concat(&io_string, y.get()); + return string(ref(io_string)); +} + +string& string::operator+=(const string& rhs) +{ + return *this = *this + rhs; +} + +string& string::operator+=(const char* y) +{ + return *this += string(y); +} + +string operator%(const string& format, const tuple& args) +{ + return string(ref(PyString_Format(format.get(), args.reference().get()))); +} + +string operator+(string x, const char* y) +{ + return x + string(y); +} + +string operator+(const char* x, string y) +{ + return string(x) + y; +} + +tuple operator+(const tuple& x, const tuple& y) +{ + tuple result(x.size() + y.size()); + for (std::size_t xi = 0; xi < x.size(); ++xi) + result.set_item(xi, x[xi]); + for (std::size_t yi = 0; yi < y.size(); ++yi) + result.set_item(yi + x.size(), y[yi]); + return result; +} + + +list::list(ref p) + : object(p) +{ + assert(accepts(p)); + if (!accepts(p)) + { + PyErr_SetString(PyExc_TypeError, p->ob_type->tp_name); + throw error_already_set(); + } +} + +list::list(std::size_t sz) + : object(ref(PyList_New(sz))) +{ +} + +PyTypeObject* list::type_obj() +{ + return &PyList_Type; +} + +bool list::accepts(ref p) +{ + return PyList_Check(p.get()); +} + +std::size_t list::size() +{ + return PyList_Size(get()); +} + +ref list::operator[](std::size_t pos) const +{ + return ref(PyList_GetItem(get(), pos), ref::increment_count); +} + +list::proxy list::operator[](std::size_t pos) +{ + return proxy(reference(), pos); +} + +void list::insert(std::size_t index, const ref& item) +{ + if (PyList_Insert(get(), index, item.get()) == -1) + throw error_already_set(); +} + +void list::push_back(const ref& item) +{ + if (PyList_Append(get(), item.get()) == -1) + throw error_already_set(); +} + +void list::append(const ref& item) +{ + this->push_back(item); +} + +list list::slice(int low, int high) const +{ + return list(ref(PyList_GetSlice(get(), low, high))); +} + +list::slice_proxy list::slice(int low, int high) +{ + return slice_proxy(reference(), low, high); +} + +void list::sort() +{ + if (PyList_Sort(get()) == -1) + throw error_already_set(); +} + +void list::reverse() +{ + if (PyList_Reverse(get()) == -1) + throw error_already_set(); +} + +tuple list::as_tuple() const +{ + return tuple(ref(PyList_AsTuple(get()))); +} + +const ref& list::proxy::operator=(const ref& rhs) +{ + m_list.set_item(m_index, rhs); + return rhs; +} + +list::proxy::operator ref() const +{ + return ref(PyList_GetItem(m_list.get(), m_index), ref::increment_count); +} + +ref list::get_item(std::size_t pos) const +{ + return ref(PyList_GetItem(this->get(), pos), ref::increment_count); +} + +void list::set_item(std::size_t pos, const ref& rhs) +{ + int result = PyList_SetItem(this->get(), pos, rhs.get()); + if (result == -1) + throw error_already_set(); + Py_INCREF(rhs.get()); +} + +list::proxy::proxy(const ref& list, std::size_t index) + : m_list(list), m_index(index) +{ +} + +const list& list::slice_proxy::operator=(const list& rhs) +{ + if (PyList_SetSlice(m_list.get(), m_low, m_high, rhs.get()) == -1) + throw error_already_set(); + return rhs; +} + +list::slice_proxy::operator ref() const +{ + return ref(PyList_GetSlice(m_list.get(), m_low, m_high)); +} + +list::slice_proxy::operator list() const +{ + return list(this->operator ref()); +} + +std::size_t list::slice_proxy::size() +{ + return this->operator list().size(); +} + +ref list::slice_proxy::operator[](std::size_t pos) const +{ + return this->operator list()[pos].operator ref(); +} + +list::slice_proxy::slice_proxy(const ref& list, int low, int high) + : m_list(list), m_low(low), m_high(high) +{ +} + +} diff --git a/objects.h b/objects.h new file mode 100644 index 00000000..31ac3284 --- /dev/null +++ b/objects.h @@ -0,0 +1,334 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef OBJECTS_DWA051100_H_ +# define OBJECTS_DWA051100_H_ + +# include "wrap_python.h" +# include "pyconfig.h" +# include "pyptr.h" +# include "boost/operators.hpp" +# include + +namespace python { + +class object +{ + public: + explicit object(ref p); + + // Return a reference to the held object + ref reference() const; + + // Return a raw pointer to the held object + PyObject* get() const; + + private: + ref m_p; +}; + +class tuple : public object +{ + public: + explicit tuple(std::size_t n = 0); + explicit tuple(ref p); + + template + tuple(const std::pair& x) + : object(ref(PyTuple_New(2))) + { + set_item(0, x.first); + set_item(1, x.second); + } + + template + tuple(const First& first, const Second& second) + : object(ref(PyTuple_New(2))) + { + set_item(0, first); + set_item(1, second); + } + + template + tuple(const First& first, const Second& second, const Third& third) + : object(ref(PyTuple_New(3))) + { + set_item(0, first); + set_item(1, second); + set_item(2, third); + } + + template + tuple(const First& first, const Second& second, const Third& third, const Fourth& fourth) + : object(ref(PyTuple_New(4))) + { + set_item(0, first); + set_item(1, second); + set_item(2, third); + set_item(3, fourth); + } + + static PyTypeObject* type_obj(); + static bool accepts(ref p); + std::size_t size() const; + ref operator[](std::size_t pos) const; + + template + void set_item(std::size_t pos, const T& rhs) + { + this->set_item(pos, make_ref(rhs)); + } + + void set_item(std::size_t pos, const ref& rhs); + + tuple slice(int low, int high) const; + + friend tuple operator+(const tuple&, const tuple&); + tuple& operator+=(const tuple& rhs); +}; + +class list : public object +{ + struct proxy; + struct slice_proxy; + public: + explicit list(ref p); + explicit list(std::size_t sz = 0); + static PyTypeObject* type_obj(); + static bool accepts(ref p); + std::size_t size(); + ref operator[](std::size_t pos) const; + proxy operator[](std::size_t pos); + ref get_item(std::size_t pos) const; + + template + void set_item(std::size_t pos, const T& x) + { this->set_item(pos, make_ref(x)); } + void set_item(std::size_t pos, const ref& ); + +// void set_item(std::size_t pos, const object& ); + + template + void insert(std::size_t index, const T& x) + { this->insert(index, make_ref(x)); } + void insert(std::size_t index, const ref& item); + + template + void push_back(const T& item) + { this->push_back(make_ref(item)); } + void push_back(const ref& item); + + template + void append(const T& item) + { this->append(make_ref(item)); } + void append(const ref& item); + + list slice(int low, int high) const; + slice_proxy slice(int low, int high); + void sort(); + void reverse(); + tuple as_tuple() const; +}; + +class string + : public object, public boost::multipliable2 +{ + public: + // Construct from an owned PyObject*. + // Precondition: p must point to a python string. + explicit string(ref p); + explicit string(const char* s); + string(const char* s, std::size_t length); + string(const string& rhs); + + enum interned_t { interned }; + string(const char* s, interned_t); + + // Get the type object for Strings + static PyTypeObject* type_obj(); + + // Return true if the given object is a python string + static bool accepts(ref o); + + // Return the length of the string. + std::size_t size() const; + + // Returns a null-terminated representation of the contents of string. + // The pointer refers to the internal buffer of string, not a copy. + // The data must not be modified in any way. It must not be de-allocated. + const char* c_str() const; + + string& operator*=(unsigned int repeat_count); + string& operator+=(const string& rhs); + friend string operator+(string x, string y); + string& operator+=(const char* rhs); + friend string operator+(string x, const char* y); + friend string operator+(const char* x, string y); + + void intern(); + + friend string operator%(const string& format, const tuple& args); +}; + +class dictionary : public object +{ + private: + struct proxy; + + public: + explicit dictionary(ref p); + dictionary(); + void clear(); + + static PyTypeObject* type_obj(); + static bool accepts(ref p); + + public: + template + proxy operator[](const Key& key) + { return this->operator[](make_ref(key)); } + proxy operator[](ref key); + + template + ref operator[](const Key& key) const + { return this->operator[](make_ref(key)); } + ref operator[](ref key) const; + + template + ref get_item(const Key& key) const + { return this->get_item(make_ref(key)); } + ref get_item(const ref& key) const; + + template + ref get_item(const Key& key, const Default& default_) const + { return this->get_item(make_ref(key), make_ref(default_)); } + ref get_item(const ref& key, const ref& default_) const; + + template + void set_item(const Key& key, const Value& value) + { this->set_item(make_ref(key), make_ref(value)); } + void set_item(const ref& key, const ref& value); + + template + void erase(const Key& key) + { this->erase(make_ref(key)); } + void erase(ref key); + +// proxy operator[](const object& key); +// ref operator[](const object& key) const; + +// ref get_item(const object& key, ref default_ = ref()) const; +// void set_item(const object& key, const ref& value); + +// void erase(const object& key); + + list items() const; + list keys() const; + list values() const; + + std::size_t size() const; + // TODO: iterator support +}; + +struct dictionary::proxy +{ + template + const ref& operator=(const T& rhs) + { return (*this) = make_ref(rhs); } + const ref& operator=(const ref& rhs); + + operator ref() const; + private: + friend class dictionary; + proxy(const ref& dict, const ref& key); + + // This is needed to work around the very strange MSVC error report that the + // return type of the built-in operator= differs from that of the ones + // defined above. Couldn't hurt to make these un-assignable anyway, though. + const ref& operator=(const proxy&); // Not actually implemented + private: + ref m_dict; + ref m_key; +}; + +struct list::proxy +{ + template + const ref& operator=(const T& rhs) + { return (*this) = make_ref(rhs); } + const ref& operator=(const ref& rhs); + + operator ref() const; + + private: + friend class list; + proxy(const ref& list, std::size_t index); + + // This is needed to work around the very strange MSVC error report that the + // return type of the built-in operator= differs from that of the ones + // defined above. Couldn't hurt to make these un-assignable anyway, though. + const ref& operator=(const proxy&); // Not actually implemented + private: + list m_list; + std::size_t m_index; +}; + +struct list::slice_proxy +{ + const list& operator=(const list& rhs); + operator ref() const; + operator list() const; + std::size_t size(); + ref operator[](std::size_t pos) const; + private: + friend class list; + slice_proxy(const ref& list, int low, int high); + private: + ref m_list; + int m_low, m_high; +}; + +} // namespace python + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE + +PyObject* to_python(const python::tuple&); +python::tuple from_python(PyObject* p, python::type); + +inline python::tuple from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +PyObject* to_python(const python::list&); +python::list from_python(PyObject* p, python::type); + +inline python::list from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +PyObject* to_python(const python::string&); +python::string from_python(PyObject* p, python::type); + +inline python::string from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +PyObject* to_python(const python::dictionary&); +python::dictionary from_python(PyObject* p, python::type); + +inline python::dictionary from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +#endif // OBJECTS_DWA051100_H_ diff --git a/operators.h b/operators.h new file mode 100644 index 00000000..95528bc8 --- /dev/null +++ b/operators.h @@ -0,0 +1,504 @@ +#ifndef OPERATORS_UK112000_H_ +#define OPERATORS_UK112000_H_ + +#include "functions.h" +#if !defined(__GNUC__) || defined(__SGI_STL_PORT) +# include +#else +# include +#endif + +namespace python { + +namespace detail { + + // helper class for automatic operand type detection + // during operator wrapping. + struct auto_operand {}; +} + +// Define operator ids that can be or'ed together +// (python::op_add | python::op_sub | python::op_mul). +// This allows to wrap several operators in one line. +enum operator_id +{ + op_add = 0x1, + op_sub = 0x2, + op_mul = 0x4, + op_div = 0x8, + op_mod = 0x10, + op_divmod =0x20, + op_pow = 0x40, + op_lshift = 0x80, + op_rshift = 0x100, + op_and = 0x200, + op_xor = 0x400, + op_or = 0x800, + op_neg = 0x1000, + op_pos = 0x2000, + op_abs = 0x4000, + op_invert = 0x8000, + op_int = 0x10000, + op_long = 0x20000, + op_float = 0x40000, + op_str = 0x80000, + op_cmp = 0x100000 +}; + +// Wrap the operators given by "which". Usage: +// foo_class.def(python::operators<(python::op_add | python::op_sub)>()); +template +struct operators {}; + +// Wrap heterogeneous operators with given left operand type. Usage: +// foo_class.def(python::operators<(python::op_add | python::op_sub)>(), +// python::left_operand()); +template +struct left_operand {}; + +// Wrap heterogeneous operators with given right operand type. Usage: +// foo_class.def(python::operators<(python::op_add | python::op_sub)>(), +// python::right_operand()); +template +struct right_operand {}; + +namespace detail +{ + template + struct operand_select + { + template + struct wrapped + { + typedef Specified type; + }; + }; + + template <> + struct operand_select + { + template + struct wrapped + { + typedef const wrapped_type& type; + }; + }; + + template struct define_operator; + + // Base class which grants access to extension_class_base::add_method() to its derived classes + struct add_operator_base + { + protected: + static inline void add_method(extension_class_base* target, function* method, const char* name) + { target->add_method(method, name); } + }; + +// +// choose_op, choose_unary_op, and choose_rop +// +// These templates use "poor man's partial specialization" to generate the +// appropriate add_method() call (if any) for a given operator and argument set. +// +// Usage: +// choose_op<(which & op_add)>::template args::add(ext_class); +// +// (see extension_class<>::def_operators() for more examples). +// + template + struct choose_op + { + template + struct args : add_operator_base + { + static inline void add(extension_class_base* target) + { + typedef define_operator def_op; + add_method(target, + new typename def_op::template operator_function(), + def_op::name()); + } + + }; + }; + + // specialization for 0 has no effect + template <> + struct choose_op<0> + { + template + struct args + { + static inline void add(extension_class_base*) + { + } + + }; + }; + + template + struct choose_unary_op + { + template + struct args : add_operator_base + { + static inline void add(extension_class_base* target) + { + typedef define_operator def_op; + add_method(target, + new typename def_op::template operator_function(), + def_op::name()); + } + + }; + }; + + // specialization for 0 has no effect + template <> + struct choose_unary_op<0> + { + template + struct args + { + static inline void add(extension_class_base*) + { + } + + }; + }; + + template + struct choose_rop + { + template + struct args : add_operator_base + { + static inline void add(extension_class_base* target) + { + typedef define_operator def_op; + add_method(target, + new typename def_op::template roperator_function(), + def_op::rname()); + } + + }; + }; + + // specialization for 0 has no effect + template <> + struct choose_rop<0> + { + template + struct args + { + static inline void add(extension_class_base*) + { + } + + }; + }; + + +// Fully specialize define_operator for all operators defined in operator_id above. +// Every specialization defines one function object for normal operator calls and one +// for operator calls with operands reversed ("__r*__" function variants). +// Specializations for most operators follow a standard pattern: execute the expression +// that uses the operator in question. This standard pattern is realized by the following +// macros so that the actual specialization can be done by just calling a macro. +#define PY_DEFINE_BINARY_OPERATORS(id, oper) \ + template <> \ + struct define_operator \ + { \ + template \ + struct operator_function : function \ + { \ + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const \ + { \ + tuple args(ref(arguments, ref::increment_count)); \ + \ + return BOOST_PYTHON_CONVERSION::to_python( \ + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()) oper \ + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type())); \ + } \ + \ + const char* description() const \ + { return "__" #id "__"; } \ + }; \ + \ + template \ + struct roperator_function : function \ + { \ + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const \ + { \ + tuple args(ref(arguments, ref::increment_count)); \ + \ + return BOOST_PYTHON_CONVERSION::to_python( \ + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()) oper \ + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type())); \ + } \ + \ + const char* description() const \ + { return "__r" #id "__"; } \ + \ + }; \ + \ + static const char * name() { return "__" #id "__"; } \ + static const char * rname() { return "__r" #id "__"; } \ + } + +#define PY_DEFINE_UNARY_OPERATORS(id, oper) \ + template <> \ + struct define_operator \ + { \ + template \ + struct operator_function : function \ + { \ + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const \ + { \ + tuple args(ref(arguments, ref::increment_count)); \ + \ + return BOOST_PYTHON_CONVERSION::to_python( \ + oper(BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()))); \ + } \ + \ + const char* description() const \ + { return "__" #id "__"; } \ + }; \ + \ + static const char * name() { return "__" #id "__"; } \ + } + + PY_DEFINE_BINARY_OPERATORS(add, +); + PY_DEFINE_BINARY_OPERATORS(sub, -); + PY_DEFINE_BINARY_OPERATORS(mul, *); + PY_DEFINE_BINARY_OPERATORS(div, /); + PY_DEFINE_BINARY_OPERATORS(mod, %); + PY_DEFINE_BINARY_OPERATORS(lshift, <<); + PY_DEFINE_BINARY_OPERATORS(rshift, >>); + PY_DEFINE_BINARY_OPERATORS(and, &); + PY_DEFINE_BINARY_OPERATORS(xor, ^); + PY_DEFINE_BINARY_OPERATORS(or, |); + + PY_DEFINE_UNARY_OPERATORS(neg, -); + PY_DEFINE_UNARY_OPERATORS(pos, +); + PY_DEFINE_UNARY_OPERATORS(abs, abs); + PY_DEFINE_UNARY_OPERATORS(invert, ~); + PY_DEFINE_UNARY_OPERATORS(int, long); + PY_DEFINE_UNARY_OPERATORS(long, PyLong_FromLong); + PY_DEFINE_UNARY_OPERATORS(float, double); + +#undef PY_DEFINE_BINARY_OPERATORS +#undef PY_DEFINE_UNARY_OPERATORS + +// Some operators need special treatment, e.g. because there is no corresponding +// expression in C++. These are specialized manually. + +// pow(): Manual specialization needed because an error message is required if this +// function is called with three arguments. The "power modulo" operator is not +// supported by define_operator, but can be wrapped manually (see special.html). + template <> + struct define_operator + { + template + struct operator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + + if (args.size() == 3 && args[2]->ob_type != Py_None->ob_type) + { + PyErr_SetString(PyExc_TypeError, "expected 2 arguments, got 3"); + throw argument_error(); + } + + return BOOST_PYTHON_CONVERSION::to_python( + pow(BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()), + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()))); + } + + const char* description() const + { return "__pow__"; } + + }; + + template + struct roperator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + + if (args.size() == 3 && args[2]->ob_type != Py_None->ob_type) + { + PyErr_SetString(PyExc_TypeError, "bad operand type(s) for pow()"); + throw argument_error(); + } + + return BOOST_PYTHON_CONVERSION::to_python( + pow(BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()), + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()))); + } + + const char* description() const + { return "__rpow__"; } + + }; + + static const char * name() { return "__pow__"; } + static const char * rname() { return "__rpow__"; } + }; + +// divmod(): Manual specialization needed because we must actually call two operators and +// return a tuple containing both results + template <> + struct define_operator + { + template + struct operator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + PyObject * res = PyTuple_New(2); + + PyTuple_SET_ITEM(res, 0, + BOOST_PYTHON_CONVERSION::to_python( + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()) / + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()))); + PyTuple_SET_ITEM(res, 1, + BOOST_PYTHON_CONVERSION::to_python( + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()) % + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()))); + + return res; + } + + const char* description() const + { return "__divmod__"; } + + }; + + template + struct roperator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + PyObject * res = PyTuple_New(2); + + PyTuple_SET_ITEM(res, 0, + BOOST_PYTHON_CONVERSION::to_python( + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()) / + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()))); + PyTuple_SET_ITEM(res, 1, + BOOST_PYTHON_CONVERSION::to_python( + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()) % + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()))); + + return res; + } + + const char* description() const + { return "__rdivmod__"; } + + }; + + static const char * name() { return "__divmod__"; } + static const char * rname() { return "__rdivmod__"; } + }; + +// cmp(): Manual specialization needed because there is no three-way compare in C++. +// It is implemented by two one-way comparisons with operators reversed in the second. + template <> + struct define_operator + { + template + struct operator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + + return BOOST_PYTHON_CONVERSION::to_python( + (BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()) < + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type())) ? + - 1 : + (BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()) < + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type())) ? + 1 : + 0) ; + } + + const char* description() const + { return "__cmp__"; } + + }; + + template + struct roperator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + + return BOOST_PYTHON_CONVERSION::to_python( + (BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type()) < + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type())) ? + - 1 : + (BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()) < + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), python::type())) ? + 1 : + 0) ; + } + + const char* description() const + { return "__rcmp__"; } + + }; + + static const char * name() { return "__cmp__"; } + static const char * rname() { return "__rcmp__"; } + }; + +// str(): Manual specialization needed because the string conversion does not follow +// the standard pattern relized by the macros. + template <> + struct define_operator + { + template + struct operator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject*) const + { + tuple args(ref(arguments, ref::increment_count)); + +#if !defined(__GNUC__) || defined(__SGI_STL_PORT) + std::ostringstream s; + s << BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()); +#else + std::ostrstream s; + s << BOOST_PYTHON_CONVERSION::from_python(args[0].get(), python::type()) << char(); +#endif + +#if !defined(__GNUC__) || defined(__SGI_STL_PORT) + return BOOST_PYTHON_CONVERSION::to_python(s.str()); +#else + return BOOST_PYTHON_CONVERSION::to_python(const_cast(s.str())); +#endif + } + + const char* description() const + { return "__str__"; } + + }; + + static const char * name() { return "__str__"; } + }; + + +} // namespace detail + +} // namespace python + +#endif /* OPERATORS_UK112000_H_ */ diff --git a/overloading.html b/overloading.html new file mode 100644 index 00000000..677052c8 --- /dev/null +++ b/overloading.html @@ -0,0 +1,156 @@ + + + Function Overloading + +
+

+ c++boost.gif (8819 bytes)Function Overloading +

+ +

An Example

+

+ To expose overloaded functions in Python, simply def() each + one with the same Python name: +

+
+inline int f1() { return 3; }
+inline int f2(int x) { return x + 1; }
+
+class X {
+public:
+    X() : m_value(0) {}
+    X(int n) : m_value(n) {}
+    int value() const { return m_value; }
+    void value(int v) { m_value = v; }
+private:
+    int m_value;
+};
+  ...
+
+void initoverload_demo()
+{
+    try
+    {
+        python::module_builder overload_demo("overload_demo");
+        // Overloaded functions at module scope
+        overload_demo.def(f1, "f");
+        overload_demo.def(f2, "f");
+
+        python::class_builder<X> x_class(overload_demo, "X");
+        // Overloaded constructors
+        x_class.def(python::constructor<>());
+        x_class.def(python::constructor<int>());
+
+        // Overloaded member functions
+        x_class.def((int (X::*)() const)&X::value, "value");
+        x_class.def((void (X::*)(int))&X::value, "value");
+  ...
+
+
+ +

+ Now in Python: +

+
+>>> from overload_demo import *
+>>> x0 = X()
+>>> x1 = X(1)
+>>> x0.value()
+0
+>>> x1.value()
+1
+>>> x0.value(3)
+>>> x0.value()
+3
+>>> X('hello')
+TypeError: No overloaded functions match (X, string). Candidates are:
+void (*)()
+void (*)(int)
+>>> f()
+3
+>>> f(4)
+5
+
+
+ +

Discussion

+

+ Notice that overloading in the Python module was produced three ways:

    +
  1. by combining the non-overloaded C++ functions int f1() + and int f2(int) and exposing them as f in Python. +
  2. by exposing the overloaded constructors of class X +
  3. by exposing the overloaded member functions X::value. +
+

+ Techniques 1. and 3. above are really alternatives. In case 3, you need + to form a pointer to each of the overloaded functions. The casting + syntax shown above is one way to do that in C++. Case 1 does not require + complicated-looking casts, but may not be viable if you can't change + your C++ interface. N.B. There's really nothing unsafe about casting an + overloaded (member) function address this way: the compiler won't let + you write it at all unless you get it right. + +

An Alternative to Casting

+

+ This approach is not neccessarily better, but may be preferable for some + people who have trouble writing out the types of (member) function + pointers or simply prefer to avoid all casts as a matter of principle: +

+
+// Forwarding functions for X::value
+inline void set_x_value(X& self, int v) { self.value(v); }
+inline int get_x_value(X& self) { return self.value(); }
+   ...
+        // Overloaded member functions
+        x_class.def(set_x_value, "value");
+        x_class.def(get_x_value, "value");
+
+
+

Here we are taking advantage of the ability to expose C++ functions at +namespace scope as Python member functions. + +

Overload Resolution

+

+ The function overload resolution mechanism in py_cpp works as + follows: + +

    + +
  • Attribute lookup for extension classes proceeds in the + usual Python way using a depth-first, left-to-right search. When a + class is found which has a matching attribute, only functions overloaded + in the context of that class are candidates for overload resolution. In + this sense, overload resolution mirrors the C++ mechanism, where a name + in a derived class “hides” all functions with the same name from a base + class. +

    + +

  • Within a name-space context (extension class or module), overloaded + functions are tried in the same order they were + def()ed. The first function whose signature can be made to + match each argument passed is the one which is ultimately called. + This means in particular that you cannot overload the same function on + both “int” and “float” because Python + automatically converts either of the two types into the other one. + If the “float” overload is found first, it is used + also used for arguments of type “int” as well, and the + “int” version of the function is never invoked. +
+ +

+ Prev: Overridable Virtual Functions + Next: Special Method Names + 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: Nov 21, 2000 +

+ diff --git a/overriding.html b/overriding.html new file mode 100644 index 00000000..2a1ba70b --- /dev/null +++ b/overriding.html @@ -0,0 +1,195 @@ + + + + Overridable Virtual Functions + + c++boost.gif (8819 bytes) + +

Overridable Virtual Functions

+ +

+ In the previous example we exposed a simple + C++ class in Python and showed that we could write a subclass. We even + redefined one of the functions in our derived class. Now we will learn + how to make the function behave virtually when called from C++. + + +

Example

+ +

In this example, it is assumed that world::get() is a virtual +member function: + +

+class world
+{
+ public:
+    world(int);
+    virtual ~world();
+    virtual std::string get() const { return "hi, world"; }
+};
+
+ +

+ We'll need a derived class* to help us + dispatch the call to Python. In our derived class, we need the following + elements: + +

    + +
  1. A PyObject* data member that holds a + reference to the corresponding Python object. + +
  2. A constructor for each exposed constructor of the + base class which stores an additional initial PyObject* argument + in the data member described above. + +
  3. An implementation of each virtual function you may + wish to override in Python which uses + python::callback<return-type>::call_method() to call + the Python override. + +
  4. For each non-pure virtual function meant to be + overridable from Python, a static member function (or a free function) taking + a reference or pointer to the base type as the first parameter and which + forwards any additional parameters neccessary to the default + implementation of the virtual function. See also this + note if the base class virtual function is private. + +
+ +
+struct world_callback : world
+{
+    world_callback(PyObject* self, int x) // 2
+        : world(x),
+          m_self(self) {}
+
+    std::string get() const // 3
+        { return python::callback<std::string>::call_method(m_self, "get"); }
+
+    static std::string default_get(const hello::world& self) const // 4
+        { return self.world::get(); }
+ private:
+    PyObject* m_self; // 1
+};
+
+ +

+ Finally, we add world_callback to the + class_builder<> declaration in our module initialization + function, and when we define the function, we must tell py_cpp about the default + implementation: + +

+// Create the Python type object for our extension class
+python::class_builder<hello::world,world_callback> world_class(hello, "world");
+// Add a virtual member function
+world_class.def(&world::get, "get", &world_callback::default_get);
+
+ +

+ Now our subclass of hello.world behaves as expected: + +

+>>> class my_subclass(hello.world):
+...     def get(self):
+...         return 'hello, world'
+...
+>>> hello.length(my_subclass())
+12
+
+ +

+ *You may ask, "Why do we need this derived + class? This could have been designed so that everything gets done right + inside of hello::world." One of the goals of py_cpp is to be + minimally intrusive on an existing C++ design. In principle, it should be + possible to expose the interface for a 3rd party library without changing + it. To unintrusively hook into the virtual functions so that a Python + override may be called, we must use a derived class. + +

Pure Virtual Functions

+ +

+ A pure virtual function with no implementation is actually a lot easier to + deal with than a virtual function with a default implementation. First of + all, you obviously don't need to supply + a default implementation. Secondly, you don't need to call + def() on the extension_class<> instance + for the virtual function. In fact, you wouldn't want to: if the + corresponding attribute on the Python class stays undefined, you'll get an + AttributeError in Python when you try to call the function, + indicating that it should have been implemented. For example: +

+
+struct baz {
+    virtual int pure(int) = 0;
+};
+
+struct baz_callback {
+    int pure(int x) { python::callback<int>::call_method(m_self, "pure", x); }
+};
+
+extern "C"
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+initfoobar()
+{
+    try
+    {
+       python::module_builder foobar("foobar");
+       python::class_builder<baz,baz_callback> baz_class("baz");
+       baz_class.def(&baz::pure, "pure");
+    }
+    catch(...)
+    {
+       python::handle_exception();    // Deal with the exception for Python
+    }
+}
+
+
+

+ Now in Python: +

+
+>>> from foobar import baz
+>>> x = baz()
+>>> x.pure(1)
+Traceback (innermost last):
+  File "<stdin>", line 1, in ?
+AttributeError: pure
+>>> class mumble(baz):
+...    def pure(self, x): return x + 1
+...
+>>> y = mumble()
+>>> y.pure(99)
+100
+
+ +

Private Non-Pure Virtual Functions

+ +

This is one area where some minor intrusiveness on the wrapped library is +required. Once it has been overridden, the only way to call the base class +implementation of a private virtual function is to make the derived class a +friend of the base class. You didn't hear it from me, but most C++ +implementations will allow you to change the declaration of the base class in +this limited way without breaking binary compatibility (though it will certainly +break the ODR). + +

+ Prev: A Simple Example Using py_cpp Next: Function Overloading 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 30, 2000 + diff --git a/pointers.html b/pointers.html new file mode 100644 index 00000000..fb133740 --- /dev/null +++ b/pointers.html @@ -0,0 +1,145 @@ + + + Pointers + +

+

+ c++boost.gif (8819 bytes)Pointers +

+ +

The Problem With Pointers

+ +

+In general, raw pointers passed to or returned from functions are problematic +for py_cpp because pointers have too many potential meanings. Is it an iterator? +A pointer to a single element? An array? When used as a return value, is the +caller expected to manage (delete) the pointed-to object or is the pointer +really just a reference? If the latter, what happens to Python references to the +referent when some C++ code deletes it? +

+There are a few cases in which pointers are converted automatically: +

    + +
  • Both const- and non-const pointers to wrapped class instances can be passed +to C++ functions. + +
  • Values of type const char* are interpreted as +null-terminated 'C' strings and when passed to or returned from C++ functions are +converted from/to Python strings. + +
+ +

Can you avoid the problem?

+ +

My first piece of advice to anyone with a case not covered above is +“find a way to avoid the problem.” For example, if you have just one +or two functions returning a pointer to a single (not an array of) const +T* for some wrapped T, you may be able to write a “thin +converting wrapper” over those two functions as follows (Since py_cpp +converts const T& values to_python by copying the T +into a new extension instance, Foo must have a public copy constructor): + +

+const Foo* f(); // original function
+const Foo& f_wrapper() { return *f(); }
+  ...
+my_module.def(f_wrapper, "f");
+
+ +

Dealing with the problem

+ +

The first step in handling the remaining cases is to figure out what the pointer +means. Several potential solutions are provided in the examples that follow: + +

Returning a pointer to a wrapped type

+ +

Returning a const pointer

+ +

If you have lots of functions returning a const T* for some +wrapped T, you may want to provide an automatic +to_python conversion function so you don't have to write lots of +thin wrappers. You can do this simply as follows: + +

+BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE // this is a gcc 2.95.2 bug workaround
+  PyObject* to_python(const Foo* p) {
+     return to_python(*p); // convert const Foo* in terms of const Foo&
+  }
+BOOST_PYTHON_END_CONVERSION_NAMESPACE
+
+ +

If you can't (afford to) copy the referent, or the pointer is non-const

+ +

If the wrapped type doesn't have a public copy constructor, if copying is +extremely costly (remember, we're dealing with Python here), or if the +pointer is non-const and you really need to be able to modify the referent from +Python, you can use the following dangerous trick. Why dangerous? Because python +can not control the lifetime of the referent, so it may be destroyed by your C++ +code before the last Python reference to it disappears: + +

+BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE // this is a gcc 2.95.2 bug workaround
+  PyObject* to_python(Foo* p)
+  {
+      return python::PyExtensionClassConverters::ptr_to_python(p);
+  }
+
+  PyObject* to_python(const Foo* p)
+  {
+      return to_python(const_cast(p));
+  }
+BOOST_PYTHON_END_CONVERSION_NAMESPACE
+
+ +This will cause the Foo* to be treated as though it were an owning smart +pointer, even though it's not. Be sure you don't use the reference for anything +from Python once the pointer becomes invalid, though. Don't worry too much about +the const_cast<> above: Const-correctness is completely lost +to Python anyway! + +

[In/]Out Parameters and Immutable Types

+ +

If you have an interface that uses non-const pointers (or references) as +in/out parameters to types which in Python are immutable (e.g. int, string), +there simply is no way to get the same interface in Python. You must +resort to transforming your interface with simple thin wrappers as shown below: +

+const void f(int* in_out_x); // original function
+const int f_wrapper(int in_x) { f(in_x); return in_x; }
+  ...
+my_module.def(f_wrapper, "f");
+
+ +

Of course, [in/]out parameters commonly occur only when there is already a +return value. You can handle this case by returning a Python tuple: +

+typedef unsigned ErrorCode;
+const char* f(int* in_out_x); // original function
+ ...
+#include <py_cpp/objects.h>
+const python::tuple f_wrapper(int in_x) { 
+  const char* s = f(in_x); 
+  return python::tuple(s, in_x);
+}
+  ...
+my_module.def(f_wrapper, "f");
+
+

Now, in Python: +

+>>> str,out_x = f(3)
+
+ + + +

+ © 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: Nov 6, 2000 +

+ diff --git a/py.cpp b/py.cpp new file mode 100644 index 00000000..1662b91f --- /dev/null +++ b/py.cpp @@ -0,0 +1,241 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "py.h" +#include +#include +#ifndef BOOST_NO_LIMITS +# include +#endif + +namespace python { + +// IMPORTANT: this function may only be called from within a catch block! +void handle_exception() +{ + try { + // re-toss the current exception so we can find out what type it is. + // NOTE: a heinous bug in MSVC6 causes exception objects re-thrown in + // this way to be double-destroyed. Thus, you must only use objects that + // can tolerate double-destruction with that compiler. Metrowerks + // Codewarrior doesn't suffer from this problem. + throw; + } + catch(const python::error_already_set&) + { + // The python error reporting has already been handled. + } + catch(const std::bad_alloc&) + { + PyErr_NoMemory(); + } + catch(const std::exception& x) + { + PyErr_SetString(PyExc_RuntimeError, x.what()); + } + catch(...) + { + PyErr_SetString(PyExc_RuntimeError, "unidentifiable C++ exception"); + } +} + +} // namespace python + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE + +long from_python(PyObject* p, python::type) +{ + // Why am I clearing the error here before trying to convert? I know there's a reason... + long result; + { + result = PyInt_AsLong(p); + if (PyErr_Occurred()) + throw python::argument_error(); + } + return result; +} + +double from_python(PyObject* p, python::type) +{ + double result; + { + result = PyFloat_AsDouble(p); + if (PyErr_Occurred()) + throw python::argument_error(); + } + return result; +} + +template +T integer_from_python(PyObject* p, python::type) +{ + const long long_result = from_python(p, python::type()); + +#ifndef BOOST_NO_LIMITS + try + { + return boost::numeric_cast(long_result); + } + catch(const boost::bad_numeric_cast&) +#else + if (static_cast(long_result) == long_result) + { + return static_cast(long_result); + } + else +#endif + { + char buffer[256]; + const char message[] = "%ld out of range for %s"; + sprintf(buffer, message, long_result, typeid(T).name()); + PyErr_SetString(PyExc_ValueError, buffer); + throw python::argument_error(); + } +#if defined(__MWERKS__) && __MWERKS__ <= 0x2400 + return 0; // Not smart enough to know that the catch clause always rethrows +#endif +} + +template +PyObject* integer_to_python(T value) +{ + long value_as_long; + +#ifndef BOOST_NO_LIMITS + try + { + value_as_long = boost::numeric_cast(value); + } + catch(const boost::bad_numeric_cast&) +#else + value_as_long = static_cast(value); + if (value_as_long != value) +#endif + { + const char message[] = "value out of range for Python int"; + PyErr_SetString(PyExc_ValueError, message); + throw python::error_already_set(); + } + + return to_python(value_as_long); +} + +int from_python(PyObject* p, python::type type) +{ + return integer_from_python(p, type); +} + +PyObject* to_python(unsigned int i) +{ + return integer_to_python(i); +} + +unsigned int from_python(PyObject* p, python::type type) +{ + return integer_from_python(p, type); +} + +short from_python(PyObject* p, python::type type) +{ + return integer_from_python(p, type); +} + +float from_python(PyObject* p, python::type) +{ + return static_cast(from_python(p, python::type())); +} + +PyObject* to_python(unsigned short i) +{ + return integer_to_python(i); +} + +unsigned short from_python(PyObject* p, python::type type) +{ + return integer_from_python(p, type); +} + +PyObject* to_python(unsigned char i) +{ + return integer_to_python(i); +} + +unsigned char from_python(PyObject* p, python::type type) +{ + return integer_from_python(p, type); +} + +PyObject* to_python(signed char i) +{ + return integer_to_python(i); +} + +signed char from_python(PyObject* p, python::type type) +{ + return integer_from_python(p, type); +} + +PyObject* to_python(unsigned long x) +{ + return integer_to_python(x); +} + +unsigned long from_python(PyObject* p, python::type type) +{ + return integer_from_python(p, type); +} + +void from_python(PyObject* p, python::type) +{ + if (p != Py_None) { + PyErr_SetString(PyExc_TypeError, "expected argument of type None"); + throw python::argument_error(); + } +} + +const char* from_python(PyObject* p, python::type) +{ + const char* s = PyString_AsString(p); + if (!s) + throw python::argument_error(); + return s; +} + +PyObject* to_python(const std::string& s) +{ + return PyString_FromString(s.c_str()); +} + +std::string from_python(PyObject* p, python::type) +{ + return std::string(from_python(p, python::type())); +} + +bool from_python(PyObject* p, python::type) +{ + int value = from_python(p, python::type()); + if (value == 0) + return false; + return true; +} + +#ifdef BOOST_MSVC6_OR_EARLIER +// An optimizer bug prevents these from being inlined. +PyObject* to_python(double d) +{ + return PyFloat_FromDouble(d); +} + +PyObject* to_python(float f) +{ + return PyFloat_FromDouble(f); +} +#endif // BOOST_MSVC6_OR_EARLIER + +BOOST_PYTHON_END_CONVERSION_NAMESPACE + diff --git a/py.h b/py.h new file mode 100644 index 00000000..e1cf6cb8 --- /dev/null +++ b/py.h @@ -0,0 +1,325 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef METHOD_DWA122899_H_ +# define METHOD_DWA122899_H_ + +# include "pyconfig.h" +# include "wrap_python.h" +# include "none.h" +# include "signatures.h" +# include +# include "errors.h" +# include + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE // this is a gcc 2.95.2 bug workaround + +// This can be instantiated on an enum to provide the to_python/from_python +// conversions, provided the values can fit in a long. +template +class py_enum_as_int_converters +{ + friend EnumType from_python(PyObject* x, python::type) + { + return static_cast( + from_python(x, python::type())); + } + + friend EnumType from_python(PyObject* x, python::type) + { + return static_cast( + from_python(x, python::type())); + } + + friend PyObject* to_python(EnumType x) + { + return to_python(static_cast(x)); + } +}; +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +namespace python { +template class enum_as_int_converters + : public BOOST_PYTHON_CONVERSION::py_enum_as_int_converters {}; + +template class wrapped_pointer; + +//#pragma warn_possunwant off +inline void decref_impl(PyObject* p) { Py_DECREF(p); } +inline void xdecref_impl(PyObject* p) { Py_XDECREF(p); } +//#pragma warn_possunwant reset + +template +inline void decref(T* p) +{ + char* const raw_p = reinterpret_cast(p); + char* const p_base = raw_p - offsetof(PyObject, ob_refcnt); + decref_impl(reinterpret_cast(p_base)); +} + +template +inline void xdecref(T* p) +{ + char* const raw_p = reinterpret_cast(p); + char* const p_base = raw_p - offsetof(PyObject, ob_refcnt); + xdecref_impl(reinterpret_cast(p_base)); +} + +} // namespace python + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE +// +// Converters +// +PyObject* to_python(long); +long from_python(PyObject* p, python::type); +long from_python(PyObject* p, python::type); + +PyObject* to_python(unsigned long); +unsigned long from_python(PyObject* p, python::type); +unsigned long from_python(PyObject* p, python::type); + +PyObject* to_python(int); +int from_python(PyObject*, python::type); +int from_python(PyObject*, python::type); + +PyObject* to_python(unsigned int); +unsigned int from_python(PyObject*, python::type); +unsigned int from_python(PyObject*, python::type); + +PyObject* to_python(short); +short from_python(PyObject*, python::type); +short from_python(PyObject*, python::type); + +PyObject* to_python(unsigned short); +unsigned short from_python(PyObject*, python::type); +unsigned short from_python(PyObject*, python::type); + +PyObject* to_python(signed char); +signed char from_python(PyObject*, python::type); +signed char from_python(PyObject*, python::type); + +PyObject* to_python(unsigned char); +unsigned char from_python(PyObject*, python::type); +unsigned char from_python(PyObject*, python::type); + +PyObject* to_python(float); +float from_python(PyObject*, python::type); +float from_python(PyObject*, python::type); + +PyObject* to_python(double); +double from_python(PyObject*, python::type); +double from_python(PyObject*, python::type); + +PyObject* to_python(bool); +bool from_python(PyObject*, python::type); +bool from_python(PyObject*, python::type); + +PyObject* to_python(void); +void from_python(PyObject*, python::type); + +PyObject* to_python(const char* s); +const char* from_python(PyObject*, python::type); + +PyObject* to_python(const std::string& s); +std::string from_python(PyObject*, python::type); +std::string from_python(PyObject*, python::type); + +// For when your C++ function really wants to pass/return a PyObject* +PyObject* to_python(PyObject*); +PyObject* from_python(PyObject*, python::type); + +// Some standard conversions to/from smart pointer types. You can add your own +// from these examples. These are not generated using the friend technique from +// wrapped_pointer because: +// +// 1. We want to be able to extend conversion to/from WrappedPointers using +// arbitrary smart pointer types. +// +// 2. It helps with compilation independence. This way, code which creates +// wrappers for functions accepting and returning smart_ptr does not +// have to have already seen the invocation of wrapped_type. +// + +// Unfortunately, MSVC6 is so incredibly lame that we have to rely on the friend +// technique to auto_generate standard pointer conversions for wrapped +// types. This means that you need to write a non-templated function for each +// specific smart_ptr which you want to convert from_python. For example, +// +// namespace python { +// #ifdef MUST_SUPPORT_MSVC +// +// MyPtr from_python(PyObject*p, type >) +// { return smart_ptr_from_python(p, type >(), type());} +// } +// +// MyPtr from_python(PyObject*p, type >) +// { return smart_ptr_from_python(p, type >(), type());} +// +// ... // definitions for MyPtr, MyPtr, etc. +// +// #else +// +// // Just once for all MyPtr +// template +// MyPtr from_python(PyObject*p, type >) +// { +// return smart_ptr_from_python(p, type >(), type()); +// } +// +// #endif +// } + +#if !defined(BOOST_MSVC6_OR_EARLIER) +template +boost::shared_ptr from_python(PyObject*p, python::type >) +{ + return smart_ptr_from_python(p, python::type >(), python::type()); +} +#endif + +#if 0 +template +PyObject* to_python(std::auto_ptr p) +{ + return new python::wrapped_pointer, T>(p); +} + +template +PyObject* to_python(boost::shared_ptr p) +{ + return new python::wrapped_pointer, T>(p); +} +#endif + +// +// inline implementations +// + +#ifndef BOOST_MSVC6_OR_EARLIER +inline PyObject* to_python(double d) +{ + return PyFloat_FromDouble(d); +} + +inline PyObject* to_python(float f) +{ + return PyFloat_FromDouble(f); +} +#endif // BOOST_MSVC6_OR_EARLIER + +inline PyObject* to_python(long l) +{ + return PyInt_FromLong(l); +} + +inline PyObject* to_python(int x) +{ + return PyInt_FromLong(x); +} + +inline PyObject* to_python(short x) +{ + return PyInt_FromLong(x); +} + +inline PyObject* to_python(bool b) +{ + return PyInt_FromLong(b); +} + +inline PyObject* to_python(void) +{ + return python::detail::none(); +} + +inline PyObject* to_python(const char* s) +{ + return PyString_FromString(s); +} + +inline std::string from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline PyObject* to_python(PyObject* p) +{ + Py_INCREF(p); + return p; +} + +inline PyObject* from_python(PyObject* p, python::type) +{ + return p; +} + +inline const char* from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline double from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline float from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline int from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline short from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline long from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline bool from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline unsigned int from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline unsigned short from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline signed char from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline unsigned char from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + +inline unsigned long from_python(PyObject* p, python::type) +{ + return from_python(p, python::type()); +} + + +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +#endif // METHOD_DWA122899_H_ diff --git a/py_cpp.html b/py_cpp.html new file mode 100644 index 00000000..15bd7049 --- /dev/null +++ b/py_cpp.html @@ -0,0 +1,211 @@ + + + + py_cpp Python/C++ binding documentation + +

+ c++boost.gif (8819 bytes) py_cpp* +

+ +

+ The source code for py_cpp, including a MSVC demo project is available here. + +

Synopsis

+

+ py_cpp is a system for quickly and easily interfacing C++ code with Python such that the Python interface is + very similar to the C++ interface. It is designed to be minimally + intrusive on your C++ design. In most cases, you should not have to alter + your C++ classes in any way in order to use them with py_cpp. The system + should simply “reflect” your C++ classes and functions into + Python. The major features of py_cpp include support for: +

+among others. + + +

Supported Platforms

+

py_cpp has been tested in the following configurations: + +

+ +

Py_cpp requires the boost libraries, and is + has been accepted for inclusion into the boost libraries pending “boostification“ + (completion of the documentation, change in some naming conventions and + resolution of some namespace issues). + +

Credits

+
    +
  • David Abrahams originated + and wrote py_cpp. + +
  • Ullrich Koethe + had independently developed a similar system. When he discovered py_cpp, + he generously contributed countless hours of coding and much insight into + improving it. He is responsible for an early version of the support for function overloading and wrote the support for + reflecting C++ inheritance + relationships. He has helped to improve error-reporting from both + Python and C++, and has designed an extremely easy-to-use way of + exposing numeric operators, including + a way to avoid explicit coercion by means of overloading. + +
  • The members of the boost mailing list and the Python community supplied + invaluable early feedback. In particular, Ron Clarke, Mark Evans, Anton + Gluck, Ralf W. Grosse-Kunstleve, Prabhu Ramachandran, and Barry Scott took + the brave step of trying to use py_cpp while it was still in early stages + of development. + +
  • The development of py_cpp wouldn't have been + possible without the generous support of Dragon Systems/Lernout and + Hauspie, Inc who supported its development as an open-source project. +
+ +

Table of Contents

+ +
    +
  1. A Brief Introduction to writing Python + extension modules + +
  2. Comparisons between py_cpp and other + systems for extending Python + +
  3. A Simple Example Using py_cpp + +
  4. Overridable Virtual Functions + +
  5. Function Overloading + +
  6. Inheritance + +
  7. Special Method and Operator Support + +
  8. A Peek Under the Hood + +
  9. Building a Module with Py_cpp + +
  10. Advanced Topics + +
      +
    1. class_builder<> + +
    2. enums + +
    3. References + +
    4. Pointers and Smart Pointers + +
    5. Built-in Python Types + +
    6. Other Extension Types + +
    7. Templates +
    + +
+ +

+ More sophisticated examples are given in + extclass_demo.cpp, extclass_demo.h, and + test_extclass.py in the source code + archive. There's much more here, and much more documentation to + come... +

+ Questions should be directed to the boost mailing list. + +

Naming Contest

+ +

+ Yes, I know py_cpp is a lousy name. Problem is, the best names my puny + imagination can muster (IDLE and GRAIL) are taken, so I'm holding a + naming contest. First prize? You get to pick the name<0.2wink> and + you will be credited in the documentation. Names that have been suggested + so far include: +

    +
  • + Py++ +
  • + Python++ +
  • + Coil +
  • + SnakeSkin +
  • + CCCP - Convert C++ + Classes to Python +
  • + C3PO - Convert C++ + Classes to Python + Objects +
  • + PALIN - Python + Augmented-Language + INtegration +
  • + CLEESE - C++ Language Extension Environment + Supremely Easy +
  • + JONES - Just Obscenely Neat Extension + System +
  • + C-thru +
  • + SeamlessC +
  • + BorderCrossing +
  • + Perseus (because he solved a hairy problem involving snakes by using + reflection and was invisible most of the time). +
+ Please post or send me your suggestions!
+
+ +

+ © 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: Nov 25, 2000 + diff --git a/pyconfig.h b/pyconfig.h new file mode 100644 index 00000000..746806ad --- /dev/null +++ b/pyconfig.h @@ -0,0 +1,56 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef CONFIG_DWA052200_H_ +# define CONFIG_DWA052200_H_ + +# include +# include + +# ifdef BOOST_NO_OPERATORS_IN_NAMESPACE + // A gcc bug forces some symbols into the global namespace +# define BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE +# define BOOST_PYTHON_END_CONVERSION_NAMESPACE +# define BOOST_PYTHON_CONVERSION +# define BOOST_PYTHON_IMPORT_CONVERSION(x) using ::x +# else +# define BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE namespace python { +# define BOOST_PYTHON_END_CONVERSION_NAMESPACE } +# define BOOST_PYTHON_CONVERSION python +# define BOOST_PYTHON_IMPORT_CONVERSION(x) void never_defined() // so we can follow the macro with a ';' +# endif + +# if defined(BOOST_MSVC) +# if _MSC_VER <= 1200 +# define BOOST_MSVC6_OR_EARLIER 1 +# endif + +# pragma warning (disable : 4786) + +# endif + +// Work around the broken library implementation/strict ansi checking on some +// EDG-based compilers (e.g. alpha), which incorrectly warn that the result of +// offsetof() is not an integer constant expression. +# if defined(__DECCXX_VER) && __DECCXX_VER <= 60290024 +# define BOOST_OFFSETOF(s_name, s_member) \ + ((size_t)__INTADDR__(&(((s_name *)0)->s_member))) +# else +# define BOOST_OFFSETOF(s_name, s_member) \ + offsetof(s_name, s_member) +# endif + +// The STLport puts all of the standard 'C' library names in std (as far as the +// user is concerned), but without it you need a fix if you're using MSVC. +# if defined(BOOST_MSVC6_OR_EARLIER) && !defined(__STLPORT) +# define BOOST_CSTD_ +# else +# define BOOST_CSTD_ std +# endif + +#endif // CONFIG_DWA052200_H_ diff --git a/pyptr.h b/pyptr.h new file mode 100644 index 00000000..b611b546 --- /dev/null +++ b/pyptr.h @@ -0,0 +1,173 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef PYPTR_DWA050400_H_ +# define PYPTR_DWA050400_H_ + +# include "pyconfig.h" +# include +# include "wrap_python.h" +# include "cast.h" +# include +# include "signatures.h" +# include "errors.h" +# include "py.h" + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE + +template +struct py_ptr_conversions : Base +{ + inline friend T from_python(PyObject* x, python::type) + { return T(python::downcast(x).get(), T::increment_count); } + + inline friend T from_python(PyObject* x, python::type) + { return T(python::downcast(x).get(), T::increment_count); } + + inline friend PyObject* to_python(T x) + { return python::as_object(x.release()); } + +}; + +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +namespace python { + +BOOST_PYTHON_IMPORT_CONVERSION(py_ptr_conversions); + +template +class reference + : public py_ptr_conversions, T, + boost::dereferenceable, T*> > // supplies op-> +{ +public: + typedef T value_type; + + reference(const reference& rhs) + : m_p(rhs.m_p) + { + Py_XINCREF(object()); + } + +#if !defined(BOOST_MSVC6_OR_EARLIER) + template + reference(const reference& rhs) + : m_p(rhs.object()) + { + Py_XINCREF(object()); + } +#endif + + reference() : m_p(0) {} + + // These are two ways of spelling the same thing, that we need to increment + // the reference count on the pointer when we're initialized. + enum increment_count_t { increment_count }; + + enum allow_null { null_ok }; + + template + explicit reference(T2* x) + : m_p(expect_non_null(x)) {} + + template + reference(T2* x, increment_count_t) + : m_p(expect_non_null(x)) { Py_INCREF(object()); } + + template + reference(T2* x, allow_null) + : m_p(x) {} + + template + reference(T2* x, allow_null, increment_count_t) + : m_p(x) { Py_XINCREF(object()); } + + template + reference(T2* x, increment_count_t, allow_null) + : m_p(x) { Py_XINCREF(object()); } + +#if !defined(BOOST_MSVC6_OR_EARLIER) + template + reference& operator=(const reference& rhs) + { + Py_XDECREF(object()); + m_p = rhs.m_p; + Py_XINCREF(object()); + return *this; + } +#endif + + reference& operator=(const reference& rhs) + { + Py_XINCREF(static_cast(rhs.m_p)); + Py_XDECREF(object()); + m_p = rhs.m_p; + return *this; + } + + ~reference() + { + Py_XDECREF(m_p); + } + + T& operator*() const { return *m_p; } + + T* get() const { return m_p; } + + T* release() + { + T* p = m_p; + m_p = 0; + return p; + } + + void reset() + { Py_XDECREF(m_p); m_p = 0; } + + template + void reset(T2* x) + { Py_XDECREF(m_p); m_p = expect_non_null(x);} + + template + void reset(T2* x, increment_count_t) + { Py_XDECREF(m_p); m_p = expect_non_null(x); Py_INCREF(object()); } + + template + void reset(T2* x, allow_null) + { Py_XDECREF(m_p); m_p = x;} + + template + void reset(T2* x, allow_null, increment_count_t) + { Py_XDECREF(m_p); m_p = x; Py_XINCREF(object()); } + + template + void reset(T2* x, increment_count_t, allow_null) + { Py_XDECREF(m_p); m_p = x; Py_XINCREF(object()); } + +#if !defined(BOOST_NO_MEMBER_TEMPLATE_FRIENDS) +private: + template friend class shared_ptr; +#endif + + inline PyObject* object() const + { return as_object(m_p); } + + T* m_p; +}; + +typedef reference ref; + +template +ref make_ref(const T& x) +{ + return ref(to_python(x)); +} + +} + +#endif // PYPTR_DWA050400_H_ diff --git a/release_notes.txt b/release_notes.txt new file mode 100644 index 00000000..8932a0aa --- /dev/null +++ b/release_notes.txt @@ -0,0 +1,217 @@ +2000-11-22 10:00 + Ullrich fixed bug in operator_dispatcher. + +2000-11-21 10:00 + Changed all class and function names into lower_case. + + Ullrich updated documentation for operator wrapping. + +2000-11-20 10:00 + Ullrich renamed ExtensionClass:register_coerce() into + ExtensionClass:def_standard_coerce() and made it public + + Ullrich improved shared_pod_manager. + +2000-11-17 15:04 + Changed allocation strategy of shared_pod_manager to make it portable. + + Added pickling support + tests thanks to "Ralf W. Grosse-Kunstleve" + + + Added a specialization of Callback to prevent unsafe usage. + + Fixed Ullrich's operator_dispatcher refcount bug + + Removed const char* return values from virtual functions in tests; that + usage was unsafe. + + Ullrich changed Module::add() so that it steals a reference (fix of refcount bug) + + Ullrich added operator_dispatcher::create() optimization + + Ullrich changed design and implementation of TypeObjectBase::enable() (to eliminate low-level + code) and added shared_pod_manager optimization. + + +2000-11-15 12:01 + Fixed refcount bugs in operator calls. + + Added callback_adjust_refcount(PyObject*, Type) to account for different ownership + semantics of Callback's return types and Caller's arguments (which both use from_python()) + This bug caused refcount errors during operator calls. + + Moved operator_dispatcher into extclass.cpp + Gave it shared ownership of the objects it wraps + + Introduced sequence points in extension_class_coerce for exception-safety + + UPPER_CASE_MACRO_NAMES + + MixedCase template type argument names + + Changed internal error reporting to use Python exceptions so we don't force the + user to link in iostreams code + + Changed error return value of call_cmp to -1 + + Moved unwrap_* functions out of operator_dispatcher. This was transitional: when + I realized they didn't need to be declared in extclass.h I moved them out, but + now that operator_dispatcher itself is in extclass.cpp they could go back in. + + Numerous formatting tweaks + + Updated the BoundFunction::create() optimization and enabled it so it could actually be used! + +2000-11-15 00:26 + + Made Ullrich's operators support work with MSVC + + Cleaned up operators.h such that invalid define_operator<0> is no longer needed. + + Ullrich created operators.h to support wrapping of C++ operators (including the "__r*__" forms). + He added several auxiliary classes to extclass.h and extclass.cpp (most importantly, + py::detail::operator_dispatcher and py::operators) + +2000-11-13 22:29 + + removed obsolete ExtensionClassFromPython for good. + + removed unused class ExtensionType forward declaration + +2000-11-12 13:08 + + Added enum_as_int_converters for easier enum wrapping + + Introduced new conversion namespace macros: + PY_BEGIN_CONVERSION_NAMESPACE, + PY_END_CONVERSION_NAMESPACE, + PY_CONVERSION + + callback.h, gen_callback.py: + Added call() function so that a regular python function (as opposed to + method or other function-as-attribute) can be called. + + Added newlines for readability. + + class_wrapper.h: + Fixed a bug in add(), which allows non-method class attributes + + Ullrich has added def_raw for simple varargs and keyword support. + + Fixed version number check for __MWERKS__ + + Added tests for enums and non-method class attributes + + objects.h/objects.cpp: + Added py::String operator*= and operator* for repetition + + Change Dict::items(), keys(), and values() to return a List + + Added template versions of set_item, etc., methods so that users can optionally + use C++ types that have to_python() functions as parameters. + + Changed various Ptr by-value parameters to const Ptr& + + +======= Release ======= +2000-11-06 0:22 + Lots of documentation updates + + added 4-argument template constructor to py::Tuple + + added "add" member function to ClassWrapper<> to allow arbitrary Python + objects to be added to an extension class. + + gen_all.py now generates support for n argument member functions and n+1 + argument member functions at the suggestion of "Ralf W. Grosse-Kunstleve" + + + Added regression tests and re-ordered declare_base calls to verify that the + phantom base class issue is resolved. + +2000-11-04 17:35 + + Integrated Ullrich Koethe's brilliant from_python_experiment for better + error-reporting in many cases. + + extclass.h, gen_extclass.py: + removed special-case MSVC code + added much commentary + removed unused py_copy_to_new_value_holder + + init_function.h, gen_init_function.py: + added missing 'template' keyword on type-dependent template member usage + removed special-case MSVC code + added much commentary + +2000-11-04 0:36 + + Removed the need for the phantom base class that screwed up inheritance + hierarchies, introduced error-prone ordering dependencies, and complexified + logic in many places! + + extclass.h: Added some explanatory comments, removed wasteful m_self member + of HeldInstance + + extclass_demo.cpp: Added #pragmas which allow compilation in ansi strict + mode under Metrowerks + + functions.h: Added virtual_function as part of phantom base class removal; + expanded commentary + + pyptr.h: Added some missing 'typename's and a GCC workaround fix + + subclass.cpp: Added missing string literal const_cast<>s. + +2000-11-03 10:58 + + Fix friend function instantiation bug caught by Metrowerks (thanks + Metrowerks!) + + Add proof-of-concept for one technique of wrapping function that return a + pointer + + Worked around MSVC optimizer bug by writing to_python(double) and + to_python(float) out-of-line + +2000-11-02 23:25 + + Add /Zm200 option to vc6_prj to deal with MSVC resource limitations + + Remove conflicting /Ot option from vc6_prj release build + +======= Release ======= +2000-11-02 17:42 + + Added a fix for interactions between default virtual function + implementations and declare_base(). You still need to write your + declare_base() /after/ all member functions have been def()d for the two + classes concerned. Many, many thanks to Ullrich Koethe + for all his work on this. + + Added missing conversions: + to_python(float) + from_python(const char* const&) + from_python(const double&) + from_python(const float&) + + Added a Regression test for a reference-counting bug thanks to Mark Evans + () + + const-ify ClassBase::getattr() + + Add repr() function to Class + + Add to_python/from_python conversions for PyPtr + + Standardize set_item/get_item interfaces (instead of proxies) for Dict and List + + Add Reprable<> template to newtypes.h + + Fix a bug wherein the __module__ attribute would be lost for classes that have a + default virtual function implementation. + + Remove extra ';' in module.cpp thanks to "Ralf W. Grosse-Kunstleve" + + + Fix a bug in the code of example1.html diff --git a/signatures.h b/signatures.h new file mode 100644 index 00000000..39dd94c2 --- /dev/null +++ b/signatures.h @@ -0,0 +1,251 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. +// +// This file automatically generated by gen_signatures.python for 10 arguments. +#ifndef SIGNATURES_DWA050900_H_ +# define SIGNATURES_DWA050900_H_ + +# include "pyconfig.h" + +namespace python { + +namespace detail { +// A stand-in for the built-in void. This one can be passed to functions and +// (under MSVC, which has a bug, be used as a default template type parameter). +struct void_t {}; +} + +// An envelope in which type information can be delivered for the purposes +// of selecting an overloaded from_python() function. This is needed to work +// around MSVC's lack of partial specialiation/ordering. Where normally we'd +// want to form a function call like void f(), We instead pass +// type as one of the function parameters to select a particular +// overload. +// +// The id typedef helps us deal with the lack of partial ordering by generating +// unique types for constructor signatures. In general, type::id is type, +// but type::id is just void_t. +template +struct type +{ + typedef type id; +}; + +template <> +struct type +{ + typedef python::detail::void_t id; +}; + +namespace detail { +// These basically encapsulate a chain of types, , used to make the syntax of +// add(constructor()) work. We need to produce a unique type for each number +// of non-default parameters to constructor<>. Q: why not use a recursive +// formulation for infinite extensibility? A: MSVC6 seems to choke on constructs +// that involve recursive template nesting. +// +// signature chaining +template +struct signature10 {}; + +template +struct signature9 {}; + +template +inline signature10 prepend(type, signature9) + { return signature10(); } + +template +struct signature8 {}; + +template +inline signature9 prepend(type, signature8) + { return signature9(); } + +template +struct signature7 {}; + +template +inline signature8 prepend(type, signature7) + { return signature8(); } + +template +struct signature6 {}; + +template +inline signature7 prepend(type, signature6) + { return signature7(); } + +template +struct signature5 {}; + +template +inline signature6 prepend(type, signature5) + { return signature6(); } + +template +struct signature4 {}; + +template +inline signature5 prepend(type, signature4) + { return signature5(); } + +template +struct signature3 {}; + +template +inline signature4 prepend(type, signature3) + { return signature4(); } + +template +struct signature2 {}; + +template +inline signature3 prepend(type, signature2) + { return signature3(); } + +template +struct signature1 {}; + +template +inline signature2 prepend(type, signature1) + { return signature2(); } + +struct signature0 {}; + +template +inline signature1 prepend(type, signature0) + { return signature1(); } + + +// This one terminates the chain. Prepending void_t to the head of a void_t +// signature results in a void_t signature again. +inline signature0 prepend(void_t, signature0) { return signature0(); } + +} // namespace detail + +template +struct constructor +{ +}; + +namespace detail { +// Return value extraction: + +// This is just another little envelope for carrying a typedef (see type, +// above). I could have re-used type, but that has a very specific purpose. I +// thought this would be clearer. +template +struct return_value_select { typedef T type; }; + +// free functions +template +return_value_select return_value(R (*)()) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2, A3)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2, A3, A4)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2, A3, A4, A5)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2, A3, A4, A5, A6)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2, A3, A4, A5, A6, A7)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2, A3, A4, A5, A6, A7, A8)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2, A3, A4, A5, A6, A7, A8, A9)) { return return_value_select(); } + +template +return_value_select return_value(R (*)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)) { return return_value_select(); } + +// TODO(?): handle 'const void' + +// member functions +template +return_value_select return_value(R (T::*)()) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6, A7)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6, A7, A8)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6, A7, A8, A9)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)() const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6, A7) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6, A7, A8) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6, A7, A8, A9) const) { return return_value_select(); } + +template +return_value_select return_value(R (T::*)(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) const) { return return_value_select(); } + +}} // namespace python::detail + +#endif diff --git a/singleton.h b/singleton.h new file mode 100644 index 00000000..ccbcc1e6 --- /dev/null +++ b/singleton.h @@ -0,0 +1,68 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef SINGLETON_DWA051900_H_ +# define SINGLETON_DWA051900_H_ + +# include "pyconfig.h" + +namespace python { namespace detail { + +struct empty {}; +template +struct singleton : Base +{ + typedef singleton singleton_base; // Convenience type for derived class constructors + + static Derived* instance(); + + // Pass-through constructors + singleton() : Base() {} + + template + singleton(const A1& a1) : Base(a1) {} + + template + singleton(const A1& a1, const A2& a2) : Base(a1, a2) {} + + template + singleton(const A1& a1, const A2& a2, const A3& a3) : Base(a1, a2, a3) {} + + template + singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4) : Base(a1, a2, a3, a4) {} + + template + singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : Base(a1, a2, a3, a4, a5) {} + + template + singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) : Base(a1, a2, a3, a4, a5, a6) {} + + template + singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7) : Base(a1, a2, a3, a4, a5, a6, a7) {} + + template + singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) : Base(a1, a2, a3, a4, a5, a6, a7, a8) {} + + template + singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9) : Base(a1, a2, a3, a4, a5, a6, a7, a8, a9) {} + + template + singleton(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8, const A9& a9, const A10& a10) : Base(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) {} + +}; + +template +Derived* singleton::instance() +{ + static Derived x; + return &x; +} + +}} // namespace python::detail + +#endif diff --git a/special.html b/special.html new file mode 100644 index 00000000..d32b843b --- /dev/null +++ b/special.html @@ -0,0 +1,816 @@ + + + Special Method and Operator Support + +

+

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

+

+ Overview +

+

+ Py_cpp is able to wrap suitable C++ functions and C++ operators into + Python operators. It supports all of the standard + special method names supported by real Python class instances + except __complex__ (more on the reasons below). Supported operators include + general, numeric, and sequence and mapping operators. In + addition, py_cpp provides a simple way to export member variables and + define attributes by means of getters and + setters. +

+ General Operators +

+ Python provides a number of special operatos for basic customization of a + class: +
+
+ __repr__: +
+ create a string representation from which the object can be + reconstructed +
+ __str__: +
+ create a string representation which is suitable for printing +
+ __cmp__: +
+ three-way compare function, used to implement comparison operators + (< etc.) +
+ __hash__: +
+ needed to use the object as a dictionary key (only allowed if __cmp__ + is also defined) +
+ __nonzero__: +
+ called if the object is used as a truth value (e.g. in an if + statement) +
+ __call__: +
+ make instances of the class callable like a function +
+ If we have a suitable C++ function that supports any of these features, + we can export it like any other function, using its Python special name. + For example, suppose that class Foo provides a string + conversion function: +
+    std::string to_string(Foo const & f)
+    {
+        std::ostringstream s;
+        s << f;
+        return s.str();
+    }
+
+ This function would be wrapped like this: +
+    python::class_builder<Foo> foo_class(my_module, "Foo");
+    foo_class.def(&to_string, "__str__");
+
+ + Note that py_cpp also supports automatic wrapping of + "__str__" and "__cmp__". This is explained in the next + section and the table of numeric + operators. + + +

+ Numeric Operators +

+ There are two fundamental ways to define numeric operators within py_cpp: + manual wrapping (as is done with general + operators) and automatic wrapping. Lets start with the second + possibility. Suppose, C++ defines a class Int (which might + represent an infinite-precision integer) which supports addition, so that + we can write (in C++): +
+    Int a, b, c;
+    ...
+    c = a + b;
+
+ To enable the same functionality in Python, we first wrap the + Int class as usual: +
+    python::class_builder<Int> int_class(my_module, "Int");
+    int_class.def(python::constructor<>());
+    ...
+
+ Then we export the addition operator like this: +
+    int_class.def(python::operators<python::op_add>());
+
+ Since Int also supports subtraction, multiplication, adn division, we + want to export those also. This can be done in a single command by + 'or'ing the operator identifiers together (a complete list of these + identifiers and the corresponding operators can be found in the table): +
+    int_class.def(python::operators<(python::op_sub | python::op_mul | python::op_div)>());
+
+ Note that the or-expression must be enclosed in parentheses. This form of + operator definition will wrap homogeneous operators, i.e. operators whose + left and right operand have the same type. Now, suppose that our C++ + library also supports addition of Ints and plain integers: +
+    Int a, b;
+    int i;
+    ...
+    a = b + i;
+    a = i + b;
+
+ To wrap these heterogeneous operators (left and right hand side have + different types), we need a possibility to specify a different type for + one of the operands. This is done using the right_operand + and left_operand templates: +
+    int_class.def(python::operators<python::op_add>(), python::right_operand<int>());
+    int_class.def(python::operators<python::op_add>(), python::left_operand<int>());
+
+ Py_cpp uses overloading to register several variants of the same + operation (more on this in the context of + coercion). Again, several operators can be exported at once: +
+    int_class.def(python::operators<(python::op_sub | python::op_mul | python::op_div)>(),
+                       python::right_operand<int>());
+    int_class.def(python::operators<(python::op_sub | python::op_mul | python::op_div)>(), 
+                       python::left_operand<int>());
+
+ The type of the operand not mentioned is taken from the class object. In + our example, the class object is int_class, and thus the + other operand's type is `Int const &'. You can override + this default by explicitly specifying a type in the + operators template: +
+    int_class.def(python::operators<python::op_add, Int>(), python::right_operand<int>());
+
+ Here, `Int' would be used instead of `Int const + &'. +

+ Note that automatic wrapping doesn't need any specific form of + operator+() (or one of the other operators), but rather wraps + the expression `left + right'. That is, this + mechanism can be used for any definition of operator+(), + such as a free function `Int operator+(Int, Int)' or a + member function `Int Int::operator+(Int)'. +

+ For the Python operators pow() and abs(), + there is no corresponding C++ operator. Instead, automatic wrapping + attempts to wrap C++ functions of the same name. This only works if + those functions are known in namespace python::detail. + Thus it might be necessary to add a using declaration prior to + wrapping: +

+    namespace python { 
+      namespace detail {
+        using my_namespace::pow;
+        using my_namespace::abs;
+    }}
+
+

+ In some cases, automatic wrapping of operators is not possible or not + desirable. Suppose, for example, that the modulo operation for Ints is + defined by a set of functions mod() (for automatic + wrapping, we would need operator%()): +

+    Int mod(Int const & left, Int const & right);
+    Int mod(Int const & left, int right);
+    Int mod(int left, Int const & right);
+
+ In order to create the Python operator "__mod__" from these functions, we + have to wrap them manually: +
+    int_class.def((Int (*)(Int const &, Int const &))&mod, "__mod__");
+    int_class.def((Int (*)(Int const &, int))&mod, "__mod__");
+
+ The third form (with int as left operand) cannot be wrapped + this way. We must first create a function rmod() with the + operands reversed: +
+    Int rmod(Int const & right, int left)
+    {
+        return mod(left, right);
+    }
+
+ This function must be wrapped under the name "__rmod__": +
+    int_class.def(&rmod,  "__rmod__");
+
+ A list of the possible operator names is also found in the table. Special treatment is necessary to export the + ternary pow operator. +

+ Automatic and manual wrapping can be mixed arbitrarily. Note that you + cannot overload the same operator for a given extension class on both + `int' and `float', because Python implicitly + converts these types into each other. Thus, the overloaded variant + found first (be it `int' or `float') will be + used for either of the two types. +

+ Coercion +

+ Plain Python can only execute operators with identical types on the left + and right hand side. If it encounters an expression where the types of + the left and right operand differ, it tries to coerce these type to a + common type before invoking the actual operator. Implementing good + coercion functions can be difficult if many type combinations must be + supported. +

+ In contrast, py_cpp provides + overloading. By means of overloading, operator calling can be + simplyfied drastically: you just register operators for all desired + type combinations, and py_cpp automatically ensures that the correct + function is called in each case. User defined coercion functions are + not necessary. To enable operator overloading, py_cpp provides + a standard coercion which is implicitly registered whenever + automatic operator wrapping is used. +

+ If you wrap all operator functions manually, but still want to use + operator overloading, you have to register the standard coercion + function explicitly: +

+    // this is not necessary if automatic operator wrapping is used
+    int_class.def_standard_coerce();
+
+ In case you encounter a situation where you absolutely need a customized + coercion, you can overload the "__coerce__" operator itself. The + signature of a coercion function must look like this: +
+    python::tuple custom_coerce(PyObject * left, PyObject * right);
+
+ The resulting tuple must contain two elements which + represent the values of left and right + converted to the same type. Such a function is wrapped as usual: +
+    some_class.def(&custom_coerce, "__coerce__");
+
+ Note that the custom coercion function is only used if it is defined + before any automatic operator wrapping on the given class or a call + to `some_class.def_standard_coerce()'. +

+ The Ternary pow() Operator +

+ In addition to the usual binary pow()-operator (meaning + x^y), Python also provides a ternary variant that implements + (x^y) % z (presumably using a more efficient algorithm than + concatenation of power and modulo operators). Automatic operator wrapping + can only be used with the binary variant. Ternary pow() must + always be wrapped manually. For a homgeneous ternary pow(), + this is done as usual: +
+    Int power(Int const & first, Int const & second, Int const & module);
+    typedef Int (ternary_function1)(const Int&, const Int&, const Int&);
+    ...
+    int_class.def((ternary_function1)&power,  "__pow__");
+
+ In case you want to support this function with non-uniform argument + types, wrapping is a little more involved. Suppose, you have to wrap: +
+    Int power(Int const & first, int second, int module);
+    Int power(int first, Int const & second, int module);
+    Int power(int first, int second, Int const & module);
+
+ The first variant can be wrapped as usual: +
+    typedef Int (ternary_function2)(const Int&, int, int);
+    int_class.def((ternary_function2)&power,  "__pow__");
+
+ In the second variant, however, Int appears only as second + argument, and in the last one it is the third argument. Therefor we must + first provide functions where the argumant order is changed so that + Int appears in first place: +
+    Int rpower(Int const & second, int first, int module)
+    {
+        return power(first, second, third);
+    }
+    Int rrpower(Int const & third, int first, int second)
+    {
+        return power(first, second, third);
+    }
+
+ These functions must be wrapped under the names "__rpow__" and + "__rrpow__" respectively: +
+    int_class.def((ternary_function2)&rpower,  "__rpow__");
+    int_class.def((ternary_function2)&rrpower,  "__rrpow__");
+
+ Note that "__rrpow__" is an extension not present in plain Python. +

+ Table of Numeric Operators +

+

+ Py_cpp supports the + Python operators listed in the following table. Note that + comparison (__cmp__) and string conversion (__str__) operators are + included in the list, although they are not strictly "numeric". +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Python Operator Name + + Python Expression + + C++ Operator Id + + C++ Expression Used For Automatic Wrapping
+ with cpp_left = from_python(left, + type<Left>()),
+ cpp_right = from_python(right, + type<Right>()),
+ and cpp_oper = from_python(oper, type<Oper>()) +
+ __add__, __radd__ + + left + right + + python::op_add + + cpp_left + cpp_right +
+ __sub__, __rsub__ + + left - right + + python::op_sub + + cpp_left - cpp_right +
+ __mul__, __rmul__ + + left * right + + python::op_mul + + cpp_left * cpp_right +
+ __div__, __rdiv__ + + left / right + + python::op_div + + cpp_left / cpp_right +
+ __mod__, __rmod__ + + left % right + + python::op_mod + + cpp_left % cpp_right +
+ __divmod__, __rdivmod__ + + (quotient, remainder)
+ = divmod(left, right)
+
+ python::op_divmod + + cpp_left / cpp_right  and  cpp_left % + cpp_right +
+ __pow__, __rpow__ + + pow(left, right)
+ (binary power) +
+ python::op_pow + + pow(cpp_left, cpp_right) +
+ __pow__ + + pow(left, right, modulo)
+ (ternary power modulo) +
+ no automatic wrapping, special treatment + required +
+ __lshift__, __rlshift__ + + left << right + + python::op_lshift + + cpp_left << cpp_right +
+ __rshift__, __rrshift__ + + left >> right + + python::op_rshift + + cpp_left >> cpp_right +
+ __and__, __rand__ + + left & right + + python::op_and + + cpp_left & cpp_right +
+ __xor__, __rxor__ + + left ^ right + + python::op_xor + + cpp_left ^ cpp_right +
+ __or__, __ror__ + + left | right + + python::op_or + + cpp_left | cpp_right +
+ __cmp__, __rcmp__ + + cmp(left, right) (3-way compare)
+ left < right
+ left <= right
+ left > right
+ left >= right
+ left == right
+ left != right +
+ python::op_cmp + + cpp_left < cpp_right  and  cpp_right < + cpp_left +
+ __neg__ + + -oper  (unary negation) + + python::op_neg + + -cpp_oper +
+ __pos__ + + +oper  (identity) + + python::op_pos + + +cpp_oper +
+ __abs__ + + abs(oper)  (absolute value) + + python::op_abs + + abs(cpp_oper) +
+ __invert__ + + ~oper  (bitwise inversion) + + python::op_invert + + ~cpp_oper +
+ __int__ + + int(oper)  (integer conversion) + + python::op_int + + long(cpp_oper) +
+ __long__ + + long(oper) 
+ (infinite precision integer conversion) +
+ python::op_long + + PyLong_FromLong(cpp_oper) +
+ __float__ + + float(oper)  (float conversion) + + python::op_float + + double(cpp_oper) +
+ __oct__ + + oct(oper)  (octal conversion) + + must be wrapped manually (wrapped function should return a string) +
+ __hex__ + + hex(oper)  (hex conversion) + + must be wrapped manually (wrapped function should return a string) +
+ __str__ + + str(oper)  (string conversion) + + python::op_str + + std::ostringstream s; s << oper; +
+ __coerce__ + + coerce(left, right) + + usually defined automatically, otherwise + special treatment required +
+ + +

+ Sequence and Mapping Operators +

+ Sequence and mapping operators let wrapped objects behave in accordance + to Python's iteration and access protocols. These protocols differ + considerably from the ones found in C++. For example, Python's typically + iteration idiom looks like  "for i in S:" , while in C++ one + uses  "for(iterator i = S.begin(); i != S.end(); ++i)". One + could try to wrap C++ iterators in order to carry the C++ idiom into + Python. However, this does not work very well because (1) it leads to + non-uniform Python code (wrapped types must be used in a different way + than Python built-in types) and (2) iterators are often implemented as + plain C++ pointers which cannot be wrapped easily because py_cpp is + designed to handle objects only. +

+ Thus, it is a good idea to provide sequence and mapping operators for + your wrapped containers. These operators have to be wrapped manually + because there are no corresponding C++ operators that could be used for + automatic wrapping. The Python documentation lists the relevant + container operators. In particular, expose __getitem__, __setitem__ + and remember to throw the PyExc_IndexError when the index + is out-of-range in order to enable the  "for i in S:"  + idiom. +

+ Here is an example. Suppose, we want to wrap a + std::map<std::size_t,std::string>. This is done as follows + as follows: +

+
+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, python::converters::to_python(key));
+        throw python::error_already_set();
+    }
+}
+
+// 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);
+}
+
+class_builder<StringMap> string_map(my_module, "StringMap");
+string_map.def(python::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[0] = 'zero'
+>>> m[1] = 'one'
+>>> m[2] = 'two'
+>>> m[3] = 'three'
+>>> len(m)
+4
+>>> for i in m:
+...    print i
+...
+zero
+one
+two
+three
+
+
+

+ 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 +

    +
  • + __getattr__<name>__ +
  • + __setattr__<name>__ +
  • + __delattr__<name>__ +
+ 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 class_builder<> and + extension_class<>: +

    +
  • + def_getter(pointer-to-member, name) // + read access to the member via attribute name +
  • + def_setter(pointer-to-member, name) // + write access to the member via attribute name +
  • + def_readonly(pointer-to-member, name) + // read-only access to the member via attribute name +
  • + def_read_write(pointer-to-member, + name) // read/write access to the member via attribute + name +
+

+ 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");
+
+class_builder<Pil> pair_int_long(my_module, "Pair");
+pair_int_long.def(python::constructor<>());
+pair_int_long.def(python::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
+
+
+

+ 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: Inheritance Next: A Peek Under the Hood Up: Top +

+ © Copyright David Abrahams and Ullrich Köthe 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: Nov 21, 2000 +

+ diff --git a/subclass.cpp b/subclass.cpp new file mode 100644 index 00000000..ee28fd72 --- /dev/null +++ b/subclass.cpp @@ -0,0 +1,884 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#include "subclass.h" +#include "functions.h" +#include "singleton.h" +#include +#include "callback.h" +#include +#include "module.h" + +namespace python { + +namespace detail { + void enable_named_method(python::detail::class_base* type_obj, const char* name); +} + +namespace { + // Add the name of the module currently being loaded to the name_space with the + // key "__module__". If no module is being loaded, or if name_space already has + // a key "__module", has no effect. This is not really a useful public + // interface; it's just used for class_t<>::class_t() below. + void add_current_module_name(dictionary&); + + bool is_prefix(const char* s1, const char* s2); + bool is_special_name(const char* name); + void enable_special_methods(python::detail::class_base* derived, const tuple& bases, const dictionary& name_space); + + void report_ignored_exception(PyObject* source) + { + // This bit of code copied wholesale from classobject.c in the Python source. + PyObject *f, *t, *v, *tb; + PyErr_Fetch(&t, &v, &tb); + f = PySys_GetObject(const_cast("stderr")); + if (f != NULL) + { + PyFile_WriteString(const_cast("Exception "), f); + if (t) { + PyFile_WriteObject(t, f, Py_PRINT_RAW); + if (v && v != Py_None) { + PyFile_WriteString(const_cast(": "), f); + PyFile_WriteObject(v, f, 0); + } + } + PyFile_WriteString(const_cast(" in "), f); + PyFile_WriteObject(source, f, 0); + PyFile_WriteString(const_cast(" ignored\n"), f); + PyErr_Clear(); /* Just in case */ + } + Py_XDECREF(t); + Py_XDECREF(v); + Py_XDECREF(tb); + } + + // + // pickle support courtesy of "Ralf W. Grosse-Kunstleve" + // + PyObject* class_reduce(PyObject* klass) + { + return PyObject_GetAttrString(klass, const_cast("__name__")); + } + + ref global_class_reduce() + { + static ref result(detail::new_wrapped_function(class_reduce)); + return result; + } + + + tuple instance_reduce(PyObject* obj) + { + ref instance_class(PyObject_GetAttrString(obj, const_cast("__class__"))); + + ref getinitargs(PyObject_GetAttrString(obj, const_cast("__getinitargs__")), + ref::null_ok); + PyErr_Clear(); + ref initargs; + if (getinitargs.get() != 0) + { + initargs = ref(PyEval_CallObject(getinitargs.get(), NULL)); + initargs = ref(PySequence_Tuple(initargs.get())); + } + else + { + initargs = ref(PyTuple_New(0)); + } + + ref getstate(PyObject_GetAttrString(obj, const_cast("__getstate__")), + ref::null_ok); + PyErr_Clear(); + if (getstate.get() != 0) + { + ref state = ref(PyEval_CallObject(getstate.get(), NULL)); + return tuple(instance_class, initargs, state); + } + + ref state(PyObject_GetAttrString(obj, const_cast("__dict__")), ref::null_ok); + PyErr_Clear(); + if (state.get() != 0 && dictionary(state).size() > 0) + { + return tuple(instance_class, initargs, state); + } + + return tuple(instance_class, initargs); + } + + ref global_instance_reduce() + { + static ref result(detail::new_wrapped_function(instance_reduce)); + return result; + } +} + + +namespace detail { + + class_base::class_base(PyTypeObject* meta_class_obj, string name, tuple bases, const dictionary& name_space) + : type_object_base(meta_class_obj), + m_name(name), + m_bases(bases), + m_name_space(name_space) + { + this->tp_name = const_cast(name.c_str()); + enable(type_object_base::getattr); + enable(type_object_base::setattr); + add_current_module_name(m_name_space); + static const python::string docstr("__doc__", python::string::interned); + if (PyDict_GetItem(m_name_space.get(), docstr.get())== 0) + { + PyDict_SetItem(m_name_space.get(), docstr.get(), Py_None); + } + enable_special_methods(this, bases, name_space); + } + + void class_base::add_base(ref base) + { + tuple new_bases(m_bases.size() + 1); + for (std::size_t i = 0; i < m_bases.size(); ++i) + new_bases.set_item(i, m_bases[i]); + new_bases.set_item(m_bases.size(), base); + m_bases = new_bases; + } + + PyObject* class_base::getattr(const char* name) + { + if (!BOOST_CSTD_::strcmp(name, "__dict__")) + { + PyObject* result = m_name_space.get(); + Py_INCREF(result); + return result; + } + + if (!BOOST_CSTD_::strcmp(name, "__bases__")) + { + PyObject* result = m_bases.get(); + Py_INCREF(result); + return result; + } + + if (!BOOST_CSTD_::strcmp(name, "__name__")) + { + PyObject* result = m_name.get(); + Py_INCREF(result); + return result; + } + + // pickle support courtesy of "Ralf W. Grosse-Kunstleve" + if (!BOOST_CSTD_::strcmp(name, "__safe_for_unpickling__")) + { + return PyInt_FromLong(1); + } + if (!BOOST_CSTD_::strcmp(name, "__reduce__")) + { + ref target(as_object(this), ref::increment_count); + return new bound_function(target, global_class_reduce()); + } + + ref local_attribute = m_name_space.get_item(string(name).reference()); + + if (local_attribute.get()) + return local_attribute.release(); + + // In case there are no bases... + PyErr_SetString(PyExc_AttributeError, name); + + // Check bases + for (std::size_t i = 0; i < m_bases.size(); ++i) + { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) + PyErr_Clear(); // we're going to try a base class + else if (PyErr_Occurred()) + break; // Other errors count, though! + + PyObject* base_attribute = PyObject_GetAttrString(m_bases[i].get(), const_cast(name)); + + if (base_attribute != 0) + { + // Unwind the actual underlying function from unbound Python class + // methods in case of multiple inheritance from real Python + // classes. Python stubbornly insists that the first argument to a + // method must be a true Python instance object otherwise. Do not + // unwrap bound methods; that would interfere with intended semantics. + if (PyMethod_Check(base_attribute) + && reinterpret_cast(base_attribute)->im_self == 0) + { + PyObject* function + = reinterpret_cast(base_attribute)->im_func; + Py_INCREF(function); + Py_DECREF(base_attribute); + return function; + } + else + { + return base_attribute; + } + } + } + return 0; + } + + // Mostly copied wholesale from Python's classobject.c + PyObject* class_base::repr() const + { + PyObject *mod = PyDict_GetItemString( + m_name_space.get(), const_cast("__module__")); + unsigned long address = reinterpret_cast(this); + string result = (mod == NULL || !PyString_Check(mod)) + ? string("") % tuple(m_name, address) + : string("") % tuple(ref(mod, ref::increment_count), m_name, address); + return result.reference().release(); + } + + + int class_base::setattr(const char* name, PyObject* value) + { + if (is_special_name(name) + && BOOST_CSTD_::strcmp(name, "__doc__") != 0 + && BOOST_CSTD_::strcmp(name, "__name__") != 0) + { + python::string message("Special attribute names other than '__doc__' and '__name__' are read-only, in particular: "); + PyErr_SetObject(PyExc_TypeError, (message + name).get()); + throw error_already_set(); + } + + if (PyCallable_Check(value)) + detail::enable_named_method(this, name); + + return PyDict_SetItemString( + m_name_space.reference().get(), const_cast(name), value); + } + + bool class_base::initialize_instance(instance* obj, PyObject* args, PyObject* keywords) + { + // Getting the init function off the obj should result in a + // bound method. + PyObject* const init_function = obj->getattr("__init__", false); + + if (init_function == 0) + { + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); // no __init__? That's legal. + } + else { + return false; // Something else? Keep the error + } + } + else + { + // Manage the reference to the bound function + ref init_function_holder(init_function); + + // Declare a ref to manage the result of calling __init__ (which should be None). + ref init_result( + PyEval_CallObjectWithKeywords(init_function, args, keywords)); + } + return true; + } + + void class_base::instance_dealloc(PyObject* obj) const + { + Py_INCREF(obj); // This allows a __del__ function to revive the obj + + PyObject* exc_type; + PyObject* exc_value; + PyObject* exc_traceback; + PyErr_Fetch(&exc_type, &exc_value, &exc_traceback); + + // This scope ensures that the reference held by del_function doesn't release + // the last reference and delete the object recursively (infinitely). + { + ref del_function; + try { + instance* const target = python::downcast(obj); + del_function = ref(target->getattr("__del__", false), ref::null_ok); + } + catch(...) { + } + + if (del_function.get() != 0) + { + ref result(PyEval_CallObject(del_function.get(), (PyObject *)NULL), ref::null_ok); + + if (result.get() == NULL) + report_ignored_exception(del_function.get()); + } + } + PyErr_Restore(exc_type, exc_value, exc_traceback); + + if (--obj->ob_refcnt <= 0) + delete_instance(obj); + } + + +} + +instance::instance(PyTypeObject* class_) + : python::detail::base_object(class_) +{ +} + +instance::~instance() +{ +} + +PyObject* instance::getattr(const char* name, bool use_special_function) +{ + if (!BOOST_CSTD_::strcmp(name, "__dict__")) + { + if (PyEval_GetRestricted()) { + PyErr_SetString(PyExc_RuntimeError, + "instance.__dict__ not accessible in restricted mode"); + return 0; + } + Py_INCREF(m_name_space.get()); + return m_name_space.get(); + } + + if (!BOOST_CSTD_::strcmp(name, "__class__")) + { + Py_INCREF(this->ob_type); + return as_object(this->ob_type); + } + + if (!BOOST_CSTD_::strcmp(name, "__reduce__")) + { + return new detail::bound_function(ref(this, ref::increment_count), global_instance_reduce()); + } + + ref local_attribute = m_name_space.get_item(string(name).reference()); + + if (local_attribute.get()) + return local_attribute.release(); + + // Check its class. + PyObject* function = + PyObject_GetAttrString(as_object(this->ob_type), const_cast(name)); + + if (function == 0 && !use_special_function) + { + return 0; + } + + ref class_attribute; + if (function != 0) + { + // This will throw if the attribute wasn't found + class_attribute = ref(function); + } + else + { + // Clear the error while we try special methods method (if any). + PyErr_Clear(); + + // First we try the special method that comes from concatenating + // "__getattr__" and and 2 trailing underscores. This is an + // extension to regular Python class functionality. + const string specific_getattr_name(detail::getattr_string() + name + "__"); + PyObject* getattr_method = PyObject_GetAttr( + as_object(this->ob_type), specific_getattr_name.get()); + + // Use just the first arg to PyEval_CallFunction if found + char* arg_format = const_cast("(O)"); + + // Try for the regular __getattr__ method if not found + if (getattr_method == 0) + { + PyErr_Clear(); + getattr_method = PyObject_GetAttrString( + as_object(this->ob_type), const_cast("__getattr__")); + + // Use both args to PyEval_CallFunction + arg_format = const_cast("(Os)"); + } + + // If there is no such method, throw now. + if (PyErr_Occurred()) + { + PyErr_SetString(PyExc_AttributeError, name); + return 0; + } + + // Take ownership of the method + ref owner(getattr_method); + + // Call it to get the attribute. + return PyEval_CallFunction(getattr_method, arg_format, this, name); + } + + if (!PyCallable_Check(class_attribute.get())) + { + PyErr_Clear(); + return class_attribute.release(); + } + else + { + return detail::bound_function::create(ref(this, ref::increment_count), class_attribute); + } +} + +// instance::setattr_dict +// +// Implements setattr() functionality for the "__dict__" attribute +// +int instance::setattr_dict(PyObject* value) +{ + if (PyEval_GetRestricted()) + { + PyErr_SetString(PyExc_RuntimeError, + "__dict__ not accessible in restricted mode"); + return -1; + } + + if (value == 0 || !PyDict_Check(value)) + { + PyErr_SetString(PyExc_TypeError, + "__dict__ must be set to a dictionary"); + return -1; + } + m_name_space = dictionary(ref(value, ref::increment_count)); + return 0; +} + +// instance::setattr - +// +// Implements the setattr() and delattr() functionality for our own instance +// objects, using the standard Python interface: if value == 0, we are deleting +// the attribute, and returns 0 unless an error occurred. +int instance::setattr(const char* name, PyObject* value) +{ + if (BOOST_CSTD_::strcmp(name, "__class__") == 0) + { + PyErr_SetString(PyExc_TypeError, "__class__ attribute is read-only"); + throw error_already_set(); + } + + if (BOOST_CSTD_::strcmp(name, "__dict__") == 0) + return setattr_dict(value); + + // Try to find an appropriate "specific" setter or getter method, either + // __setattr____(value) or __delattr____(). This is an extension + // to regular Python class functionality. + const string& base_name = value ? detail::setattr_string() : detail::delattr_string(); + const string specific_method_name(base_name + name + "__"); + + ref special_method( + PyObject_GetAttr(as_object(this->ob_type), specific_method_name.get()), + ref::null_ok); + + PyObject* result_object = 0; + if (special_method.get() != 0) + { + // The specific function was found; call it now. Note that if value is + // not included in the format string, it is ignored. + char* format_string = const_cast(value ? "(OO)" : "(O)"); + result_object = PyEval_CallFunction(special_method.get(), format_string, this, value); + } + else + { + // If not found, try the usual __setattr__(name, value) or + // __delattr__(name) functions. + PyErr_Clear(); + special_method.reset( + PyObject_GetAttr(as_object(this->ob_type), base_name.get()), + ref::null_ok); + + if (special_method.get() != 0) + { + // The special function was found; call it now. Note that if value + // is not included in the format string, it is ignored. + char* format_string = const_cast(value ? "(OsO)" : "(Os)"); + result_object = PyEval_CallFunction( + special_method.get(), format_string, this, name, value); + } + } + + // If we found an appropriate special method, handle the return value. + if (special_method.get() != 0) + { + ref manage_result(result_object); + return 0; + } + + PyErr_Clear(); // Nothing was found; clear the python error state + + if (value == 0) // Try to remove the attribute from our name space + { + const int result = PyDict_DelItemString(m_name_space.reference().get(), + const_cast(name)); + if (result < 0) + { + PyErr_Clear(); + PyErr_SetString(PyExc_AttributeError, "delete non-existing instance attribute"); + } + return result; + } + else // Change the specified item in our name space + { + return PyDict_SetItemString(m_name_space.reference().get(), + const_cast(name), value); + } +} + +PyObject* instance::call(PyObject* args, PyObject* keywords) +{ + return PyEval_CallObjectWithKeywords( + ref(getattr("__call__")).get(), // take possession of the result from getattr() + args, keywords); +} + +PyObject* instance::repr() +{ + return callback::call_method(this, "__repr__"); +} + +int instance::compare(PyObject* other) +{ + return callback::call_method(this, "__cmp__", other); +} + +PyObject* instance::str() +{ + return callback::call_method(this, "__str__"); +} + +long instance::hash() +{ + return callback::call_method(this, "__hash__"); +} + +int instance::length() +{ + return callback::call_method(this, "__len__"); +} + +PyObject* instance::get_subscript(PyObject* key) +{ + return callback::call_method(this, "__getitem__", key); +} + +void instance::set_subscript(PyObject* key, PyObject* value) +{ + if (value == 0) + callback::call_method(this, "__delitem__", key); + else + callback::call_method(this, "__setitem__", key, value); +} + +PyObject* instance::get_slice(int start, int finish) +{ + return callback::call_method(this, "__getslice__", start, finish); +} + +void instance::set_slice(int start, int finish, PyObject* value) +{ + if (value == 0) + callback::call_method(this, "__delslice__", start, finish); + else + callback::call_method(this, "__setslice__", start, finish, value); +} + +PyObject* instance::add(PyObject* other) +{ + return callback::call_method(this, "__add__", other); +} + +PyObject* instance::subtract(PyObject* other) +{ + return callback::call_method(this, "__sub__", other); +} + +PyObject* instance::multiply(PyObject* other) +{ + return callback::call_method(this, "__mul__", other); +} + +PyObject* instance::divide(PyObject* other) +{ + return callback::call_method(this, "__div__", other); +} + +PyObject* instance::remainder(PyObject* other) +{ + return callback::call_method(this, "__mod__", other); +} + +PyObject* instance::divmod(PyObject* other) +{ + return callback::call_method(this, "__divmod__", other); +} + +PyObject* instance::power(PyObject* exponent, PyObject* modulus) +{ + if (as_object(modulus->ob_type) == Py_None) + return callback::call_method(this, "__pow__", exponent); + else + return callback::call_method(this, "__pow__", exponent, modulus); +} + +PyObject* instance::negative() +{ + return callback::call_method(this, "__neg__"); +} + +PyObject* instance::positive() +{ + return callback::call_method(this, "__pos__"); +} + +PyObject* instance::absolute() +{ + return callback::call_method(this, "__abs__"); +} + +int instance::nonzero() +{ + return callback::call_method(this, "__nonzero__"); +} + +PyObject* instance::invert() +{ + return callback::call_method(this, "__invert__"); +} + +PyObject* instance::lshift(PyObject* other) +{ + return callback::call_method(this, "__lshift__", other); +} + +PyObject* instance::rshift(PyObject* other) +{ + return callback::call_method(this, "__rshift__", other); +} + +PyObject* instance::do_and(PyObject* other) +{ + return callback::call_method(this, "__and__", other); +} + +PyObject* instance::do_xor(PyObject* other) +{ + return callback::call_method(this, "__xor__", other); +} + +PyObject* instance::do_or(PyObject* other) +{ + return callback::call_method(this, "__or__", other); +} + +int instance::coerce(PyObject** x, PyObject** y) +{ + assert(this == *x); + + // Coerce must return a tuple + tuple result(callback::call_method(this, "__coerce__", *y)); + + *x = result[0].release(); + *y = result[1].release(); + return 0; +} + +PyObject* instance::as_int() +{ + return callback::call_method(this, "__int__"); +} + +PyObject* instance::as_long() +{ + return callback::call_method(this, "__long__"); +} + +PyObject* instance::as_float() +{ + return callback::call_method(this, "__float__"); +} + +PyObject* instance::oct() +{ + return callback::call_method(this, "__oct__"); +} + +PyObject* instance::hex() +{ + return callback::call_method(this, "__hex__"); +} + +namespace { + struct named_capability + { + const char* name; + detail::type_object_base::capability capability; + }; + + const named_capability enablers[] = + { + { "__hash__", detail::type_object_base::hash }, + { "__cmp__", detail::type_object_base::compare }, + { "__repr__", detail::type_object_base::repr }, + { "__str__", detail::type_object_base::str }, + { "__call__", detail::type_object_base::call }, + { "__getattr__", detail::type_object_base::getattr }, + { "__setattr__", detail::type_object_base::setattr }, + { "__len__", detail::type_object_base::mapping_length }, + { "__len__", detail::type_object_base::sequence_length }, + { "__getitem__", detail::type_object_base::mapping_subscript }, + { "__getitem__", detail::type_object_base::sequence_item }, + { "__setitem__", detail::type_object_base::mapping_ass_subscript }, + { "__setitem__", detail::type_object_base::sequence_ass_item }, + { "__delitem__", detail::type_object_base::mapping_ass_subscript }, + { "__delitem__", detail::type_object_base::sequence_ass_item }, + { "__getslice__", detail::type_object_base::sequence_slice }, + { "__setslice__", detail::type_object_base::sequence_ass_slice }, + { "__delslice__", detail::type_object_base::sequence_ass_slice }, + { "__add__", detail::type_object_base::number_add }, + { "__sub__", detail::type_object_base::number_subtract }, + { "__mul__", detail::type_object_base::number_multiply }, + { "__div__", detail::type_object_base::number_divide }, + { "__mod__", detail::type_object_base::number_remainder }, + { "__divmod__", detail::type_object_base::number_divmod }, + { "__pow__", detail::type_object_base::number_power }, + { "__neg__", detail::type_object_base::number_negative }, + { "__pos__", detail::type_object_base::number_positive }, + { "__abs__", detail::type_object_base::number_absolute }, + { "__nonzero__", detail::type_object_base::number_nonzero }, + { "__invert__", detail::type_object_base::number_invert }, + { "__lshift__", detail::type_object_base::number_lshift }, + { "__rshift__", detail::type_object_base::number_rshift }, + { "__and__", detail::type_object_base::number_and }, + { "__xor__", detail::type_object_base::number_xor }, + { "__or__", detail::type_object_base::number_or }, + { "__coerce__", detail::type_object_base::number_coerce }, + { "__int__", detail::type_object_base::number_int }, + { "__long__", detail::type_object_base::number_long }, + { "__float__", detail::type_object_base::number_float }, + { "__oct__", detail::type_object_base::number_oct }, + { "__hex__", detail::type_object_base::number_hex } + }; + + bool is_prefix(const char* s1, const char* s2) + { + while (*s1 != 0 && *s2 != 0 && *s1 == *s2) + ++s1, ++s2; + return *s1 == 0; + } + + bool is_special_name(const char* name) + { + if (name[0] != '_' || name[1] != '_' || name[2] == 0 || name[3] == 0) + return false; + + std::size_t name_length = BOOST_CSTD_::strlen(name); + return name[name_length - 1] == '_' && name[name_length - 2] == '_'; + } +} + +namespace detail { + // Enable the special handler for methods of the given name, if any. + void enable_named_method(python::detail::class_base* type_obj, const char* name) + { + const std::size_t num_enablers = sizeof(enablers) / sizeof(enablers[0]); + + // Make sure this ends with "__" since we'll only compare the head of the + // string. This is done to make the __getattr____/__setattr____ + // extension work. + if (!is_special_name(name)) + return; + + for (std::size_t i = 0; i < num_enablers; ++i) + { + if (is_prefix(enablers[i].name + 2, name + 2)) + { + type_obj->enable(enablers[i].capability); + } + } + } +} + +namespace { + // Enable any special methods which are enabled in the base class. + void enable_special_methods(python::detail::class_base* derived, const tuple& bases, const dictionary& name_space) + { + for (std::size_t i = 0; i < bases.size(); ++i) + { + PyObject* base = bases[i].get(); + + for (std::size_t n = 0; n < PY_ARRAY_LENGTH(enablers); ++n) + { + ref attribute( + PyObject_GetAttrString(base, const_cast(enablers[n].name)), + ref::null_ok); + PyErr_Clear(); + if (attribute.get() != 0 && PyCallable_Check(attribute.get())) + detail::add_capability(enablers[n].capability, derived); + } + } + + list keys(name_space.keys()); + for (std::size_t j = 0, len = keys.size(); j < len; ++j) + { + string name_obj(keys.get_item(j)); + const char* name = name_obj.c_str(); + + if (!is_special_name(name)) + continue; + + for (std::size_t i = 0; i < PY_ARRAY_LENGTH(enablers); ++i) + { + if (is_prefix(enablers[i].name + 2, name + 2)) + { + detail::add_capability(enablers[i].capability, derived); + } + } + } + } + + void add_current_module_name(dictionary& name_space) + { + static string module_key("__module__", string::interned); + name_space.set_item(module_key, module_builder::name()); + } +} + +void adjust_slice_indices(PyObject* obj, int& start, int& finish) +{ + int length = callback::call_method(obj, "__len__"); + + // This is standard Python class behavior. + if (start < 0) + start += length; + if (finish < 0) + finish += length; + + // This is not + if (start < 0) + start = 0; + if (finish < 0) + finish = 0; +} + +namespace detail { +const string& setattr_string() +{ + static string x("__setattr__", string::interned); + return x; +} + +const string& getattr_string() +{ + static string x("__getattr__", string::interned); + return x; +} + +const string& delattr_string() +{ + static string x("__delattr__", string::interned); + return x; +} +} + +} // namespace python diff --git a/subclass.h b/subclass.h new file mode 100644 index 00000000..a7679a95 --- /dev/null +++ b/subclass.h @@ -0,0 +1,527 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef SUBCLASS_DWA051500_H_ +# define SUBCLASS_DWA051500_H_ + +# include "pyconfig.h" +# include "newtypes.h" +# include "objects.h" +# include "singleton.h" +# include +# include "py.h" +# include "callback.h" + +namespace python { + +// A simple type which acts something like a built-in Python class obj. +class instance + : public python::detail::python_object +{ + public: + instance(PyTypeObject* class_); + ~instance(); + + // Standard Python functions. + PyObject* repr(); + int compare(PyObject*); + PyObject* str(); + long hash(); + PyObject* call(PyObject* args, PyObject* keywords); + PyObject* getattr(const char* name, bool use_special_function = true); + int setattr(const char* name, PyObject* value); + + // Mapping methods + int length(); + PyObject* get_subscript(PyObject* key); + void set_subscript(PyObject* key, PyObject* value); + + // Sequence methods + PyObject* get_slice(int start, int finish); + void set_slice(int start, int finish, PyObject* value); + + // Number methods + PyObject* add(PyObject* other); + PyObject* subtract(PyObject* other); + PyObject* multiply(PyObject* other); + PyObject* divide(PyObject* other); + PyObject* remainder(PyObject* other); + PyObject* divmod(PyObject* other); + PyObject* power(PyObject*, PyObject*); + PyObject* negative(); + PyObject* positive(); + PyObject* absolute(); + int nonzero(); + PyObject* invert(); + PyObject* lshift(PyObject* other); + PyObject* rshift(PyObject* other); + PyObject* do_and(PyObject* other); + PyObject* do_xor(PyObject* other); + PyObject* do_or(PyObject* other); + int coerce(PyObject**, PyObject**); + PyObject* as_int(); + PyObject* as_long(); + PyObject* as_float(); + PyObject* oct(); + PyObject* hex(); + + private: // noncopyable, without the size bloat + instance(const instance&); + void operator=(const instance&); + + private: // helper functions + int setattr_dict(PyObject* value); + + private: + dictionary m_name_space; +}; + +template class meta_class; + +namespace detail { + class class_base : public type_object_base + { + public: + class_base(PyTypeObject* meta_class_obj, string name, tuple bases, const dictionary& name_space); + tuple bases() const; + string name() const; + dictionary& dict(); + + // Standard Python functions. + PyObject* getattr(const char* name); + int setattr(const char* name, PyObject* value); + PyObject* repr() const; + void add_base(ref base); + + protected: + bool initialize_instance(instance* obj, PyObject* args, PyObject* keywords); + + private: // virtual functions + // Subclasses should override this to delete the particular obj type + virtual void delete_instance(PyObject*) const = 0; + + private: // python::type_object_base required interface implementation + void instance_dealloc(PyObject*) const; // subclasses should not override this + + private: + string m_name; + tuple m_bases; + dictionary m_name_space; + }; + + void enable_named_method(class_base* type_obj, const char* name); +} + +// A type which acts a lot like a built-in Python class. T is the obj type, +// so class_t is a very simple "class-alike". +template +class class_t + : public python::detail::class_base +{ + public: + class_t(meta_class* meta_class_obj, string name, tuple bases, const dictionary& name_space); + + // Standard Python functions. + PyObject* call(PyObject* args, PyObject* keywords); + + private: // Implement mapping methods on instances + PyObject* instance_repr(PyObject*) const; + int instance_compare(PyObject*, PyObject* other) const; + PyObject* instance_str(PyObject*) const; + long instance_hash(PyObject*) const; + int instance_mapping_length(PyObject*) const; + PyObject* instance_mapping_subscript(PyObject*, PyObject*) const; + int instance_mapping_ass_subscript(PyObject*, PyObject*, PyObject*) const; + + private: // Implement sequence methods on instances + int instance_sequence_length(PyObject*) const; + PyObject* instance_sequence_item(PyObject* obj, int n) const; + int instance_sequence_ass_item(PyObject* obj, int n, PyObject* value) const; + PyObject* instance_sequence_slice(PyObject*, int start, int finish) const; + int instance_sequence_ass_slice(PyObject*, int start, int finish, PyObject* value) const; + + private: // Implement number methods on instances + PyObject* instance_number_add(PyObject*, PyObject*) const; + PyObject* instance_number_subtract(PyObject*, PyObject*) const; + PyObject* instance_number_multiply(PyObject*, PyObject*) const; + PyObject* instance_number_divide(PyObject*, PyObject*) const; + PyObject* instance_number_remainder(PyObject*, PyObject*) const; + PyObject* instance_number_divmod(PyObject*, PyObject*) const; + PyObject* instance_number_power(PyObject*, PyObject*, PyObject*) const; + PyObject* instance_number_negative(PyObject*) const; + PyObject* instance_number_positive(PyObject*) const; + PyObject* instance_number_absolute(PyObject*) const; + int instance_number_nonzero(PyObject*) const; + PyObject* instance_number_invert(PyObject*) const; + PyObject* instance_number_lshift(PyObject*, PyObject*) const; + PyObject* instance_number_rshift(PyObject*, PyObject*) const; + PyObject* instance_number_and(PyObject*, PyObject*) const; + PyObject* instance_number_xor(PyObject*, PyObject*) const; + PyObject* instance_number_or(PyObject*, PyObject*) const; + int instance_number_coerce(PyObject*, PyObject**, PyObject**) const; + PyObject* instance_number_int(PyObject*) const; + PyObject* instance_number_long(PyObject*) const; + PyObject* instance_number_float(PyObject*) const; + PyObject* instance_number_oct(PyObject*) const; + PyObject* instance_number_hex(PyObject*) const; + + private: // Miscellaneous "special" methods + PyObject* instance_call(PyObject* obj, PyObject* args, PyObject* keywords) const; + PyObject* instance_getattr(PyObject* obj, const char* name) const; + int instance_setattr(PyObject* obj, const char* name, PyObject* value) const; + + private: // Implementation of python::detail::class_base required interface + void delete_instance(PyObject*) const; + + private: // noncopyable, without the size bloat + class_t(const class_t&); + void operator=(const class_t&); +}; + +// The type of a class_t object. +template +class meta_class + : public python::detail::reprable< + python::detail::callable< + python::detail::getattrable< + python::detail::setattrable< + python::detail::type_object > > > > >, + boost::noncopyable +{ + public: + meta_class(); + + // Standard Python functions. + PyObject* call(PyObject* args, PyObject* keywords); + + struct type_object + : python::detail::singleton > > + { + type_object() : singleton_base(&PyType_Type) {} + }; +}; + +// +// Member function implementations. +// +template +meta_class::meta_class() + : properties(type_object::instance()) +{ +} + +template +class_t::class_t(meta_class* meta_class_obj, string name, tuple bases, const dictionary& name_space) + : python::detail::class_base(meta_class_obj, name, bases, name_space) +{ +} + +template +void class_t::delete_instance(PyObject* obj) const +{ + delete downcast(obj); +} + +template +PyObject* class_t::call(PyObject* args, PyObject* keywords) +{ + reference result(new T(this)); + if (!this->initialize_instance(result.get(), args, keywords)) + return 0; + else + return result.release(); +} + +template +PyObject* class_t::instance_repr(PyObject* obj) const +{ + return downcast(obj)->repr(); +} + +template +int class_t::instance_compare(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->compare(other); +} + +template +PyObject* class_t::instance_str(PyObject* obj) const +{ + return downcast(obj)->str(); +} + +template +long class_t::instance_hash(PyObject* obj) const +{ + return downcast(obj)->hash(); +} + +template +int class_t::instance_mapping_length(PyObject* obj) const +{ + return downcast(obj)->length(); +} + +template +int class_t::instance_sequence_length(PyObject* obj) const +{ + return downcast(obj)->length(); +} + +template +PyObject* class_t::instance_mapping_subscript(PyObject* obj, PyObject* key) const +{ + return downcast(obj)->get_subscript(key); +} + +template +PyObject* class_t::instance_sequence_item(PyObject* obj, int n) const +{ + ref key(to_python(n)); + return downcast(obj)->get_subscript(key.get()); +} + +template +int class_t::instance_sequence_ass_item(PyObject* obj, int n, PyObject* value) const +{ + ref key(to_python(n)); + downcast(obj)->set_subscript(key.get(), value); + return 0; +} + +template +int class_t::instance_mapping_ass_subscript(PyObject* obj, PyObject* key, PyObject* value) const +{ + downcast(obj)->set_subscript(key, value); + return 0; +} + +void adjust_slice_indices(PyObject* obj, int& start, int& finish); + +template +PyObject* class_t::instance_sequence_slice(PyObject* obj, int start, int finish) const +{ + adjust_slice_indices(obj, start, finish); + return downcast(obj)->get_slice(start, finish); +} + +template +int class_t::instance_sequence_ass_slice(PyObject* obj, int start, int finish, PyObject* value) const +{ + adjust_slice_indices(obj, start, finish); + downcast(obj)->set_slice(start, finish, value); + return 0; +} + +template +PyObject* class_t::instance_call(PyObject* obj, PyObject* args, PyObject* keywords) const +{ + return downcast(obj)->call(args, keywords); +} + +template +PyObject* class_t::instance_getattr(PyObject* obj, const char* name) const +{ + return downcast(obj)->getattr(name); +} + + +template +int class_t::instance_setattr(PyObject* obj, const char* name, PyObject* value) const +{ + return downcast(obj)->setattr(name, value); +} + +template +PyObject* class_t::instance_number_add(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->add(other); +} + +template +PyObject* class_t::instance_number_subtract(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->subtract(other); +} + +template +PyObject* class_t::instance_number_multiply(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->multiply(other); +} + +template +PyObject* class_t::instance_number_divide(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->divide(other); +} + +template +PyObject* class_t::instance_number_remainder(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->remainder(other); +} + +template +PyObject* class_t::instance_number_divmod(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->divmod(other); +} + +template +PyObject* class_t::instance_number_power(PyObject* obj, PyObject* exponent, PyObject* modulus) const +{ + return downcast(obj)->power(exponent, modulus); +} + +template +PyObject* class_t::instance_number_negative(PyObject* obj) const +{ + return downcast(obj)->negative(); +} + +template +PyObject* class_t::instance_number_positive(PyObject* obj) const +{ + return downcast(obj)->positive(); +} + +template +PyObject* class_t::instance_number_absolute(PyObject* obj) const +{ + return downcast(obj)->absolute(); +} + +template +int class_t::instance_number_nonzero(PyObject* obj) const +{ + return downcast(obj)->nonzero(); +} + +template +PyObject* class_t::instance_number_invert(PyObject* obj) const +{ + return downcast(obj)->invert(); +} + +template +PyObject* class_t::instance_number_lshift(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->lshift(other); +} + +template +PyObject* class_t::instance_number_rshift(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->rshift(other); +} + +template +PyObject* class_t::instance_number_and(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->do_and(other); +} + +template +PyObject* class_t::instance_number_xor(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->do_xor(other); +} + +template +PyObject* class_t::instance_number_or(PyObject* obj, PyObject* other) const +{ + return downcast(obj)->do_or(other); +} + +template +int class_t::instance_number_coerce(PyObject* obj, PyObject** x, PyObject** y) const +{ + return downcast(obj)->coerce(x, y); +} + +template +PyObject* class_t::instance_number_int(PyObject* obj) const +{ + return downcast(obj)->as_int(); +} + +template +PyObject* class_t::instance_number_long(PyObject* obj) const +{ + return downcast(obj)->as_long(); +} + +template +PyObject* class_t::instance_number_float(PyObject* obj) const +{ + return downcast(obj)->as_float(); +} + +template +PyObject* class_t::instance_number_oct(PyObject* obj) const +{ + return downcast(obj)->oct(); +} + +template +PyObject* class_t::instance_number_hex(PyObject* obj) const +{ + return downcast(obj)->hex(); +} + +namespace detail { + inline dictionary& class_base::dict() + { + return m_name_space; + } + + inline tuple class_base::bases() const + { + return m_bases; + } +} + +template +PyObject* meta_class::call(PyObject* args, PyObject* /*keywords*/) +{ + PyObject* name; + PyObject* bases; + PyObject* name_space; + + if (!PyArg_ParseTuple(args, const_cast("O!O!O!"), + &PyString_Type, &name, + &PyTuple_Type, &bases, + &PyDict_Type, &name_space)) + { + return 0; + } + + return as_object( + new class_t(this, string(ref(name, ref::increment_count)), + tuple(ref(bases, ref::increment_count)), + dictionary(ref(name_space, ref::increment_count))) + ); +} + +namespace detail { + const string& setattr_string(); + const string& getattr_string(); + const string& delattr_string(); + + inline string class_base::name() const + { + return m_name; + } +} + + +} // namespace python +#endif diff --git a/test_example1.py b/test_example1.py new file mode 100644 index 00000000..0e3a9a18 --- /dev/null +++ b/test_example1.py @@ -0,0 +1,50 @@ +r''' +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +That's it! If we build this shared library and put it on our PYTHONPATH we can +now access our C++ class and function from Python. + + >>> import hello + >>> hi_world = hello.world(3) + >>> hi_world.get() + 'hi, world' + >>> hello.length(hi_world) + 9 + +We can even make a subclass of hello.world: + + + >>> class my_subclass(hello.world): + ... def get(self): + ... return 'hello, world' + ... + >>> y = my_subclass(2) + >>> y.get() + 'hello, world' + +Pretty cool! You can't do that with an ordinary Python extension type! + + >>> hello.length(y) + 9 + +Of course, you may now have a slightly empty feeling in the pit of your little +pythonic stomach. Perhaps you feel your subclass deserves to have a length() of +12? If so, read on... +''' +from hello import * + +def run(args = None): + if args is not None: + import sys + sys.argv = args + import doctest, test_example1 + doctest.testmod(test_example1) + +if __name__ == '__main__': + run() diff --git a/test_extclass.py b/test_extclass.py new file mode 100644 index 00000000..578197f1 --- /dev/null +++ b/test_extclass.py @@ -0,0 +1,1087 @@ +r''' +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +Automatic checking of the number and type of arguments. Foo's constructor takes +a single long parameter. + + >>> ext = Foo() + Traceback (innermost last): + File "", line 1, in ? + TypeError: function requires exactly 1 argument; 0 given + + >>> try: ext = Foo('foo') + ... except TypeError, err: + ... assert re.match( + ... '(illegal argument type for built-in operation)|(an integer is required)', str(err)) + ... else: print 'no exception' + + >>> ext = Foo(1) + +Call a virtual function. This call takes a trip into C++ where +FooCallback::add_len() looks up the Python "add_len" attribute and finds the +wrapper for FooCallback::default_add_len(), which in turn calls Foo::add_len(). + + >>> ext.add_len('hello') + 6 + >>> ext.set(3) + >>> ext.add_len('hello') + 8 + +Call a pure virtual function which should have been overridden, but was not. + + >>> ext.call_pure() + Traceback (innermost last): + File "", line 1, in ? + AttributeError: pure + +We can subclass Foo. + + >>> class Subclass(Foo): + ... def __init__(self, seq): + ... Foo.__init__(self, len(seq)) + ... + ... def pure(self): + ... return 'not pure anymore!' + ... + ... def get(self): + ... return Foo.add_len(self, '') + ... + ... def add_len(self, s): + ... print 'called add_len()' + ... return self.get() + len(s) + ... + >>> b = Subclass('yippee') + >>> b.get() + 6 + >>> b.mumble() + 'mumble' + >>> b.call_pure() + 'not pure anymore!' + +If no __init__ function is defined, the one from the base class takes effect, just +like in a Python class. + + >>> class DemonstrateInitPassthru(Foo): pass + ... + >>> q = DemonstrateInitPassthru(1) + >>> q.add_len("x") + 2 + +If we don't initialize the base class, we'll get a RuntimeError when we try to +use its methods. The test illustrates the kind of error to expect. + + >>> class BadSubclass(Foo): + ... def __init__(self): pass + ... + >>> barf = BadSubclass() + >>> barf.set(4) + Traceback (innermost last): + ... + RuntimeError: __init__ function for extension class 'Foo' was never called. + +Here we are tesing that the simple definition procedure used in the C++ demo +file for classes without any virtual functions actually worked. + + >>> bar = Bar(3, 4) + >>> bar.first() + 3 + >>> bar.second() + 4 + >>> baz = Baz() + +We can actually return the wrapped classes by value + + >>> baz.pass_bar(bar).first() + 3 + >>> bar.pass_baz(baz) is baz # A copy of the return value is made. + 0 + >>> type(bar.pass_baz(baz)) is type(baz) + 1 + +And, yes, we can multiply inherit from these classes. + + >>> class MISubclass(Subclass, Bar): + ... def __init__(self, s): + ... Subclass.__init__(self, s) + ... Bar.__init__(self, 0, len(s)) + ... + >>> mi = MISubclass('xx') + >>> mi.first() + 0 + >>> mi.second() + 2 + >>> mi.mumble() + 'mumble' + +We can even mulitply inherit from built-in Python classes, even if they are +first in the list of bases + + >>> class RealPythonClass: + ... def real_python_method(self): + ... print 'RealPythonClass.real_python_method()' + ... def other_first(self, other): + ... return other.first() + + >>> class MISubclass2(RealPythonClass, Bar): + ... def new_method(self): + ... print 'MISubclass2.new_method()' + ... bound_function = RealPythonClass().other_first + ... + >>> mi2 = MISubclass2(7, 8) + >>> mi2.first() # we can call inherited member functions from Bar + 7 + >>> mi2.real_python_method() # we can call inherited member functions from RealPythonClass + RealPythonClass.real_python_method() + + >>> mi2.new_method() # we can call methods on the common derived class + MISubclass2.new_method() + + We can call unbound methods from the base class accessed through the derived class + >>> MISubclass2.real_python_method(mi2) + RealPythonClass.real_python_method() + + We have not interfered with ordinary python bound methods + >>> MISubclass2.bound_function(mi2) + 7 + >>> mi2.bound_function() + 7 + +Any object whose class is derived from Bar can be passed to a function expecting +a Bar parameter: + + >>> baz.pass_bar(mi).first() + 0 + +But objects not derived from Bar cannot: + + >>> baz.pass_bar(baz) + Traceback (innermost last): + ... + TypeError: extension class 'Baz' is not convertible into 'Bar'. + +The clone function on Baz returns a smart pointer; we wrap it into an +extension_instance and make it look just like any other Baz obj. + + >>> baz_clone = baz.clone() + >>> baz_clone.pass_bar(mi).first() + 0 + +Functions expecting an std::auto_ptr parameter will not accept a raw Baz + + >>> try: baz.eat_baz(Baz()) + ... except RuntimeError, err: + ... assert re.match("Object of extension class 'Baz' does not wrap <.*>.", + ... str(err)) + ... else: + ... print 'no exception' + +We can pass std::auto_ptr where it is expected + + >>> baz.eat_baz(baz_clone) + +And if the auto_ptr has given up ownership? + + # MSVC6 ships with an outdated auto_ptr that doesn't get zeroed out when it + # gives up ownership. If you are using MSVC6 without the new Dinkumware + # library, SGI STL or the STLport, expect this test to crash unless you put + # --broken-auto-ptr on the command line. + >>> if not '--broken-auto-ptr' in sys.argv: + ... try: baz_clone.clone() + ... except RuntimeError, err: + ... assert re.match('Converting from python, pointer or smart pointer to <.*> is NULL.', str(err)) + ... else: + ... print 'no exeption' + +Polymorphism also works: + + >>> polymorphic_foo = baz.create_foo() + >>> polymorphic_foo.call_pure() + 'this was never pure!' + >>> baz.get_foo_value(polymorphic_foo) + 1000 + +Pickling tests: + + >>> world.__module__ + 'demo' + >>> world.__safe_for_unpickling__ + 1 + >>> world.__reduce__() + 'world' + >>> reduced = world('Hello').__reduce__() + >>> reduced[0] == world + 1 + >>> reduced[1:] + (('Hello',), (0,)) + >>> import StringIO + >>> import cPickle + >>> pickle = cPickle + >>> for number in (24, 42): + ... wd = world('California') + ... wd.set_secret_number(number) + ... # Dump it out and read it back in. + ... f = StringIO.StringIO() + ... pickle.dump(wd, f) + ... f = StringIO.StringIO(f.getvalue()) + ... wl = pickle.load(f) + ... # + ... print wd.greet(), wd.get_secret_number() + ... print wl.greet(), wl.get_secret_number() + ... + Hello from California! 24 + Hello from California! 24 + Hello from California! 42 + Hello from California! 0 + +Special member attributes. Tests courtesy of Barry Scott + + >>> class DerivedFromFoo(Foo): + ... def __init__(self): + ... Foo.__init__( self, 1 ) + ... def fred(self): + ... 'Docs for DerivedFromFoo.fred' + ... print 'Barry.fred' + ... def __del__(self): + ... print 'Deleting DerivedFromFoo' + + >>> class Base: + ... i_am_base = 'yes' + ... def fred(self): + ... 'Docs for Base.fred' + ... pass + + + >>> class DerivedFromBase(Base): + ... i_am_derived_from_base = 'yes' + ... def fred(self): + ... 'Docs for DerivedFromBase.fred' + ... pass + + >>> df = DerivedFromFoo() + >>> dir(df) + [] + >>> dir(DerivedFromFoo) + ['__del__', '__doc__', '__init__', '__module__', 'fred'] + >>> df.__dict__ + {} + + >>> df.fred.__doc__ + 'Docs for DerivedFromFoo.fred' + >>> db = DerivedFromBase() + >>> dir(db) + [] + >>> dir(DerivedFromBase) + ['__doc__', '__module__', 'fred', 'i_am_derived_from_base'] + >>> db.__dict__ + {} + >>> db.fred.__doc__ + 'Docs for DerivedFromBase.fred' + +Special member functions in action + >>> del df + Deleting DerivedFromFoo + + # force method table sharing + >>> class DerivedFromStringMap(StringMap): pass + ... + + >>> m = StringMap() + +__getitem__() + >>> m[1] + Traceback (innermost last): + File "", line 1, in ? + KeyError: 1 + +__setitem__() + + >>> m[1] = 'hello' + +__getitem__() + >>> m[1] + 'hello' + +__delitem__() + >>> del m[1] + >>> m[1] # prove that it's gone + Traceback (innermost last): + File "", line 1, in ? + KeyError: 1 + +__delitem__() + >>> del m[2] + Traceback (innermost last): + File "", line 1, in ? + KeyError: 2 + +__length__() + >>> len(m) + 0 + >>> m[3] = 'farther' + >>> len(m) + 1 + +Check for sequence/mapping confusion: + >>> for x in m: + ... print x + ... + Traceback (innermost last): + File "", line 1, in ? + KeyError: 0 + +Check for the ability to pass a non-const reference as a constructor parameter + >>> x = Fubar(Foo(1)) + +Some simple overloading tests: + >>> r = Range(3) + >>> print str(r) + (3, 3) + >>> r.start + 3 + >>> r.finish + 3 + >>> r.__len__() + 0 + >>> r.__len__(4) + >>> r.finish + 7 + >>> try: r = Range('yikes') + ... except TypeError, e: + ... assert re.match( + ... 'No overloaded functions match [(]Range, string[)]\. Candidates are:\n.*\n.*', + ... str(e)) + ... else: print 'no exception' + +Sequence tests: + >>> len(Range(3, 10)) + 7 + + >>> map(lambda x:x, Range(3, 10)) + [3, 4, 5, 6, 7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[-2:]) + [8, 9] + + >>> map(lambda x:x, Range(3, 10)[:-4]) + [3, 4, 5] + + >>> map(lambda x:x, Range(3, 10)[4:]) + [7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[4:100]) + [7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[20:]) + [] + + >>> map(lambda x:x, Range(3, 10)[0:4]) + [3, 4, 5, 6] + +Numeric tests: + >>> x = Rational(2,3) + >>> y = Rational(1,4) + >>> print x + y + 11/12 + >>> print x - y + 5/12 + >>> print x * y + 1/6 + >>> print x / y + 8/3 + >>> print x + 1 # testing coercion + 5/3 + >>> print 1 + x # coercion the other way + 5/3 + +delete non-existent attribute: + del m.foobar + Traceback (innermost last): + File "", line 1, in ? + AttributeError: delete non-existing obj attribute + +Testing __getattr__ and __getattr__: + + >>> n = IntPair(1, 2) + >>> n.first + 1 + >>> n.second + 2 + >>> n.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: third + +Testing __setattr__ and __setattr__: + >>> n.first = 33 # N.B __setattr__first sets first to + >>> n.first # the negative of its argument. + -33 + >>> n.second = 66 + >>> n.second + 66 + +Testing __delattr__ and __delattr__: + >>> del n.first + Traceback (innermost last): + File "", line 1, in ? + AttributeError: first can't be deleted! + >>> del n.second + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + >>> del n.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + + # Now show that we can override it. + + >>> class IntTriple(IntPair): + ... def __getattr__(self, s): + ... if s in ['first', 'second']: + ... return IntPair.__getattr__(self, s) + ... elif s == 'third': + ... return 3 + ... else: + ... raise AttributeError(s) + ... + ... # Also show that __setattr__ is supported + ... def __setattr__(self, name, value): + ... raise AttributeError('no writable attributes') + ... + >>> p = IntTriple(0, 1) + >>> p.first + 0 + >>> p.second + 1 + >>> p.third + 3 + >>> p.bax + Traceback (innermost last): + File "", line 1, in ? + AttributeError: bax + >>> p.third = 'yes' + Traceback (innermost last): + File "", line 1, in ? + AttributeError: no writable attributes + >>> del p.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + +demonstrate def_readonly, def_read_write: + >>> sp = StringPair("hello", "world") + >>> sp.first # first is read-only + 'hello' + >>> first_string(sp) # prove that we're not just looking in sp's __dict__ + 'hello' + >>> sp.first = 'hi' # we're not allowed to change it + Traceback (innermost last): + File "", line 1, in ? + AttributeError: 'first' attribute is read-only + >>> first_string(sp) # prove that it hasn't changed + 'hello' + + >>> sp.second # second is read/write + 'world' + >>> second_string(sp) + 'world' + >>> sp.second = 'universe' # set the second attribute + >>> sp.second + 'universe' + >>> second_string(sp) # this proves we didn't just set it in sp's __dict__ + 'universe' + +some __str__ and __repr__ tests: + >>> sp + ('hello', 'universe') + >>> repr(sp) + "('hello', 'universe')" + >>> str(sp) + "('hello', 'universe')" + + Range has a __str__ function but not a __repr__ function + >>> range = Range(5, 20) + >>> str(range) + '(5, 20)' + >>> assert re.match('', repr(range)) + +__hash__ and __cmp__ tests: + # Range has both __hash__ and __cmp__, thus is hashable + >>> colors = { Range(3,4): 'blue', Range(7,9): 'red' } + >>> colors[Range(3,4)] + 'blue' + + # StringPair has only __cmp__ + >>> { StringPair('yo', 'eddy'): 1 } + Traceback (innermost last): + File "", line 1, in ? + TypeError: unhashable type + + # But it can be sorted + >>> stringpairs = [ StringPair('yo', 'eddy'), StringPair('yo', 'betty'), sp ] + >>> stringpairs.sort() + >>> stringpairs + [('hello', 'universe'), ('yo', 'betty'), ('yo', 'eddy')] + +make_pair is a global function in the module. + + >>> couple = make_pair(3,12) + >>> couple.first + 3 + >>> couple.second + 12 + +Testing __call__: + >>> couple2 = make_pair(3, 7) + >>> comparator = CompareIntPair() + >>> comparator(couple, couple) + 0 + >>> comparator(couple, couple2) + 0 + >>> comparator(couple2, couple) + 1 + +Testing overloaded free functions + >>> overloaded() + 'Hello world!' + >>> overloaded(1) + 1 + >>> overloaded('foo') + 'foo' + >>> overloaded(1,2) + 3 + >>> overloaded(1,2,3) + 6 + >>> overloaded(1,2,3,4) + 10 + >>> overloaded(1,2,3,4,5) + 15 + >>> try: overloaded(1, 'foo') + ... except TypeError, err: + ... assert re.match("No overloaded functions match \(int, string\)\. Candidates are:", + ... str(err)) + ... else: + ... print 'no exception' + +Testing overloaded constructors + + >>> over = OverloadTest() + >>> over.getX() + 1000 + >>> over = OverloadTest(1) + >>> over.getX() + 1 + >>> over = OverloadTest(1,1) + >>> over.getX() + 2 + >>> over = OverloadTest(1,1,1) + >>> over.getX() + 3 + >>> over = OverloadTest(1,1,1,1) + >>> over.getX() + 4 + >>> over = OverloadTest(1,1,1,1,1) + >>> over.getX() + 5 + >>> over = OverloadTest(over) + >>> over.getX() + 5 + >>> try: over = OverloadTest(1, 'foo') + ... except TypeError, err: + ... assert re.match("No overloaded functions match \(OverloadTest, int, string\)\. Candidates are:", + ... str(err)) + ... else: + ... print 'no exception' + +Testing overloaded methods + + >>> over.setX(3) + >>> over.overloaded() + 3 + >>> over.overloaded(1) + 1 + >>> over.overloaded(1,1) + 2 + >>> over.overloaded(1,1,1) + 3 + >>> over.overloaded(1,1,1,1) + 4 + >>> over.overloaded(1,1,1,1,1) + 5 + >>> try: over.overloaded(1,'foo') + ... except TypeError, err: + ... assert re.match("No overloaded functions match \(OverloadTest, int, string\)\. Candidates are:", + ... str(err)) + ... else: + ... print 'no exception' + +Testing base class conversions + + >>> testUpcast(over) + Traceback (innermost last): + TypeError: extension class 'OverloadTest' is not convertible into 'Base'. + >>> der1 = Derived1(333) + >>> der1.x() + 333 + >>> testUpcast(der1) + 333 + >>> der1 = derived1Factory(1000) + >>> testDowncast1(der1) + 1000 + >>> testDowncast2(der1) + Traceback (innermost last): + TypeError: extension class 'Base' is not convertible into 'Derived2'. + >>> der2 = Derived2(444) + >>> der2.x() + 444 + >>> testUpcast(der2) + 444 + >>> der2 = derived2Factory(1111) + >>> testDowncast2(der2) + Traceback (innermost last): + TypeError: extension class 'Base' is not convertible into 'Derived2'. + +Testing interaction between callbacks, base declarations, and overloading +- testCallback() calls callback() (within C++) +- callback() is overloaded (in the wrapped class CallbackTest) +- callback() is redefined in RedefineCallback (overloading is simulated by type casing) +- testCallback() should use the redefined callback() + + >>> c = CallbackTest() + >>> c.testCallback(1) + 2 + >>> c.testCallback('foo') + Traceback (innermost last): + File "", line 1, in ? + TypeError: illegal argument type for built-in operation + >>> c.callback(1) + 2 + >>> c.callback('foo') + 'foo 1' + + >>> import types + >>> class RedefineCallback(CallbackTest): + ... def callback(self, x): + ... if type(x) is types.IntType: + ... return x - 2 + ... else: + ... return CallbackTest.callback(self,x) + ... + >>> r = RedefineCallback() + >>> r.callback(1) + -1 + >>> r.callback('foo') + 'foo 1' + >>> r.testCallback('foo') + Traceback (innermost last): + File "", line 1, in ? + TypeError: illegal argument type for built-in operation + >>> r.testCallback(1) + -1 + >>> testCallback(r, 1) + -1 + +Regression test for a reference-counting bug thanks to Mark Evans +() + >>> sizelist([]) + 0.0 + >>> sizelist([1, 2, 4]) + 3.0 + +And another for doubles + >>> vector_double().push_back(3.0) + +Tests for method lookup in the context of inheritance +Set up the tests + + >>> a1 = A1() + >>> a2 = A2() + >>> b1 = B1() + >>> b2 = B2() + >>> pa1_a1 = factoryA1asA1() + >>> pb1_a1 = factoryB1asA1() + >>> pb2_a1 = factoryB2asA1() + >>> pc_a1 = factoryCasA1() + >>> pa2_a2 = factoryA2asA2() + >>> pb1_a2 = factoryB1asA2() + >>> pb1_b1 = factoryB1asB1() + >>> pc_b1 = factoryCasB1() + >>> class DA1(A1): + ... def overrideA1(self): + ... return 'DA1.overrideA1' + ... + >>> da1 = DA1() + >>> class DB1(B1): + ... def overrideA1(self): + ... return 'DB1.overrideA1' + ... def overrideB1(self): + ... return 'DB1.overrideB1' + ... + >>> db1 = DB1() + >>> class DB2(B2): pass + ... + >>> db2 = DB2() + +test overrideA1 + + >>> a1.overrideA1() + 'A1::overrideA1' + >>> b1.overrideA1() + 'B1::overrideA1' + >>> b2.overrideA1() + 'B2::overrideA1' + >>> da1.overrideA1() + 'DA1.overrideA1' + >>> db1.overrideA1() + 'DB1.overrideA1' + >>> pa1_a1.overrideA1() + 'A1::overrideA1' + >>> pb1_a1.overrideA1() + 'B1::overrideA1' + >>> pb2_a1.overrideA1() + 'B2::overrideA1' + >>> pb1_b1.overrideA1() + 'B1::overrideA1' + >>> pc_a1.overrideA1() + 'B1::overrideA1' + >>> pc_b1.overrideA1() + 'B1::overrideA1' + +test call_overrideA1 + + >>> call_overrideA1(a1) + 'A1::overrideA1' + >>> call_overrideA1(b1) + 'B1::overrideA1' + >>> call_overrideA1(b2) + 'B2::overrideA1' + >>> call_overrideA1(da1) + 'DA1.overrideA1' + >>> call_overrideA1(db1) + 'DB1.overrideA1' + >>> call_overrideA1(pa1_a1) + 'A1::overrideA1' + >>> call_overrideA1(pb1_a1) + 'B1::overrideA1' + >>> call_overrideA1(pb2_a1) + 'B2::overrideA1' + >>> call_overrideA1(pb1_b1) + 'B1::overrideA1' + >>> call_overrideA1(pc_a1) + 'B1::overrideA1' + >>> call_overrideA1(pc_b1) + 'B1::overrideA1' + +test inheritA1 + + >>> a1.inheritA1() + 'A1::inheritA1' + >>> b1.inheritA1() + 'A1::inheritA1' + >>> b2.inheritA1() + 'A1::inheritA1' + >>> da1.inheritA1() + 'A1::inheritA1' + >>> db1.inheritA1() + 'A1::inheritA1' + >>> pa1_a1.inheritA1() + 'A1::inheritA1' + >>> pb1_a1.inheritA1() + 'A1::inheritA1' + >>> pb2_a1.inheritA1() + 'A1::inheritA1' + >>> pb1_b1.inheritA1() + 'A1::inheritA1' + >>> pc_a1.inheritA1() + 'A1::inheritA1' + >>> pc_b1.inheritA1() + 'A1::inheritA1' + +test call_inheritA1 + + >>> call_inheritA1(a1) + 'A1::inheritA1' + >>> call_inheritA1(b1) + 'A1::inheritA1' + >>> call_inheritA1(b2) + 'A1::inheritA1' + >>> call_inheritA1(da1) + 'A1::inheritA1' + >>> call_inheritA1(db1) + 'A1::inheritA1' + >>> call_inheritA1(pa1_a1) + 'A1::inheritA1' + >>> call_inheritA1(pb1_a1) + 'A1::inheritA1' + >>> call_inheritA1(pb2_a1) + 'A1::inheritA1' + >>> call_inheritA1(pb1_b1) + 'A1::inheritA1' + >>> call_inheritA1(pc_a1) + 'A1::inheritA1' + >>> call_inheritA1(pc_b1) + 'A1::inheritA1' + +test inheritA2 + + >>> a2.inheritA2() + 'A2::inheritA2' + >>> b1.inheritA2() + 'A2::inheritA2' + >>> b2.inheritA2() + 'A2::inheritA2' + >>> db1.inheritA2() + 'A2::inheritA2' + >>> pa2_a2.inheritA2() + 'A2::inheritA2' + >>> pb1_a2.inheritA2() + 'A2::inheritA2' + >>> pb1_b1.inheritA2() + 'A2::inheritA2' + +test overrideB1 + + >>> b1.overrideB1() + 'B1::overrideB1' + >>> db1.overrideB1() + 'DB1.overrideB1' + >>> pb1_b1.overrideB1() + 'B1::overrideB1' + >>> pc_b1.overrideB1() + 'C::overrideB1' + +test call_overrideB1 + + >>> call_overrideB1(b1) + 'B1::overrideB1' + >>> call_overrideB1(db1) + 'DB1.overrideB1' + >>> call_overrideB1(pb1_a1) + 'B1::overrideB1' + >>> call_overrideB1(pc_a1) + 'C::overrideB1' + >>> call_overrideB1(pb1_b1) + 'B1::overrideB1' + >>> call_overrideB1(pc_b1) + 'C::overrideB1' + +test inheritB2 + + >>> b2.inheritB2() + 'B2::inheritB2' + >>> db2.inheritB2() + 'B2::inheritB2' + +========= test the new def_raw() feature ========== + + >>> r = RawTest(1) + >>> raw(r,1,third=1,fourth=1) + 4 + >>> r.raw(1,third=1,fourth=1) + 4 + >>> raw(r,1,third=1,f=1) + Traceback (innermost last): + KeyError: fourth + >>> raw(r,1,third=1) + Traceback (innermost last): + TypeError: wrong number of arguments + >>> raw(r,1) + Traceback (innermost last): + TypeError: wrong number of arguments + >>> raw() + Traceback (innermost last): + TypeError: wrong number of arguments + >>> raw1(1,second=1) + 2 + >>> raw1(1) + 1 + >>> raw1(second=1) + 1 + >>> raw1() + 0 + >>> raw2(1,second=1) + 2 + >>> raw2(1) + 1 + >>> raw2(second=1) + 1 + >>> raw2() + 0 + +========= test export of operators ========== + + >>> i = Int(2) + >>> j = i+i + >>> j.i() + 4 + >>> j = i-i + >>> j.i() + 0 + >>> j = i*i + >>> j.i() + 4 + >>> i>> cmp(i,i) + 0 + >>> k = Int(5) + >>> j = divmod(k, i) + >>> j[0].i() + 2 + >>> j[1].i() + 1 + >>> j = pow(i, k) + >>> j.i() + 32 + >>> j = pow(i, k, k) + >>> j.i() + 2 + >>> j = -i + >>> j.i() + -2 + >>> str(i) + '2' + >>> j = i/i + Traceback (innermost last): + TypeError: bad operand type(s) for / + >>> j = abs(i) + Traceback (innermost last): + TypeError: bad operand type for abs() + >>> j = i+1 + >>> j.i() + 3 + >>> j = i-1 + >>> j.i() + 1 + >>> j = i*1 + >>> j.i() + 2 + >>> i<1 + 0 + >>> cmp(i,1) + 1 + >>> j = pow(i, 5) + >>> j.i() + 32 + >>> j = pow(i, 5, k) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + >>> j = pow(i, 5, 5) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + >>> j = i/1 + Traceback (innermost last): + TypeError: bad operand type(s) for / + >>> j = 1+i + >>> j.i() + 3 + >>> j = 1-i + >>> j.i() + -1 + >>> j = 1*i + >>> j.i() + 2 + >>> 1>> cmp(1,i) + -1 + >>> j = 1/i + Traceback (innermost last): + TypeError: bad operand type(s) for / + >>> pow(1,i) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + +Test operator export to a subclass + + # force method table sharing + >>> class IntDerived1(Int): pass + ... + + >>> class IntDerived(Int): + ... def __init__(self, i): + ... Int.__init__(self, i) + ... def __str__(self): + ... return 'IntDerived: ' + str(self.i()) + ... + >>> f = IntDerived(3) + >>> str(f) + 'IntDerived: 3' + >>> j = f * f + >>> j.i() + 9 + >>> j = f * i + >>> j.i() + 6 + >>> j = f * 5 + >>> j.i() + 15 + >>> j = i * f + >>> j.i() + 6 + >>> j = 5 * f + >>> j.i() + 15 + + +========= Prove that the "phantom base class" issue is resolved ========== + + >>> assert pa1_a1.__class__ == A1 + >>> assert pb1_a1.__class__ == A1 + >>> assert pb2_a1.__class__ == A1 + >>> assert pc_a1.__class__ == A1 + >>> assert pa2_a2.__class__ == A2 + >>> assert pb1_a2.__class__ == A2 + >>> assert pb1_b1.__class__ == B1 + >>> assert pc_b1.__class__ == B1 + >>> assert A1 in B1.__bases__ + >>> assert A2 in B1.__bases__ + >>> assert A1 in B2.__bases__ + >>> assert A2 in B2.__bases__ + >>> assert A1 in DA1.__bases__ + >>> assert B1 in DB1.__bases__ + >>> assert B2 in DB2.__bases__ + +=============================================================== +test methodologies for wrapping functions that return a pointer + + >>> get_record().value + 1234 + + In this methodology, the referent is copied + >>> get_record() == get_record() + 0 + +======== Enums and non-method class attributes ============== + >>> eo = EnumOwner(EnumOwner.one, EnumOwner.two) + >>> eo.first + 1 + >>> eo.second + 2 + >>> eo.first = EnumOwner.three + >>> eo.second = EnumOwner.one + >>> eo.first + 3 + >>> eo.second + 1 +''' + +from demo import * +import string +import re +import sys + +def run(args = None): + if args is not None: + sys.argv = args + import doctest, test_extclass + doctest.testmod(test_extclass) + +if __name__ == '__main__': + run() diff --git a/todo.txt b/todo.txt new file mode 100644 index 00000000..d97eade5 --- /dev/null +++ b/todo.txt @@ -0,0 +1,426 @@ +Check for const reference parameters in all from_python functions in py.h, including implementations. +Better python and C++ exception handling/error reporting. +long long support +use Python generic numeric coercion in from_python() for C++ numeric types +Rename PyPtr to Reference. +Report Cygwin linker memory issues +__init__ stuff + Make abstract classes non-instantiable (?) + Call default __init__ functions automatically where applicable (?) +Support for Python LONG types in Objects.h +Throw TypeError after asserting when objects from objects.cpp detect a type mismatch. +Figure out how to package everything as a shared library. +Unicode string support +Add read-only wrapper for __dict__ attribute +Objects.h support for generic objects, Sequence objects, etc. +empty() member functions for objects.hpp + +Testing + Python 2.0 + object revival in __del__ + More thorough tests of objects.h/cpp classes + Better reference-count checking + +Optimizations + Remove one level of indirection on type objects (no vtbl?). + Specializations of Caller<> for commmon combinations of argument types (?) + Replace uses of XXXable classes + Don't allocate instance __dict__ unless used. + + +Documentation: + + differences between Python classes and ExtensionClasses + additional capabilities of ExtensionClasses + slice adjustment + + Why special attributes other than __doc__ and __name__ are immutable. + + An example of the problems with the built-in Python classes. + + >>> class A: + ... def __getattr__(self, name): + ... return 'A.__getattr__' + ... + >>> class B(A): pass + ... + >>> class C(B): pass + ... + >>> C().x + 'A.__getattr__' + >>> B.__bases__ = () + >>> C().x + 'A.__getattr__' + + Smart pointers + #ifndef PY_NO_INLINE_FRIENDS_IN_NAMESPACE + namespace py { + #endif + + template + struct VtkConverters + { + typedef py::PyExtensionClassConverters Converters; + + friend vtk_ptr& from_python(PyObject* p, py::Type&>) + { return Converters::ptr_from_python(p, py::Type >()); } + + friend vtk_ptr& from_python(PyObject* p, py::Type >) + { return Converters::ptr_from_python(p, py::Type >()); } + + friend const vtk_ptr& from_python(PyObject* p, py::Type&>) + { return Converters::ptr_from_python(p, py::Type >()); } + + friend PyObject* to_python(vtk_ptr x) + { return Converters::ptr_to_python(x); } + }; + + #ifndef PY_NO_INLINE_FRIENDS_IN_NAMESPACE + } + #endif + + template + struct VtkWrapper : py::ClassWrapper, py::VtkConverters + { + typedef py::ClassWrapper Base; + VtkWrapper(Module& module, const char* name) + : Base(module, name) {} + }; + + exception handling + + Advanced Topics: + Advanced Type Conversion + adding conversions for fundamental types + generic conversions for template types (with partial spec). + + Interacting with built-in Python objects and types from C++ + + dealing with non-const reference/pointer parameters + + extending multiple-argument support using gen_all.py + + + Fancy wrapping tricks + templates + Yes. If you look at the examples in extclass_demo.cpp you'll see that I have + exposed several template instantiations (e.g. std::pair) in Python. + Keep in mind, however, that you can only expose a template instantiation, + not a template. In other words, MyTemplate can be exposed. MyTemplate + itself cannot. + + Well, that's not strictly true. Wow, this is more complicated to explain + than I thought. + You can't make an ExtensionClass, since after all MyTemplate is + not a type. You can only expose a concrete type to Python. + + What you *can* do (if your compiler supports partial ordering of function + templates - MSVC is broken and does not) is to write appropriate + from_python() and to_python() functions for converting a whole class of + template instantiations to/from Python. That won't let you create an + instance of MyTemplate from Python, but it will let you + pass/return arbitrary MyTemplate instances to/from your + wrapped C++ functions. + + template + MyTemplate from_python(PyObject* x, py::Type >) + { + // code to convert x into a MyTemplate... that part is up to you + } + + template + PyObject* from_python(const MyTemplate&) + { + // code to convert MyTemplate into a PyObject*... that part is up to + you + } + + For example, you could use this to convert Python lists to/from + std::vector automatically. + + Pointer return values + + Case 1: + + > I am now also able to wrap the problematic TextRecordIterator for Python. + > However, one of its function compiles with this warning: + > + > d:\py_cpp/caller.h(33) : warning C4800: 'const class Record *const ' + > : forcing value to bool 'true' or 'false' (performance warning) + > d:\py_cpp/functions.h(54) : see reference to function template + > instantiation 'struct _object *__cdecl py::Caller::call(const class Record + > *const (__thiscall TextRecordIterator::*)(void),struct _object *,struct + > _object *)' being compiled + > + > If you look at the offending code, you'll see that we really do need to + > get back that pointer: + > + > const Record* const TextRecordIterator::Next() { + > if (fStatus != RecordIterator::SUCCESS) { + > return 0; + > } else { + > return &fData; + > } + > } + > + > The point of the TextRecordIterator is to hand over one reord after + > another. A bool wouldn't do us much good here :-) + > + > Do you have any suggestions for fixing this? + + In general, py_cpp doesn't automatically convert pointer return values + to_python because pointers have too many potential meanings. Is it an + iterator? A pointer to a single element? An array? Is ownership being passed + to Python or is the pointer really just a reference? If the latter, what + happens when some C++ code deletes the referent. The only exception to this + rule is const char*, since it has a generally accepted interpretation (could + be trouble with some generic code, though!) + + If you have wrapped the Record class, you could add this to namespace py: + + PyObject* to_python(const Record* p) { + return to_python(*p); + } + + Of course, this will cause the Record class to be copied. If you can't live + with that (Record would have to be /really/ heavyweight to make this + worthwhile), you can follow one of these dangerous approaches: + + 1. Use the technique I described with dangerous_array in + http://www.egroups.com/message/boost/6196. You do not have to expose Record + explicitly in this case. Instead the class you expose will be more of a + Record_proxy + + 2. Wrap Record in the usual way, then add the following to namespace py: + + PyObject* to_python(const Record* p) + { + return ExtensionClass::ptr_to_python(const_cast(p)); + } + + This will cause the Record* to be treated as though it were an owning smart + pointer, even though it's not. Be sure you don't use the reference for + anything from Python once the pointer becomes invalid, though. Don't worry + too much about the const-correctness issue: Const-correctness is completely + lost to Python anyway! + + 3. As above, but instead wrap const Record rather than plain Record. Then + you can avoid the const_cast, but you obviously can't def() any non-const + member functions of Record. + + Case 2: + + > I have yet another question. This is more a general wrapper question. + > Let me say that there is a function that returns a float* which most + > probably is an array. Similarly if I have a function that takes a + > float* as an argument, what is the best way of wrapping this? + + I think you have correctly perceived that it doesn't make sense for me to + automatically convert all pointers, since the ownership semantics are so + blurry. + + > 1) If the array is small it makes sense to convert it to either a + > tuple or list. What is the easiest way to do this?? I am looking + > for a way that makes one write the least code. :) + + How can you tell the length of the array from a single pointer? + Once you've answered that question, you can expose a wrapper function which + returns an instance of the py::Tuple or py::List class from objects.h. If + you are using a List, for example, you could write something like this: + + py::List wrap_f() + { + T* start = f(); + py::List x; + for (T* p = start; p != start + length_constant; ++p) + x.push_back(py::to_python(*p)); + return x; + } + + > 2) If the array is large it may not make sense to use a list/tuple + > esp. if the values are used for computationally intense programs. + + In this case you can do one of several somewhat dangerous things. Why + dangerous? Because python can not control the lifetime of the data, so the + data in the array may be destroyed or become invalid before the last + reference to it disappears. The basic approach is to make a small C++ class + which contains the pointer, and expose that: + + // UNTESTED + template + struct dangerous_array + { + dangerous_array(T* start, T* end) + : m_start(start), m_end(end) {} + + // exposed as "__len__" + std::size_t length() { + return m_end - m_start; + } + + // exposed as "__getitem__" + T get_item(std::size_t n) { + check_range(n); + return start[n]; + } + + // exposed as "__setitem__" if the array is mutable + void set_item(std::size_t n, const T& x) { + check_range(n); + start[n] = x; + } + private: + void check_range(std::size_t n) { + if (n >= m_end - m_start) { + PyErr_SetString(PyExc_IndexError, "array index out of range"); + throw py::ErrorAlreadySet; + } + } + T* m_start; + T* m_end; + }; + + A reasonably safe approach would be to make a wrapper function for each + function that returns a T*, and expose that instead. If you're too lazy and + you really like to live on the edge, though, you can write to_python(T*) in + terms of to_python(const dangerous_array&), and you'll automatically + convert all T* return values to a wrapped dangerous_array. + + > 3) For an arbitrary class "class_A", say, can py_cpp handle + > references to class_A &instance, or class_A *instance?? i.e. will it + > wrap function calls to such objects? This question is obviously + > related to the earlier questions. + + Yes, iff class_A has been exposed to python with a ClassWrapper. + See http://people.ne.mediaone.net/abrahams/downloads/under-the-hood.html for + a few details. + + raw C++ arrays + You could expose a function like this one to get the desired effect: + + #include + void set_len(UnitCell& x, py::Tuple tuple) + { + double len[3]; + for (std::size_t i =0; i < 3; ++i) + len[i] = py::from_python(tuple[i].get(), py::Type()); + x.set_len(len); + } + + Types that are already wrapped by other libraries + + It's not documented yet, but you should be able to use a raw PyObject* or a + py::Ptr as one parameter to your C++ function. Then you can manipulate it as + any other generic Python object. + + Alternatively, If the NTL gives you a C/C++ interface, you can also write + your own converter function: + + some_ntl_type& from_python(PyObject* p, py::Type) + { + // an Example implementation. Basically, you need + // to extract the NTL type from the PyObject*. + if (p->ob_type != NTL_long_type) { + PyErr_SetString(PyExc_TypeErr, "NTL long required"); + throw py::ArgumentError(); + } + return *static_cast(p); + } + + then the C++ functions you're wrapping can take a some_NTL_type& parameter + directly. + + "Thin converting wrappers" for constructors + + hijack some of the functionality + described in the section on Overridable Virtual Functions (even though you + don't have any virtual functions). I suggest this workaround: + + struct UnitCellWrapper : UnitCell + { + UnitCellWrapper(PyObject* self, py::Tuple x, py::Tuple y) + : UnitCell(from_python(x[1], py::Type()), + from_python(x[2], py::Type()), + from_python(x[3], py::Type()), + from_python(y[1], py::Type()), + from_python(y[2], py::Type()), + from_python(y[3], py::Type())) + {} + } + + py::ClassWrapper unit_cell_class; + unit_cell_class.def(py::Constructor()); + ... + + returning references to wrapped objects + + the importance of declaration order of ClassWrappers/ExtensionInstances + + out parameters and non-const pointers + + Calling back into Python: + // caveat: UNTESTED! + #include + #include + #include + #include + int main() + { + try { + py::Ptr module(PyImport_ImportModule("weapons")); + const int strength = 10; + const char* manufacturer = "Vordon Empire"; + py::Ptr a_blaster(py::Callback::call_method( + module.get(), "Blaster", strength, manufacturer)); + py::Callback::call_method(a_blaster.get(), "Fire"); + int old_strength = py::Callback::call_method(a_blaster.get(), "get_strength"); + py::Callback::call_method(a_blaster.get(), "set_strength", 5); + } + catch(...) + { + } + } + + Miscellaneous + About the vc6 project and the debug build + About doctest.py + +Boost remarks: + + > > One of us is completely nuts ;->. How can I move the test + > > (is_prefix(enablers[i].name + 2, name + 2)) outside the loop if it + depends + > > on the loop index, i? + > > + > name += 2; + > for() + > { + > if (is_prefix(enablers[i].name + 2, name)) + > } + + I see now. I guess I should stop pussyfooting and either go for optimization + or clarity here, eh? + + ------ + + > Re: Dict + > Why abbreviate this? Code is read 5 or 6 times for every time its + > written. The few extra characters don't affect compile time or program + > speed. It's part of my personal goal of write what you mean, name them + what + > they are. + + I completely agree. Abbrevs rub me the wrong way, 2 ;-> + + ------- + + + + +Later: + keyword and varargs? + Put explicit Type<> arguments at the beginnings of overloads, to make them look more like template instance specifications. + +Known bugs + can't handle 'const void' return values + Who returns 'const void'? I did it once, by mistake ;) diff --git a/tru64.mak b/tru64.mak new file mode 100644 index 00000000..6631615c --- /dev/null +++ b/tru64.mak @@ -0,0 +1,51 @@ +# +# Tested with: +# Compaq C++ V6.2-024 for Digital UNIX V5.0 (Rev. 910) +# +# Python 1.5.2 was installed without any customizations. +# boost_all.zip vers. 1.18.1 was unpacked using unzip -aa and not modified. +# STLport-4.1b3 was unpacked using unzip -aa and not modified. +# +# Initial version 2000-10-20: Ralf W. Grosse-Kunstleve, rwgk@cci.lbl.gov +# + +PYINC= /usr/local/include/python1.5 +BOOSTINC= /usr/local/boost_1_18_1 +STLPORTINC= /usr/local/STLport-4.1b3/stlport +STLPORTOPTS= \ + -D__USE_STD_IOSTREAM \ + -D__STL_NO_SGI_IOSTREAMS \ + -D__STL_NO_NEW_C_HEADERS \ + -D_RWSTD_COMPILE_INSTANTIATE=1 + +STDOPTS= -std strict_ansi +WARNOPTS= -msg_disable 186,450,1115 +# use -msg_display_number to obtain integer tags for -msg_disable + +CPP= cxx +CPPOPTS= -I$(STLPORTINC) $(STLPORTOPTS) -I$(BOOSTINC) -I$(PYINC) \ + $(STDOPTS) $(WARNOPTS) + +LD= cxx +LDOPTS= -shared -expect_unresolved '*' + +OBJ = extclass.o functions.o init_function.o module.o newtypes.o \ + objects.o py.o subclass.o + +.SUFFIXES: .o .cpp + +all: demo.so hello.so + +demo.so: $(OBJ) extclass_demo.o + $(LD) $(LDOPTS) $(OBJ) extclass_demo.o -o demo.so + +hello.so: $(OBJ) example1.o + $(LD) $(LDOPTS) $(OBJ) example1.o -o hello.so + +.cpp.o: + -$(CPP) $(CPPOPTS) $(INC) -c $*.cpp + +clean: + rm -f $(OBJ) extclass_demo.o example1.o demo.so hello.so so_locations + rm -rf cxx_repository + rm -f *.pyc diff --git a/under-the-hood.html b/under-the-hood.html new file mode 100644 index 00000000..db3722c0 --- /dev/null +++ b/under-the-hood.html @@ -0,0 +1,62 @@ + + + + A Peek Under the Hood + +

+ c++boost.gif (8819 bytes) +

+

+ A Peek Under the Hood +

+

+ Declaring a class_builder<T> causes the instantiation + of an extension_class<T> to which it forwards all + member function calls and which is doing most of the real work. + extension_class<T> is a subclass of + PyTypeObject, the struct which Python's 'C' API uses + to describe a type. An instance of the + extension_class<> becomes the Python type object + corresponding to hello::world. When we add it to the module it goes into the + module's dictionary to be looked up under the name "world". +

+ Py_cpp uses C++'s template argument deduction mechanism to determine the + types of arguments to functions (except constructors, for which we must + provide an argument list + because they can't be named in C++). Then, it calls the appropriate + overloaded functions PyObject* + to_python(S) and + S'from_python(PyObject*, + type<S>) which convert between any C++ + type S and a PyObject*, the type which represents a + reference to any Python object in its 'C' API. The extension_class<T> + template defines a whole raft of these conversions (for T, T*, + T&, std::auto_ptr<T>, etc.), using the same inline + friend function technique employed by the boost operators + library. +

+ Because the to_python and from_python functions + for a user-defined class are defined by + extension_class<T>, it is important that an instantiation of + extension_class<T> is visible to any code which wraps + a C++ function with a T, T*, const T&, etc. parameter or + return value. In particular, you may want to create all of the classes at + the top of your module's init function, then def the member + functions later to avoid problems with inter-class dependencies. +

+ Previous: Function Overloading + Next: Building a Module with Py_cpp + 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: Sept 30, 2000 + diff --git a/vc6_prj/test_demo.py b/vc6_prj/test_demo.py new file mode 100644 index 00000000..eb68e4a2 --- /dev/null +++ b/vc6_prj/test_demo.py @@ -0,0 +1,6 @@ +import demo # Get demo imported now so test_extclass won't look in its own directory +import os +os.chdir('..') +import test_extclass +test_extclass.run(['-v', '--broken-auto-ptr']) + diff --git a/vc6_prj/test_hello.py b/vc6_prj/test_hello.py new file mode 100644 index 00000000..6da3c4bc --- /dev/null +++ b/vc6_prj/test_hello.py @@ -0,0 +1,6 @@ +import hello # Get demo imported now so test_extclass won't look in its own directory +import os +os.chdir('..') +import test_example1 +test_example1.run(["-v"]) + diff --git a/vc6_prj/vc6_prj.dsp b/vc6_prj/vc6_prj.dsp new file mode 100644 index 00000000..72d465ff --- /dev/null +++ b/vc6_prj/vc6_prj.dsp @@ -0,0 +1,158 @@ +# Microsoft Developer Studio Project File - Name="vc6_prj" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=vc6_prj - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "vc6_prj.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "vc6_prj.mak" CFG="vc6_prj - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "vc6_prj - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "vc6_prj - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "vc6_prj - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "VC6_PRJ_EXPORTS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /MD /W3 /GR /GX /Ox /Og /Oi /Os /Op- /Oy /Gy /I "c:\boost" /I "c:\tools\python\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "VC6_PRJ_EXPORTS" /FD /Zm200 /c +# SUBTRACT CPP /Ot +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"demo.dll" /libpath:"c:/tools/python/libs" +# SUBTRACT LINK32 /pdb:none /debug + +!ELSEIF "$(CFG)" == "vc6_prj - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "VC6_PRJ_EXPORTS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /MDd /Gm /GR /GX /ZI /Od /Oy- /I "c:\boost" /I "c:\tools\python\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "VC6_PRJ_EXPORTS" /FR /FD /GZ /Zm200 /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"demo_d.dll" /pdbtype:sept /libpath:"c:/tools/python/src/pcbuild" + +!ENDIF + +# Begin Target + +# Name "vc6_prj - Win32 Release" +# Name "vc6_prj - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\extclass.cpp + +!IF "$(CFG)" == "vc6_prj - Win32 Release" + +!ELSEIF "$(CFG)" == "vc6_prj - Win32 Debug" + +# ADD CPP /I "c:\tools\python\src\include" /D "DEBUG_PYTHON" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\extclass_demo.cpp +# End Source File +# Begin Source File + +SOURCE=..\functions.cpp +# End Source File +# Begin Source File + +SOURCE=..\init_function.cpp +# End Source File +# Begin Source File + +SOURCE=..\module.cpp +# End Source File +# Begin Source File + +SOURCE=..\newtypes.cpp +# End Source File +# Begin Source File + +SOURCE=..\objects.cpp +# End Source File +# Begin Source File + +SOURCE=..\py.cpp +# End Source File +# Begin Source File + +SOURCE=..\subclass.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# Begin Source File + +SOURCE=.\ReadMe.txt +# End Source File +# End Target +# End Project diff --git a/vc6_prj/vc6_prj.dsw b/vc6_prj/vc6_prj.dsw new file mode 100644 index 00000000..0e48beed --- /dev/null +++ b/vc6_prj/vc6_prj.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "vc6_prj"=.\vc6_prj.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/vc6_prj/vc6_prj.opt b/vc6_prj/vc6_prj.opt new file mode 100644 index 00000000..543c06ad Binary files /dev/null and b/vc6_prj/vc6_prj.opt differ diff --git a/wrap_python.h b/wrap_python.h new file mode 100644 index 00000000..7c6dd5be --- /dev/null +++ b/wrap_python.h @@ -0,0 +1,61 @@ +#ifdef _DEBUG +# ifndef DEBUG_PYTHON +# undef _DEBUG // Don't let Python force the debug library just because we're debugging. +# define DEBUG_UNDEFINED_FROM_WRAP_PYTHON_H +# endif +#endif + +// +// Some things we need in order to get Python.h to work with compilers other +// than MSVC on Win32 +// +#if defined(_WIN32) +# ifdef __GNUC__ + +typedef int pid_t; +# define WORD_BIT 32 +# define hypot _hypot +# include +# define HAVE_CLOCK +# define HAVE_STRFTIME +# define HAVE_STRERROR +# define NT_THREADS +# define WITH_THREAD +# ifndef NETSCAPE_PI +# define USE_SOCKET +# endif + +# ifdef USE_DL_IMPORT +# define DL_IMPORT(RTYPE) __declspec(dllimport) RTYPE +# endif + +# ifdef USE_DL_EXPORT +# define DL_IMPORT(RTYPE) __declspec(dllexport) RTYPE +# define DL_EXPORT(RTYPE) __declspec(dllexport) RTYPE +# endif + +# define HAVE_LONG_LONG 1 +# define LONG_LONG long long + +# elif defined(__MWERKS__) + +# ifndef _MSC_VER +# define PY_MSC_VER_DEFINED_FROM_WRAP_PYTHON_H 1 +# define _MSC_VER 900 +# endif + +# endif + +#endif // _WIN32 + +#include + +#ifdef PY_MSC_VER_DEFINED_FROM_WRAP_PYTHON_H +# undef _MSC_VER +#endif + +#ifdef DEBUG_UNDEFINED_FROM_WRAP_PYTHON_H +# undef DEBUG_UNDEFINED_FROM_WRAP_PYTHON_H +# define _DEBUG +#endif +