diff --git a/base_object.h b/base_object.h index e9183178..231dead1 100644 --- a/base_object.h +++ b/base_object.h @@ -59,4 +59,4 @@ inline BaseObject::~BaseObject() } -#endif BASE_OBJECT_DWA051600_H_ +#endif // BASE_OBJECT_DWA051600_H_ diff --git a/class_wrapper.h b/class_wrapper.h index f0c342c2..b383d263 100644 --- a/class_wrapper.h +++ b/class_wrapper.h @@ -20,9 +20,6 @@ class ClassWrapper public: ClassWrapper(Module& module, const char* name) : m_class(new ExtensionClass(name)) -#if 0 // def PY_MSVC6_OR_EARLIER - , m_msvc_hack(name) -#endif { module.add(Ptr(as_object(m_class.get()), Ptr::new_ref), name); } @@ -68,11 +65,47 @@ class ClassWrapper template void def_read_write(MemberType T::*pm, const char* name) { m_class->def_read_write(pm, name); } + + // declare the given class a base class of this one and register + // conversion functions + template + void declare_base(ClassWrapper 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(ClassWrapper const & base, WithoutDowncast) + { + m_class->declare_base(base.get_extension_class(), without_downcast); + } + + // declare the given class a base class of this one and register + // conversion functions + template + void declare_base(ExtensionClass * 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(ExtensionClass * base, WithoutDowncast) + { + m_class->declare_base(base, without_downcast); + } + + // get the embedded ExtensioClass object + ExtensionClass * get_extension_class() const + { + return m_class.get(); + } + private: PyPtr > m_class; -#if 0 // def PY_MSVC6_OR_EARLIER - PyExtensionClassConverters m_msvc_hack; -#endif }; // The bug mentioned at the top of this file is that on certain compilers static diff --git a/comparisons.html b/comparisons.html new file mode 100644 index 00000000..d11af0eb --- /dev/null +++ b/comparisons.html @@ -0,0 +1,176 @@ + + + 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. This is not entirely a + bad thing, as you can do some Pythonic things with CXX (e.g. variable + and keyword arguments) that I haven't yet figured out how to enable with + py_cpp. As far as I can tell, also 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. That use is + also supported (less-well) by the py_cpp object wrappers. Paul F. Dubois, the CXX maintainer, + 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. I hope that you, dear reader, may be able to help me + complete my comparitive analysis of CXX. + + +

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

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 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. +
  • + Thus, Zope's ExtensionClasses support pickling. Currently py_cpp + ExtensionClasses do not. +
  • + 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's Extension SubClasses admit multiple-inheritance from both Zope + ExtensionClasses and Python classes. This capability is currently + not available in py_cpp. +
  • + Zope supplies the standard special Python class attributes __doc__, + __name__, __bases__, __dict__, and __module__ on its + ExtensionClasses; py_cpp does not (coming soon) +
  • + Zope supplies some creative but esoteric idioms such as + Acquisition. +
  • + 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. +
+

+ Also, the Zope docs say: "The first superclass listed in the class + statement defining an extension subclass must be either a base + extension class or an extension subclass. This restriction will be + removed in Python-1.5." I believe that this promise was made + prematurely. I have looked at the Python 1.5.2 source code and I don't + believe it is possible to deliver on it. +

+ 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: Oct 18, 2000 +

+ diff --git a/demo.bdf b/demo.bdf deleted file mode 100644 index 638c16e5..00000000 --- a/demo.bdf +++ /dev/null @@ -1,4 +0,0 @@ -TargetName=demo -TargetType=dll -SourceFiles=extclass_demo.cpp -Libs=py_cpp python utils diff --git a/demo_d.bdf b/demo_d.bdf deleted file mode 100644 index 92699e0c..00000000 --- a/demo_d.bdf +++ /dev/null @@ -1,4 +0,0 @@ -TargetName=demo_d -TargetType=dll -SourceFiles=extclass_demo_d.cpp -Libs=py_cpp_d python_d utils diff --git a/example1.bdf b/example1.bdf deleted file mode 100644 index 72fdc159..00000000 --- a/example1.bdf +++ /dev/null @@ -1,4 +0,0 @@ -TargetName=example1 -TargetType=dll -SourceFiles=example1.cpp -Libs=py_cpp python utils diff --git a/example1.html b/example1.html new file mode 100644 index 00000000..9a4a868d --- /dev/null +++ b/example1.html @@ -0,0 +1,127 @@ + + + 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: +

+
+namespace hello {
+  class world
+  {
+   public:
+      world(int);
+      ~world();
+      const char* get() const { return "hi, world"; }
+    ...
+  };
+  void 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
+       py::Module hello("hello");
+       // Create the Python type object for our extension class
+       py::ClassWrapper<hello::world> world_class(hello, "world");
+       // Add the __init__ function
+       world_class.def(py::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(...)
+    {
+       py::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 15, 2000 +

+ diff --git a/extclass.cpp b/extclass.cpp index a1734760..c654afa3 100644 --- a/extclass.cpp +++ b/extclass.cpp @@ -157,7 +157,7 @@ void report_missing_instance_data( } else { - two_string_error(PyExc_TypeError, "extension class '%.*s' is not derived from '%.*s'.", + two_string_error(PyExc_TypeError, "extension class '%.*s' is not convertible into '%.*s'.", instance->ob_type->tp_name, target_class->tp_name); } } @@ -218,6 +218,66 @@ ExtensionClassBase::ExtensionClassBase(const char* name) { } +// 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 InstanceHolderBase (a wrapper class). A conversion +// of the held object to 'T *' is allowed when the conversion +// 'dynamic_cast *>(an_instance_holder_base)' succeeds. +void* ExtensionClassBase::try_class_conversions(InstanceHolderBase* object) const +{ + void* result = try_derived_class_conversions(object); + if(result) + return result; + + return try_base_class_conversions(object); +} + +void* ExtensionClassBase::try_base_class_conversions(InstanceHolderBase* 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* ExtensionClassBase::try_derived_class_conversions(InstanceHolderBase* 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 ExtensionClassBase::add_method(Function* method, const char* name) { add_method(PyPtr(method), name); diff --git a/extclass.h b/extclass.h index 3a3be054..32a0041e 100644 --- a/extclass.h +++ b/extclass.h @@ -5,6 +5,9 @@ // // 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.py #ifndef EXTENSION_CLASS_DWA052000_H_ # define EXTENSION_CLASS_DWA052000_H_ @@ -24,6 +27,7 @@ namespace py { // forward declarations class ExtensionInstance; +class ExtensionClassBase; template class InstanceHolder; template class InstanceValueHolder; template class InstancePtrHolder; @@ -50,10 +54,39 @@ T* check_non_null(T* p) template class HeldInstance; +namespace detail { + typedef void* (*ConversionFunction)(void*); + + struct BaseClassInfo + { + BaseClassInfo(ExtensionClassBase* t, ConversionFunction f) + :class_object(t), convert(f) + {} + + ExtensionClassBase* class_object; + ConversionFunction convert; + }; + + typedef BaseClassInfo DerivedClassInfo; +} + class ExtensionClassBase : public Class { public: ExtensionClassBase(const char* name); + + public: + // the purpose of try_class_conversions() and its related functions + // is explained in extclass.cpp + void* try_class_conversions(InstanceHolderBase*) const; + void* try_base_class_conversions(InstanceHolderBase*) const; + void* try_derived_class_conversions(InstanceHolderBase*) const; + + private: + virtual void* extract_object_from_holder(InstanceHolderBase* v) const = 0; + virtual std::vector const& base_classes() const = 0; + virtual std::vector const& derived_classes() const = 0; + protected: void add_method(PyPtr method, const char* name); void add_default_method(PyPtr method, const char* name); @@ -69,12 +102,24 @@ template class ClassRegistry { public: - static Class* class_object() + static ExtensionClassBase* class_object() { return static_class_object; } - static void register_class(py::Class*); - static void unregister_class(py::Class*); + + // Register/unregister the Python class object corresponding to T + static void register_class(ExtensionClassBase*); + static void unregister_class(ExtensionClassBase*); + + // Establish C++ inheritance relationships + static void register_base_class(py::detail::BaseClassInfo const&); + static void register_derived_class(py::detail::DerivedClassInfo const&); + + // Query the C++ inheritance relationships + static std::vector const& base_classes(); + static std::vector const& derived_classes(); private: - static py::Class* static_class_object; + static ExtensionClassBase* static_class_object; + static std::vector static_base_class_info; + static std::vector static_derived_class_info; }; #ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // back to global namespace for this GCC bug @@ -106,8 +151,6 @@ class PyExtensionClassConverters { return py::Type(); } #endif - PyExtensionClassConverters() {} - // Convert to T* friend T* from_python(PyObject* obj, py::Type) { @@ -120,6 +163,11 @@ class PyExtensionClassConverters py::InstanceHolder* held = dynamic_cast*>(*p); if (held != 0) return held->target(); + + // see extclass.cpp for an explanation of try_class_conversions() + void * target = py::ClassRegistry::class_object()->try_class_conversions(*p); + if(target) + return static_cast(target); } py::report_missing_instance_data(self, py::ClassRegistry::class_object(), typeid(T)); throw py::ArgumentError(); @@ -168,7 +216,6 @@ class PyExtensionClassConverters return py::PyPtr(new py::ExtensionInstance(class_)); } - // Convert to const T* friend const T* from_python(PyObject* p, py::Type) { return from_python(p, py::Type()); } @@ -247,6 +294,28 @@ class ReadOnlySetattrFunction : public Function String m_name; }; +namespace detail +{ + +template +struct DefineConversion +{ + static void * upcast_ptr(void * v) + { + return static_cast(static_cast(v)); + } + + static void * downcast_ptr(void * v) + { + return dynamic_cast(static_cast(v)); + } +}; + +} + +enum WithoutDowncast { without_downcast }; + + // An easy way to make an extension base class which wraps T. Note that Python // subclasses of this class will simply be Class objects. // @@ -279,10 +348,12 @@ class ExtensionClass // define constructors template void def(Constructor) - { add_constructor( // the following incantation builds a Signature1, - prepend(Type::Id(), // Signature2, ... constructor. It _should_ all - prepend(Type::Id(), // get optimized away. Just another workaround - prepend(Type::Id(), // for the lack of partial specialization in MSVC + // 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())))))); @@ -339,10 +410,49 @@ class ExtensionClass this->def_getter(pm, name); this->def_setter(pm, name); } + + // declare the given class a base class of this one and register + // up and down conversion functions + template + void declare_base(ExtensionClass * base) + { + // see extclass.cpp for an explanation of why we need to register + // conversion functions + detail::BaseClassInfo baseInfo(base, + &detail::DefineConversion::downcast_ptr); + ClassRegistry::register_base_class(baseInfo); + add_base(Ptr(as_object(base), Ptr::new_ref)); + + detail::DerivedClassInfo derivedInfo(this, + &detail::DefineConversion::upcast_ptr); + ClassRegistry::register_derived_class(derivedInfo); + } + + // declare the given class a base class of this one and register + // only up conversion function + template + void declare_base(ExtensionClass * base, WithoutDowncast) + { + // see extclass.cpp for an explanation of why we need to register + // conversion functions + detail::BaseClassInfo baseInfo(base, 0); + ClassRegistry::register_base_class(baseInfo); + add_base(Ptr(as_object(base), Ptr::new_ref)); + + detail::DerivedClassInfo derivedInfo(this, + &detail::DefineConversion::upcast_ptr); + ClassRegistry::register_derived_class(derivedInfo); + } - private: + private: // types typedef InstanceValueHolder Holder; - + + private: // ExtensionClassBase virtual function implementations + std::vector const& base_classes() const; + std::vector const& derived_classes() const; + void* extract_object_from_holder(InstanceHolderBase* v) const; + + private: // Utility functions template void add_constructor(Signature sig) { @@ -350,7 +460,69 @@ class ExtensionClass } }; -#include "extclass_pygen.h" +// A simple wrapper over a T which allows us to use ExtensionClass with a +// single template parameter only. See ExtensionClass, above. +template +class HeldInstance : public T +{ + // There are no member functions: we want to avoid inadvertently overriding + // any virtual functions in T. +public: + HeldInstance(PyObject* p) : T(), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1) : T(a1), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1, const A2& a2) : T(a1, a2), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3) : T(a1, a2, a3), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4) : T(a1, a2, a3, a4), m_self(p) {} + template + HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : T(a1, a2, a3, a4, a5), m_self(p) {} +protected: + PyObject* m_self; // Not really needed; doesn't really hurt. +}; + +class InstanceHolderBase +{ +public: + virtual ~InstanceHolderBase() {} +}; + +template +class InstanceHolder : public InstanceHolderBase +{ +public: + virtual Held *target() = 0; +}; + +template +class InstanceValueHolder : public InstanceHolder +{ +public: + Held* target() { return &m_held; } + Wrapper* value_target() { return &m_held; } + + InstanceValueHolder(ExtensionInstance* p) : + m_held(p) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1) : + m_held(p, a1) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2) : + m_held(p, a1, a2) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3) : + m_held(p, a1, a2, a3) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4) : + m_held(p, a1, a2, a3, a4) {} + template + InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : + m_held(p, a1, a2, a3, a4, a5) {} +private: + Wrapper m_held; +}; template class InstancePtrHolder : public InstanceHolder @@ -358,7 +530,7 @@ class InstancePtrHolder : public InstanceHolder public: HeldType* target() { return &*m_ptr; } PtrType& ptr() { return m_ptr; } - + InstancePtrHolder(PtrType ptr) : m_ptr(ptr) {} private: PtrType m_ptr; @@ -397,28 +569,38 @@ ExtensionClass::ExtensionClass(const char* name) ClassRegistry::register_class(this); } +template +inline +std::vector const & +ExtensionClass::base_classes() const +{ + return ClassRegistry::base_classes(); +} + +template +inline +std::vector const & +ExtensionClass::derived_classes() const +{ + return ClassRegistry::derived_classes(); +} + +template +void* ExtensionClass::extract_object_from_holder(InstanceHolderBase* v) const +{ + py::InstanceHolder* held = dynamic_cast*>(v); + if(held) return held->target(); + return 0; +} + template ExtensionClass::~ExtensionClass() { ClassRegistry::unregister_class(this); } -#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // Back to the global namespace for this GCC bug -} -#endif - -// -// Static data member declaration. -// -#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // Back from the global namespace for this GCC bug -namespace py { -#endif - template -Class* ClassRegistry::static_class_object; - -template -inline void ClassRegistry::register_class(Class* p) +inline void ClassRegistry::register_class(ExtensionClassBase* p) { // You're not expected to create more than one of these! assert(static_class_object == 0); @@ -426,7 +608,7 @@ inline void ClassRegistry::register_class(Class* p) } template -inline void ClassRegistry::unregister_class(Class* p) +inline void ClassRegistry::unregister_class(ExtensionClassBase* p) { // The user should be destroying the same object they created. assert(static_class_object == p); @@ -434,6 +616,41 @@ inline void ClassRegistry::unregister_class(Class* p) static_class_object = 0; } +template +void ClassRegistry::register_base_class(py::detail::BaseClassInfo const & i) +{ + static_base_class_info.push_back(i); +} + +template +void ClassRegistry::register_derived_class(py::detail::DerivedClassInfo const & i) +{ + static_derived_class_info.push_back(i); +} + +template +std::vector const& ClassRegistry::base_classes() +{ + return static_base_class_info; +} + +template +std::vector const& ClassRegistry::derived_classes() +{ + return static_derived_class_info; +} + +// +// Static data member declaration. +// +template +ExtensionClassBase* ClassRegistry::static_class_object; +template +std::vector ClassRegistry::static_base_class_info; +template +std::vector ClassRegistry::static_derived_class_info; + } // namespace py #endif // EXTENSION_CLASS_DWA052000_H_ + diff --git a/extclass_d.cpp b/extclass_d.cpp deleted file mode 100644 index 6963607e..00000000 --- a/extclass_d.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#define DEBUG_PYTHON -#include "extclass.cpp" - diff --git a/extclass_demo.cpp b/extclass_demo.cpp index dd6c13f5..0334e0cd 100644 --- a/extclass_demo.cpp +++ b/extclass_demo.cpp @@ -8,6 +8,7 @@ #include "extclass_demo.h" #include "class_wrapper.h" #include // used for portability on broken compilers +#include namespace extclass_demo { @@ -290,6 +291,245 @@ 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 py::Callback::call_method(m_self, "callback", x); + } + std::string callbackString(std::string const & x) + { + return py::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); +} + +typedef boost::rational Ratio; + +py::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 py::String(buf); +} + +py::String ratio_repr(const Ratio& r) +{ + char buf[200]; + sprintf(buf, "Rational(%d, %d)", r.numerator(), r.denominator()); + return py::String(buf); +} + +py::Tuple ratio_coerce(const Ratio& r1, int r2) +{ + return py::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; } +}; + +/************************************************************/ +/* */ +/* init the module */ +/* */ +/************************************************************/ + void init_module(py::Module& m) { m.add(new Foo::PythonClass); @@ -309,6 +549,21 @@ void init_module(py::Module& m) m.def(first_string, "first_string"); m.def(second_string, "second_string"); + // This shows the wrapping of a 3rd-party numeric type. + py::ClassWrapper > rational(m, "Rational"); + rational.def(py::Constructor()); + rational.def(py::Constructor()); + rational.def(py::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__"); + py::ClassWrapper range(m, "Range"); range.def(py::Constructor()); range.def(py::Constructor()); @@ -321,6 +576,64 @@ void init_module(py::Module& m) 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"); + + py::ClassWrapper over(m, "OverloadTest"); + over.def(py::Constructor()); + over.def(py::Constructor()); + over.def(py::Constructor()); + over.def(py::Constructor()); + over.def(py::Constructor()); + over.def(py::Constructor()); + over.def(py::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"); + + py::ClassWrapper base(m, "Base"); + base.def(&Base::x, "x"); + + py::ClassWrapper derived1(m, "Derived1"); + // this enables conversions between Base and Derived1 + // and makes wrapped methods of Base available + derived1.declare_base(base); + derived1.def(py::Constructor()); + + py::ClassWrapper derived2(m, "Derived2"); + // don't enable downcast from Base to Derived2 + derived2.declare_base(base, py::without_downcast); + derived2.def(py::Constructor()); + + m.def(&testUpcast, "testUpcast"); + m.def(&derived1Factory, "derived1Factory"); + m.def(&derived2Factory, "derived2Factory"); + m.def(&testDowncast1, "testDowncast1"); + m.def(&testDowncast2, "testDowncast2"); + + py::ClassWrapper callbackTestBase(m, "CallbackTestBase"); + callbackTestBase.def(&CallbackTestBase::testCallback, "testCallback"); + m.def(&testCallback, "testCallback"); + + py::ClassWrapper callbackTest(m, "CallbackTest"); + callbackTest.def(py::Constructor()); + callbackTest.def(&CallbackTest::callback, "callback", + &CallbackTestCallback::default_callback); + callbackTest.def(&CallbackTest::callbackString, "callback", + &CallbackTestCallback::default_callbackString); + + callbackTest.declare_base(callbackTestBase); } void init_module() diff --git a/extclass_demo_d.cpp b/extclass_demo_d.cpp deleted file mode 100644 index d0201cda..00000000 --- a/extclass_demo_d.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define DEBUG_PYTHON -#include "extclass_demo.cpp" diff --git a/extclass_pygen.h b/extclass_pygen.h deleted file mode 100644 index a63fe12d..00000000 --- a/extclass_pygen.h +++ /dev/null @@ -1,80 +0,0 @@ -// (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.py - -#ifndef EXTCLASS_PYGEN_DWA070900_H_ -# define EXTCLASS_PYGEN_DWA070900_H_ - - -// A simple wrapper over a T which allows us to use ExtensionClass with a -// single template parameter only. See ExtensionClass, above. -template -class HeldInstance : public T -{ - // There are no member functions: we want to avoid inadvertently overriding - // any virtual functions in T. -public: - HeldInstance(PyObject* p) : T(), m_self(p) {} - template - HeldInstance(PyObject* p, const A1& a1) : T(a1), m_self(p) {} - template - HeldInstance(PyObject* p, const A1& a1, const A2& a2) : T(a1, a2), m_self(p) {} - template - HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3) : T(a1, a2, a3), m_self(p) {} - template - HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4) : T(a1, a2, a3, a4), m_self(p) {} - template - HeldInstance(PyObject* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : T(a1, a2, a3, a4, a5), m_self(p) {} -protected: - PyObject* m_self; // Not really needed; doesn't really hurt. -}; - -class InstanceHolderBase -{ -public: - virtual ~InstanceHolderBase() {} -}; - -template -class InstanceHolder : public InstanceHolderBase -{ -public: - virtual Held *target() = 0; -}; - -template -class InstanceValueHolder : public InstanceHolder -{ -public: - Held* target() { return &m_held; } - Wrapper* value_target() { return &m_held; } - - InstanceValueHolder(ExtensionInstance* p) : - m_held(p) {} - template - InstanceValueHolder(ExtensionInstance* p, const A1& a1) : - m_held(p, a1) {} - template - InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2) : - m_held(p, a1, a2) {} - template - InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3) : - m_held(p, a1, a2, a3) {} - template - InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4) : - m_held(p, a1, a2, a3, a4) {} - template - InstanceValueHolder(ExtensionInstance* p, const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) : - m_held(p, a1, a2, a3, a4, a5) {} -private: - Wrapper m_held; -}; - -#endif 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_d.cpp b/functions_d.cpp deleted file mode 100644 index 2522b504..00000000 --- a/functions_d.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define DEBUG_PYTHON -#include "functions.cpp" diff --git a/gcc.mak b/gcc.mak index 00cea9e5..cec872cf 100644 --- a/gcc.mak +++ b/gcc.mak @@ -11,7 +11,15 @@ LIBSRC = \ 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 @@ -23,11 +31,11 @@ INC = -I/home/koethe/include -I/home/koethe/C++/boost -I/home/koethe/python/incl [ -s $@ ] || rm -f $@ demo: extclass_demo.o libpycpp.a - g++ -shared -o demomodule.so extclass_demo.o -L. -lpycpp + g++ -shared -o demomodule.$(MODULE_EXTENSION) $(PYTHON_LIB) extclass_demo.o -L. -lpycpp python test_extclass.py clean: - rm -rf *.o *.so *.a *.d *.pyc *.bak a.out + rm -rf *.o *.$(MODULE_EXTENSION) *.a *.d *.pyc *.bak a.out libpycpp.a: $(LIBOBJ) rm -f libpycpp.a diff --git a/gen_all.py b/gen_all.py index 744e936b..31eab966 100644 --- a/gen_all.py +++ b/gen_all.py @@ -11,7 +11,7 @@ def gen_all(args): open('init_function.h', 'w').write(gen_init_function(args)) open('signatures.h', 'w').write(gen_signatures(args)) open('singleton.h', 'w').write(gen_singleton(args)) - open('extclass_pygen.h', 'w').write(gen_extclass(args)) + open('extclass.h', 'w').write(gen_extclass(args)) if __name__ == '__main__': import sys diff --git a/gen_extclass.py b/gen_extclass.py index 04c2c555..13b84fb4 100644 --- a/gen_extclass.py +++ b/gen_extclass.py @@ -23,10 +23,352 @@ def gen_extclass(args): // This file automatically generated for %d-argument constructors by // gen_extclass.py -#ifndef EXTCLASS_PYGEN_DWA070900_H_ -# define EXTCLASS_PYGEN_DWA070900_H_ +#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 py { + +// forward declarations +class ExtensionInstance; +template class InstanceHolder; +template class InstanceValueHolder; +template class InstancePtrHolder; + +MetaClass* extension_meta_class(); +ExtensionInstance* get_extension_instance(PyObject* p); +void report_missing_instance_data(ExtensionInstance*, Class*, const std::type_info&); +void report_missing_ptr_data(ExtensionInstance*, Class*, const std::type_info&); +void report_missing_class_object(const std::type_info&); +void report_released_smart_pointer(const std::type_info&); +template +struct ExtensionClassFromPython +{ +}; + +template +T* check_non_null(T* p) +{ + if (p == 0) + report_released_smart_pointer(typeid(T)); + return p; +} + +template class HeldInstance; + +class ExtensionClassBase : public Class +{ + public: + ExtensionClassBase(const char* name); + protected: + void add_method(PyPtr method, const char* name); + void add_default_method(PyPtr method, const char* name); + void add_method(Function* method, const char* name); + void add_default_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 ClassRegistry +{ + public: + static Class* class_object() + { return static_class_object; } + static void register_class(py::Class*); + static void unregister_class(py::Class*); + private: + static py::Class* static_class_object; +}; + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // back to global namespace for this GCC bug +} +#endif + +// 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 = HeldInstance. +template > +class PyExtensionClassConverters +{ + public: +#ifdef BOOST_MSVC + // Convert return values of type T to python objects. What happens if T is + // not copyable? Apparently there is no problem with g++ or MSVC unless this + // is actually used. With a conforming compiler we will have a problem. + friend PyObject* to_python(const T& x) + { + py::PyPtr result(create_instance(false)); + result->add_implementation( + std::auto_ptr( + new py::InstanceValueHolder(result.get(), x))); + return result.release(); + } +#else + friend py::Type py_holder_type(const T&) + { return py::Type(); } +#endif + + PyExtensionClassConverters() {} + + // Convert to T* + friend T* from_python(PyObject* obj, py::Type) + { + // Downcast to an ExtensionInstance, then find the actual T + py::ExtensionInstance* self = py::get_extension_instance(obj); + typedef std::vector::const_iterator Iterator; + for (Iterator p = self->wrapped_objects().begin(); + p != self->wrapped_objects().end(); ++p) + { + py::InstanceHolder* held = dynamic_cast*>(*p); + if (held != 0) + return held->target(); + } + py::report_missing_instance_data(self, py::ClassRegistry::class_object(), typeid(T)); + throw py::ArgumentError(); + } + + // Convert to PtrType, where PtrType can be dereferenced to obtain a T. + template + static PtrType& ptr_from_python(PyObject* obj, py::Type) + { + // Downcast to an ExtensionInstance, then find the actual T + py::ExtensionInstance* self = py::get_extension_instance(obj); + typedef std::vector::const_iterator Iterator; + for (Iterator p = self->wrapped_objects().begin(); + p != self->wrapped_objects().end(); ++p) + { + py::InstancePtrHolder* held = + dynamic_cast*>(*p); + if (held != 0) + return held->ptr(); + } + py::report_missing_ptr_data(self, py::ClassRegistry::class_object(), typeid(T)); + throw py::ArgumentError(); + } + + template + static PyObject* ptr_to_python(PtrType x) + { + py::PyPtr result(create_instance(true)); + result->add_implementation( + std::auto_ptr( + new py::InstancePtrHolder(x))); + return result.release(); + } + + static py::PyPtr create_instance(bool seek_base) + { + if (py::ClassRegistry::class_object() == 0) + py::report_missing_class_object(typeid(T)); + + py::Class* class_ + = seek_base && py::ClassRegistry::class_object()->bases().size() > 0 + ? py::Downcast >( + py::ClassRegistry::class_object()->bases()[0].get()).get() + : py::ClassRegistry::class_object(); + + return py::PyPtr(new py::ExtensionInstance(class_)); + } + + + // Convert to const T* + friend const T* from_python(PyObject* p, py::Type) + { return from_python(p, py::Type()); } + + // Convert to T& + friend T& from_python(PyObject* p, py::Type) + { return *py::check_non_null(from_python(p, py::Type())); } + + // Convert to const T& + friend const T& from_python(PyObject* p, py::Type) + { return from_python(p, py::Type()); } + + // Convert to T + friend const T& from_python(PyObject* p, py::Type) + { return from_python(p, py::Type()); } + + friend std::auto_ptr& from_python(PyObject* p, py::Type&>) + { return ptr_from_python(p, py::Type >()); } + + friend std::auto_ptr& from_python(PyObject* p, py::Type >) + { return ptr_from_python(p, py::Type >()); } + + friend const std::auto_ptr& from_python(PyObject* p, py::Type&>) + { return ptr_from_python(p, py::Type >()); } + + friend PyObject* to_python(std::auto_ptr x) + { return ptr_to_python(x); } + + friend boost::shared_ptr& from_python(PyObject* p, py::Type&>) + { return ptr_from_python(p, py::Type >()); } + + friend boost::shared_ptr& from_python(PyObject* p, py::Type >) + { return ptr_from_python(p, py::Type >()); } + + friend const boost::shared_ptr& from_python(PyObject* p, py::Type&>) + { return ptr_from_python(p, py::Type >()); } + + friend PyObject* to_python(boost::shared_ptr x) + { return ptr_to_python(x); } +}; + +#ifndef BOOST_MSVC +template +py::InstanceHolderBase* +py_copy_to_new_value_holder(py::ExtensionInstance* p, const T& x, py::Type) +{ + return new py::InstanceValueHolder(p, x); +} + +template +PyObject* to_python(const T& x) +{ + py::PyPtr result( + PyExtensionClassConverters::create_instance(false)); + result->add_implementation( + std::auto_ptr( + py_copy_to_new_value_holder(result.get(), x, py_holder_type(x)))); + return result.release(); +} +#endif + +#ifdef PY_NO_INLINE_FRIENDS_IN_NAMESPACE // back from global namespace for this GCC bug +namespace py { +using ::PyExtensionClassConverters; +#endif + +template class InstanceHolder; + +class ReadOnlySetattrFunction : public Function +{ + public: + ReadOnlySetattrFunction(const char* name); + PyObject* do_call(PyObject* args, PyObject* keywords) const; + const char* description() const; + private: + String m_name; +}; + +// An easy way to make an extension base class which wraps T. Note that Python +// subclasses of this class will simply be Class 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 ExtensionClass + : public PyExtensionClassConverters, // This generates the to_python/from_python functions + public ExtensionClassBase +{ + public: + typedef T WrappedType; + typedef U CallbackType; + + // Construct with a name that comes from typeid(T).name(). The name only + // affects the objects of this class are represented through repr() + ExtensionClass(); + + // Construct with the given name. The name only affects the objects of this + // class are represented through repr() + ExtensionClass(const char* name); + + ~ExtensionClass(); + + // define constructors +""" % args + + gen_function( +""" template <%(class A%n%:, %)> + 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) + + +""" + // 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 + 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 + void def(Fn fn, const char* name, DefaultFn default_fn) + { + this->add_default_method(new_wrapped_function(default_fn), name); + this->add_method(new_wrapped_function(fn), name); + } + + // Provide a function which implements x., reading from the given + // member (pm) of the T instance + template + void def_getter(MemberType T::*pm, const char* name) + { + this->add_getter_method(new GetterFunction(pm), name); + } + + // Provide a function which implements assignment to x., writing to + // the given member (pm) of the T instance + template + void def_setter(MemberType T::*pm, const char* name) + { + this->add_setter_method(new SetterFunction(pm), name); + } + + // Expose the given member (pm) of the T instance as a read-only attribute + template + void def_readonly(MemberType T::*pm, const char* name) + { + this->add_setter_method(new ReadOnlySetattrFunction(name), name); + this->def_getter(pm, name); + } + + // Expose the given member (pm) of the T instance as a read/write attribute + template + void def_read_write(MemberType T::*pm, const char* name) + { + this->def_getter(pm, name); + this->def_setter(pm, name); + } + + private: + typedef InstanceValueHolder Holder; + + template + void add_constructor(Signature sig) + { + this->add_constructor_object(InitFunction::create(sig)); + } +}; + // A simple wrapper over a T which allows us to use ExtensionClass with a // single template parameter only. See ExtensionClass, above. template @@ -34,7 +376,7 @@ class HeldInstance : public T { // There are no member functions: we want to avoid inadvertently overriding // any virtual functions in T. -public:""" % args +public:""" + gen_functions(held_instance, args) + """ protected: @@ -66,8 +408,85 @@ public: private: Wrapper m_held; }; +""" + +""" +template +class InstancePtrHolder : public InstanceHolder +{ + public: + HeldType* target() { return &*m_ptr; } + PtrType& ptr() { return m_ptr; } -#endif + InstancePtrHolder(PtrType ptr) : m_ptr(ptr) {} + private: + PtrType m_ptr; +}; + +class ExtensionInstance : public Instance +{ + public: + ExtensionInstance(PyTypeObject* class_); + ~ExtensionInstance(); + + void add_implementation(std::auto_ptr holder); + + typedef std::vector WrappedObjects; + const WrappedObjects& wrapped_objects() const + { return m_wrapped_objects; } + private: + WrappedObjects m_wrapped_objects; +}; + +// +// Template function implementations +// + +template +ExtensionClass::ExtensionClass() + : ExtensionClassBase(typeid(T).name()) +{ + ClassRegistry::register_class(this); +} + +template +ExtensionClass::ExtensionClass(const char* name) + : ExtensionClassBase(name) +{ + ClassRegistry::register_class(this); +} + +template +ExtensionClass::~ExtensionClass() +{ + ClassRegistry::unregister_class(this); +} + +template +inline void ClassRegistry::register_class(Class* p) +{ + // You're not expected to create more than one of these! + assert(static_class_object == 0); + static_class_object = p; +} + +template +inline void ClassRegistry::unregister_class(Class* 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; +} + +// +// Static data member declaration. +// +template +Class* ClassRegistry::static_class_object; + +} // namespace py + +#endif // EXTENSION_CLASS_DWA052000_H_ """) if __name__ == '__main__': diff --git a/init_function_d.cpp b/init_function_d.cpp deleted file mode 100644 index fc40d2d2..00000000 --- a/init_function_d.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define DEBUG_PYTHON -#include "init_function.cpp" diff --git a/makefile b/makefile deleted file mode 100644 index f86b11cf..00000000 --- a/makefile +++ /dev/null @@ -1,48 +0,0 @@ -######################################################### -# -# makefile - Manhattan py_cpp makefile -# -# Author: David Abrahams -# Date: 04-May-2000 -# Copyright (c) 2000 Dragon Systems, Inc. -# -# Revision history at bottom. -# -######################################################### - -# Set up $(ROOT_DIR) -include ../make/userdirs.mak -include $(ROOT_DIR)/make/common.mak -include $(ROOT_DIR)/python/pythonhelp.mak -include $(ROOT_DIR)/utils/utilhelp.mak -include $(ROOT_DIR)/py_cpp/py_cpphelp.mak - -MAKEFILE_INCLUDES = $(PYTHON_INCLUDES) - -# Look in this directory, then the output subdirectory (BIN_DIR) for modules to load without qualification. -export PYTHONPATH := $(THISDIR)$(PYTHONPATH_SEP)$(THISDIR)/$(BIN_DIR)$(PYTHONPATH_SEP)$(PYTHONPATH) - -# In order to get the automatic dependency generation working correctly, it -# is necessary to list the source files here. -SRC_FILES = extclass.cpp init_function.cpp subclass.cpp functions.cpp module.cpp newtypes.cpp py.cpp objects.cpp -SRC_FILES += extclass_demo.cpp example1.cpp - -SRC_FILES += extclass_d.cpp init_function_d.cpp subclass_d.cpp functions_d.cpp module_d.cpp newtypes_d.cpp py_d.cpp objects_d.cpp -SRC_FILES += extclass_demo_d.cpp -LIBS=$(PYTHON_LIB) $(PYTHON_D_LIB) - -test : demo - $(PYTHON_EXE) test_extclass.py $(ARGS) - -test_d : demo_d - $(DEBUGGER) $(PYTHON_D_EXE) test_extclass.py $(ARGS) - -ifndef PYTHON_D_LIB -PYTHON_D_LIB = $(SPACE_CHAR) -endif - --include py_cpp.mk1 --include py_cpp_d.mk1 --include demo.mk1 --include demo_d.mk1 --include example1.mk1 \ No newline at end of file diff --git a/module_d.cpp b/module_d.cpp deleted file mode 100644 index a57ebca0..00000000 --- a/module_d.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define DEBUG_PYTHON -#include "module.cpp" diff --git a/newtypes.cpp b/newtypes.cpp index 3c169e9c..2cc570ee 100644 --- a/newtypes.cpp +++ b/newtypes.cpp @@ -86,21 +86,13 @@ static MethodStruct* enable_method(const MethodStruct* base, MemberPtr p, Fn f) return const_cast(detail::UniquePodSet::instance().get(new_value)); } -// TODO: is there a problem with calling convention here, or can I really pass a -// pointer to a C++ linkage function as a C-linkage function pointer? The -// compilers seem to swallow it, but is it legal? Symantec C++ for Mac didn't -// behave this way, FWIW. -// Using C++ linkage allows us to keep the virtual function members of -// TypeObjectBase private and use friendship to get them called. +namespace { -extern "C" { - -static PyObject* do_instance_repr(PyObject* instance) +PyObject* call(PyObject* instance, PyObject* (TypeObjectBase::*f)(PyObject*) const) { try { - return static_cast(instance->ob_type) - ->instance_repr(instance); + return (static_cast(instance->ob_type)->*f)(instance); } catch(...) { @@ -109,12 +101,14 @@ static PyObject* do_instance_repr(PyObject* instance) } } -static int do_instance_compare(PyObject* instance, PyObject* other) +// Naming this differently allows us to use it for functions returning long on +// compilers without partial ordering +template +R int_call(PyObject* instance, R (TypeObjectBase::*f)(PyObject*) const) { try { - return static_cast(instance->ob_type) - ->instance_compare(instance, other); + return (static_cast(instance->ob_type)->*f)(instance); } catch(...) { @@ -123,12 +117,18 @@ static int do_instance_compare(PyObject* instance, PyObject* other) } } -static PyObject* do_instance_str(PyObject* instance) +// Implemented in terms of int_call, above +int call(PyObject* instance, int (TypeObjectBase::*f)(PyObject*) const) +{ + return int_call(instance, f); +} + +template +PyObject* call(PyObject* instance, PyObject* (TypeObjectBase::*f)(PyObject*, A1) const, A1 a1) { try { - return static_cast(instance->ob_type) - ->instance_str(instance); + return (static_cast(instance->ob_type)->*f)(instance, a1); } catch(...) { @@ -137,12 +137,40 @@ static PyObject* do_instance_str(PyObject* instance) } } -static long do_instance_hash(PyObject* instance) +template +int call(PyObject* instance, int (TypeObjectBase::*f)(PyObject*, A1) const, A1 a1) { try { - return static_cast(instance->ob_type) - ->instance_hash(instance); + return (static_cast(instance->ob_type)->*f)(instance, a1); + } + catch(...) + { + handle_exception(); + return -1; + } +} + +template +PyObject* call(PyObject* instance, PyObject* (TypeObjectBase::*f)(PyObject*, A1, A2) const, A1 a1, A2 a2) +{ + try + { + return (static_cast(instance->ob_type)->*f)(instance, a1, a2); + } + catch(...) + { + handle_exception(); + return 0; + } +} + +template +int call(PyObject* instance, int (TypeObjectBase::*f)(PyObject*, A1, A2) const, A1 a1, A2 a2) +{ + try + { + return (static_cast(instance->ob_type)->*f)(instance, a1, a2); } catch(...) { @@ -151,20 +179,70 @@ static long do_instance_hash(PyObject* instance) } } -static PyObject* do_instance_call(PyObject* instance, PyObject* args, PyObject* keywords) +template +int call(PyObject* instance, int (TypeObjectBase::*f)(PyObject*, A1, A2, A3) const, A1 a1, A2 a2, A3 a3) { try { - return static_cast(instance->ob_type) - ->instance_call(instance, args, keywords); + return (static_cast(instance->ob_type)->*f)(instance, a1, a2, a3); } catch(...) { handle_exception(); - return 0; + return -1; } } +int call_length_function(PyObject* instance, int (TypeObjectBase::*f)(PyObject*) const) +{ + try + { + const int outcome = + (static_cast(instance->ob_type)->*f)(instance); + + if (outcome < 0) + { + PyErr_SetString(PyExc_ValueError, "__len__() should return >= 0"); + return -1; + } + return outcome; + } + catch(...) + { + handle_exception(); + return -1; + } +} + +} + +extern "C" { + +static PyObject* do_instance_repr(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_repr); +} + +static int do_instance_compare(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_compare, other); +} + +static PyObject* do_instance_str(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_str); +} + +static long do_instance_hash(PyObject* instance) +{ + return int_call(instance, &TypeObjectBase::instance_hash); +} + +static PyObject* do_instance_call(PyObject* instance, PyObject* args, PyObject* keywords) +{ + return call(instance, &TypeObjectBase::instance_call, args, keywords); +} + static void do_instance_dealloc(PyObject* instance) { try @@ -181,88 +259,29 @@ static void do_instance_dealloc(PyObject* instance) static PyObject* do_instance_getattr(PyObject* instance, char* name) { - try - { - return static_cast(instance->ob_type) - ->instance_getattr(instance, name); - } - catch(...) - { - handle_exception(); - return 0; - } + const char* name_ = name; + return call(instance, &TypeObjectBase::instance_getattr, name_); } static int do_instance_setattr(PyObject* instance, char* name, PyObject* value) { - try - { - return static_cast(instance->ob_type) - ->instance_setattr(instance, name, value); - } - catch(...) - { - handle_exception(); - return -1; - } + const char* name_ = name; + return call(instance, &TypeObjectBase::instance_setattr, name_, value); } static int do_instance_mp_length(PyObject* instance) { - try - { - const int outcome = - static_cast(instance->ob_type) - ->instance_mapping_length(instance); - - if (outcome < 0) - { - PyErr_SetString(PyExc_ValueError, "__len__() should return >= 0"); - return -1; - } - return outcome; - } - catch(...) - { - handle_exception(); - return -1; - } + return call_length_function(instance, &TypeObjectBase::instance_mapping_length); } static int do_instance_sq_length(PyObject* instance) { - try - { - const int outcome = - static_cast(instance->ob_type) - ->instance_sequence_length(instance); - - if (outcome < 0) - { - PyErr_SetString(PyExc_ValueError, "__len__() should return >= 0"); - return -1; - } - return outcome; - } - catch(...) - { - handle_exception(); - return -1; - } + return call_length_function(instance, &TypeObjectBase::instance_sequence_length); } static PyObject* do_instance_mp_subscript(PyObject* instance, PyObject* index) { - try - { - return static_cast(instance->ob_type) - ->instance_mapping_subscript(instance, index); - } - catch(...) - { - handle_exception(); - return 0; - } + return call(instance, &TypeObjectBase::instance_mapping_subscript, index); } static PyObject* do_instance_sq_item(PyObject* instance, int index) @@ -293,88 +312,149 @@ static PyObject* do_instance_sq_item(PyObject* instance, int index) static int do_instance_mp_ass_subscript(PyObject* instance, PyObject* index, PyObject* value) { - try - { - return static_cast(instance->ob_type) - ->instance_mapping_ass_subscript(instance, index, value); - } - catch(...) - { - handle_exception(); - return -1; - } + return call(instance, &TypeObjectBase::instance_mapping_ass_subscript, index, value); } static int do_instance_sq_ass_item(PyObject* instance, int index, PyObject* value) { - try - { - return static_cast(instance->ob_type) - ->instance_sequence_ass_item(instance, index, value); - } - catch(...) - { - handle_exception(); - return -1; - } + return call(instance, &TypeObjectBase::instance_sequence_ass_item, index, value); } static PyObject* do_instance_sq_concat(PyObject* instance, PyObject* other) { - try - { - return static_cast(instance->ob_type) - ->instance_sequence_concat(instance, other); - } - catch(...) - { - handle_exception(); - return 0; - } + return call(instance, &TypeObjectBase::instance_sequence_concat, other); } static PyObject* do_instance_sq_repeat(PyObject* instance, int n) { - try - { - return static_cast(instance->ob_type) - ->instance_sequence_repeat(instance, n); - } - catch(...) - { - handle_exception(); - return 0; - } + return call(instance, &TypeObjectBase::instance_sequence_repeat, n); } static PyObject* do_instance_sq_slice( PyObject* instance, int start, int finish) { - try - { - return static_cast(instance->ob_type) - ->instance_sequence_slice(instance, start, finish); - } - catch(...) - { - handle_exception(); - return 0; - } + return call(instance, &TypeObjectBase::instance_sequence_slice, start, finish); } static int do_instance_sq_ass_slice( PyObject* instance, int start, int finish, PyObject* value) { - try - { - return static_cast(instance->ob_type) - ->instance_sequence_ass_slice(instance, start, finish, value); - } - catch(...) - { - handle_exception(); - return -1; - } + return call(instance, &TypeObjectBase::instance_sequence_ass_slice, start, finish, value); +} + +static PyObject* do_instance_nb_add(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_add, other); +} + +static PyObject* do_instance_nb_subtract(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_subtract, other); +} + +static PyObject* do_instance_nb_multiply(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_multiply, other); +} + +static PyObject* do_instance_nb_divide(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_divide, other); +} + +static PyObject* do_instance_nb_remainder(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_remainder, other); +} + +static PyObject* do_instance_nb_divmod(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_divmod, other); +} + +static PyObject* do_instance_nb_power(PyObject* instance, PyObject* exponent, PyObject* modulus) +{ + return call(instance, &TypeObjectBase::instance_number_power, exponent, modulus); +} + +static PyObject* do_instance_nb_negative(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_negative); +} + +static PyObject* do_instance_nb_positive(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_positive); +} + +static PyObject* do_instance_nb_absolute(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_absolute); +} + +static int do_instance_nb_nonzero(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_nonzero); +} + +static PyObject* do_instance_nb_invert(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_invert); +} + + +static PyObject* do_instance_nb_lshift(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_lshift, other); +} + +static PyObject* do_instance_nb_rshift(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_rshift, other); +} + +static PyObject* do_instance_nb_and(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_and, other); +} + +static PyObject* do_instance_nb_xor(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_xor, other); +} + +static PyObject* do_instance_nb_or(PyObject* instance, PyObject* other) +{ + return call(instance, &TypeObjectBase::instance_number_or, other); +} + +static int do_instance_nb_coerce(PyObject**instance, PyObject**other) +{ + return call(*instance, &TypeObjectBase::instance_number_coerce, instance, other); +} +static PyObject* do_instance_nb_int(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_int); +} + +static PyObject* do_instance_nb_long(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_long); +} + +static PyObject* do_instance_nb_float(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_float); +} + +static PyObject* do_instance_nb_oct(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_oct); +} + +static PyObject* do_instance_nb_hex(PyObject* instance) +{ + return call(instance, &TypeObjectBase::instance_number_hex); } } @@ -384,24 +464,25 @@ template struct category_type; #define DECLARE_CAPABILITY_TYPE(field, sub_structure) \ template <> \ - struct category_type<(offsetof(PyTypeObject, tp_as_##field))> \ + struct category_type<(PY_OFFSETOF(PyTypeObject, tp_as_##field))> \ { \ typedef sub_structure type; \ } #define CAPABILITY(field) \ - { offsetof(PyTypeObject, tp_##field), 0, Dispatch(do_instance_##field), 0, -1 } + { PY_OFFSETOF(PyTypeObject, tp_##field), 0, Dispatch(do_instance_##field), 0, -1 } #define CAPABILITY2(category, field) \ - { offsetof(PyTypeObject, tp_as_##category), \ - offsetof(category_type::type, field), \ + { PY_OFFSETOF(PyTypeObject, tp_as_##category), \ + PY_OFFSETOF(category_type::type, field), \ Dispatch(do_instance_##field), \ - sizeof(category_type::type), \ - offsetof(AllMethods, category) \ + sizeof(category_type::type), \ + PY_OFFSETOF(AllMethods, category) \ } DECLARE_CAPABILITY_TYPE(mapping, PyMappingMethods); DECLARE_CAPABILITY_TYPE(sequence, PySequenceMethods); +DECLARE_CAPABILITY_TYPE(number, PyNumberMethods); const CapabilityEntry capabilities[] = { CAPABILITY(hash), @@ -422,7 +503,31 @@ const CapabilityEntry capabilities[] = { CAPABILITY2(sequence, sq_concat), CAPABILITY2(sequence, sq_repeat), CAPABILITY2(sequence, sq_slice), - CAPABILITY2(sequence, sq_ass_slice) + CAPABILITY2(sequence, sq_ass_slice), + + CAPABILITY2(number, nb_add), + CAPABILITY2(number, nb_subtract), + CAPABILITY2(number, nb_multiply), + CAPABILITY2(number, nb_divide), + CAPABILITY2(number, nb_remainder), + CAPABILITY2(number, nb_divmod), + CAPABILITY2(number, nb_power), + CAPABILITY2(number, nb_negative), + CAPABILITY2(number, nb_positive), + CAPABILITY2(number, nb_absolute), + CAPABILITY2(number, nb_nonzero), + CAPABILITY2(number, nb_invert), + CAPABILITY2(number, nb_lshift), + CAPABILITY2(number, nb_rshift), + CAPABILITY2(number, nb_and), + CAPABILITY2(number, nb_xor), + CAPABILITY2(number, nb_or), + CAPABILITY2(number, nb_coerce), + CAPABILITY2(number, nb_int), + CAPABILITY2(number, nb_long), + CAPABILITY2(number, nb_float), + CAPABILITY2(number, nb_oct), + CAPABILITY2(number, nb_hex) }; const std::size_t num_capabilities = PY_ARRAY_LENGTH(capabilities); @@ -445,7 +550,7 @@ const CapabilityEntry capabilities[] = { if (c.substructure_size == 0) { if (src == 0 || -#if defined(__MWERKS__) && __MWERKS__ <= 0x4000 +#if defined(__MWERKS__) && __MWERKS__ <= 0x4000 || defined(__alpha) && defined(__osf__) ((const Dispatch*)src) #else reinterpret_cast(src) @@ -621,6 +726,121 @@ int TypeObjectBase::instance_sequence_ass_slice(PyObject*, int, int, PyObject*) return unimplemented("instance_sequence_ass_slice"); } +PyObject* TypeObjectBase::instance_number_add(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_add"); +} + +PyObject* TypeObjectBase::instance_number_subtract(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_subtract"); +} + +PyObject* TypeObjectBase::instance_number_multiply(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_multiply"); +} + +PyObject* TypeObjectBase::instance_number_divide(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_divide"); +} + +PyObject* TypeObjectBase::instance_number_remainder(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_remainder"); +} + +PyObject* TypeObjectBase::instance_number_divmod(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_divmod"); +} + +PyObject* TypeObjectBase::instance_number_power(PyObject*, PyObject*, PyObject*) const +{ + return unimplemented("instance_number_divmod"); +} + +PyObject* TypeObjectBase::instance_number_negative(PyObject*) const +{ + return unimplemented("instance_number_negative"); +} + +PyObject* TypeObjectBase::instance_number_positive(PyObject*) const +{ + return unimplemented("instance_number_positive"); +} + +PyObject* TypeObjectBase::instance_number_absolute(PyObject*) const +{ + return unimplemented("instance_number_absolute"); +} + +int TypeObjectBase::instance_number_nonzero(PyObject*) const +{ + return unimplemented("instance_number_nonzero"); +} + +PyObject* TypeObjectBase::instance_number_invert(PyObject*) const +{ + return unimplemented("instance_number_invert"); +} + +PyObject* TypeObjectBase::instance_number_lshift(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_lshift"); +} + +PyObject* TypeObjectBase::instance_number_rshift(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_rshift"); +} + +PyObject* TypeObjectBase::instance_number_and(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_and"); +} + +PyObject* TypeObjectBase::instance_number_xor(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_xor"); +} + +PyObject* TypeObjectBase::instance_number_or(PyObject*, PyObject*) const +{ + return unimplemented("instance_number_or"); +} + +int TypeObjectBase::instance_number_coerce(PyObject*, PyObject**, PyObject**) const +{ + return unimplemented("instance_number_coerce"); +} + +PyObject* TypeObjectBase::instance_number_int(PyObject*) const +{ + return unimplemented("instance_number_int"); +} + +PyObject* TypeObjectBase::instance_number_long(PyObject*) const +{ + return unimplemented("instance_number_long"); +} + +PyObject* TypeObjectBase::instance_number_float(PyObject*) const +{ + return unimplemented("instance_number_float"); +} + +PyObject* TypeObjectBase::instance_number_oct(PyObject*) const +{ + return unimplemented("instance_number_oct"); +} + +PyObject* TypeObjectBase::instance_number_hex(PyObject*) const +{ + return unimplemented("instance_number_hex"); +} + TypeObjectBase::~TypeObjectBase() { } diff --git a/newtypes.h b/newtypes.h index 94382232..ec8839a8 100644 --- a/newtypes.h +++ b/newtypes.h @@ -30,6 +30,8 @@ namespace py { +class InstanceHolderBase; + class TypeObjectBase : public PythonType { public: @@ -42,7 +44,14 @@ class TypeObjectBase : public PythonType 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 + 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); @@ -75,7 +84,31 @@ class TypeObjectBase : public PythonType virtual PyObject* instance_sequence_slice(PyObject* instance, int start, int finish) const; virtual int instance_sequence_ass_item(PyObject* instance, int n, PyObject* value) const; virtual int instance_sequence_ass_slice(PyObject* instance, 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 diff --git a/objects.h b/objects.h index c2d1eabe..4f54befc 100644 --- a/objects.h +++ b/objects.h @@ -13,6 +13,7 @@ # include "pyconfig.h" # include "pyptr.h" # include "boost/operators.hpp" +# include namespace py { @@ -36,8 +37,31 @@ class Tuple : public Object public: Tuple(std::size_t n = 0); explicit Tuple(Ptr p); + + template + Tuple(const std::pair& x) + : Object(Ptr(PyTuple_New(2))) + { + set_item(0, Ptr(to_python(x.first))); + set_item(1, Ptr(to_python(x.second))); + } - Tuple(const Ptr* start, const Ptr* finish); // not yet implemented. + template + Tuple(const First& first, const Second& second) + : Object(Ptr(PyTuple_New(2))) + { + set_item(0, Ptr(to_python(first))); + set_item(1, Ptr(to_python(second))); + } + + template + Tuple(const First& first, const Second& second, const Third& third) + : Object(Ptr(PyTuple_New(3))) + { + set_item(0, Ptr(to_python(first))); + set_item(1, Ptr(to_python(second))); + set_item(2, Ptr(to_python(third))); + } static PyTypeObject* type_object(); static bool accepts(Ptr p); diff --git a/objects_d.cpp b/objects_d.cpp deleted file mode 100644 index 95e19316..00000000 --- a/objects_d.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define DEBUG_PYTHON -#include "objects.cpp" diff --git a/overloading.html b/overloading.html new file mode 100644 index 00000000..499df5dc --- /dev/null +++ b/overloading.html @@ -0,0 +1,139 @@ + + + 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
+    {
+        py::Module overload_demo("overload_demo");
+        // Overloaded functions at module scope
+        overload_demo.def(f1, "f");
+        overload_demo.def(f2, "f");
+
+        py::ClassWrapper<X> x_class(overload_demo, "X");
+        // Overloaded constructors
+        x_class.def(py::Constructor<>());
+        x_class.def(py::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 way. When a + class is found which has a matching attribute, only functions overloaded + in the context of that class are candidates for overload resolution. +
  • 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. +
+

+ Prev: 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 15, 2000 +

+ diff --git a/overriding.html b/overriding.html new file mode 100644 index 00000000..82cb442d --- /dev/null +++ b/overriding.html @@ -0,0 +1,175 @@ + + + + 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. Of course, the first + step if we want it to act like a virtual function when called from our + C++ code, is to make it virtual: +

+
+  class world
+  {
+    ...
+      virtual const char* get() const { return "hi, world"; }
+    ...
+  };
+
+
+

+ Then we'll need a derived class* to help us + dispatch the call to Python: +

+
+struct world_callback : hello::world
+{
+    // The first argument must by a PyObject* (the corresponding Python object)
+    // The rest of the argument list should match the base class constructor
+    world_callback(PyObject* self, int x)
+        : world(x),            // dispatch to base object
+          m_self(self) {}      // hang onto the Python object
+
+    // Dispatch the call to Python
+    const char* get() const
+    {
+        // Any function arguments would go at the end of the argument list
+        // The return type is a template parameter
+        return py::Callback<const char*>::call_method(m_self, "get");
+    }
+
+    // Something Python can call in case there is no override of get()
+    const char* default_get() const
+        { return this->hello::world::get(); }
+ private:
+    PyObject* m_self; // A way to hold onto the Python object
+};
+
+
+

+ Finally, we add world_callback to the + ClassWrapper<> declaration in our module initialization + function: +

+
+// Create the Python type object for our extension class
+py::ClassWrapper<hello::world,world_callback> world_class(hello, "world");
+       ...
+
+
+

+ ...and when we define the function, we must tell py_cpp about the default + implementation: +

+
+// Add a virtual member function
+world_class.def(&hello::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. +

+ 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 ExtensionClass<> 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 void pure(int) = 0;
+};
+
+struct baz_callback
+{
+    void pure(int x) { py::Callback<void>::call_method(m_self, "pure", x); }
+};
+
+extern "C"
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+initfoobar()
+{
+    try
+    {
+       py::Module foobar("foobar");
+       py::ClassWrapper<baz,baz_callback> baz_class("baz");
+       baz_class.def(&baz::pure, "pure");
+    }
+    catch(...)
+    {
+       py::handle_exception();    // Deal with the exception for Python
+    }
+}
+
+
+

+ Now in Python: +

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

+ Prev: A Simple Example Using py_cpp Next: A Peek Under the Hood Up: Top +

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

+ Updated: Sept 30, 2000 + diff --git a/py.cpp b/py.cpp index a64e27c5..9ac2490d 100644 --- a/py.cpp +++ b/py.cpp @@ -7,7 +7,11 @@ // producing this work. #include "py.h" -#include +#include +#include +#ifndef BOOST_NO_LIMITS +# include +#endif namespace py { @@ -82,11 +86,19 @@ T integer_from_python(PyObject* p, py::Type) { const long long_result = from_python(p, py::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"; @@ -103,12 +115,17 @@ template PyObject* integer_to_python(T value) { long value_as_long; - + +#ifndef BOOST_NO_LIMITS try { - value_as_long = boost::numeric_cast(value); + 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); diff --git a/py.h b/py.h index 2cf89807..06365764 100644 --- a/py.h +++ b/py.h @@ -165,6 +165,11 @@ PyObject* to_python(boost::shared_ptr p) // inline implementations // +inline PyObject* to_python(double d) +{ + return PyFloat_FromDouble(d); +} + inline PyObject* to_python(long l) { return PyInt_FromLong(l); diff --git a/py_cpp.bdf b/py_cpp.bdf deleted file mode 100644 index 32be8441..00000000 --- a/py_cpp.bdf +++ /dev/null @@ -1,4 +0,0 @@ -TargetName=py_cpp -TargetType=lib -SourceFiles=extclass.cpp init_function.cpp subclass.cpp functions.cpp objects.cpp -SourceFiles+=module.cpp newtypes.cpp py.cpp diff --git a/py_cpp.html b/py_cpp.html new file mode 100644 index 00000000..6de92e5e --- /dev/null +++ b/py_cpp.html @@ -0,0 +1,122 @@ + + + + py_cpp Python/C++ binding documentation + +

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

+

+ 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 source code for py_cpp, including a MSVC demo project is available + here. It has been tested against Python + 1.5.2 with GCC 2.95.2, Metrowerks CodeWarrior Pro6 and with Microsoft + Visual C++ 6 sp4 using both the + STLport standard library implementation and the library + implementation which ships with the compiler. It has also been tested + against Python 2.0c1 with MSVC++ 6sp4 by Alex Martelli. + Py_cpp requires the Boost libraries, + and is currently under formal review on the boost mailing list for + acceptance into boost. +

+ py_cpp was originally written by David Abrahams. Ullrich Koethe supplied + an early version of the overloading support and wrote the support for + implicit conversions of arguments that have a C++ inheritance + relationship. Alex Martelli supplied the first tests against Python 2.0. + The members of the boost mailing list and the Python community supplied + invaluable early feedback. The development of py_cpp wouldn't have been + possible without the generous support of Dragon Systems/Lernout and + Hauspie, Inc. +

+ 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. + Special Method Name Support +
  7. + A Peek Under the Hood +
+

+ More sophisticated examples, including examples which demonstrate that + these ExtensionClasses support some of Python's "special" member + functions (e.g. __getattr__(self, name)), 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 or to David Abrahams, the primary + author and maintainer. +

+ 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 +
  • + C-thru +
  • + SeamlessC +
  • + BorderCrossing +
+ 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: Oct 18, 2000 + diff --git a/py_cpp_20001013.zip b/py_cpp_20001013.zip deleted file mode 100644 index 420d2bc4..00000000 Binary files a/py_cpp_20001013.zip and /dev/null differ diff --git a/py_cpp_d.bdf b/py_cpp_d.bdf deleted file mode 100644 index e8598e51..00000000 --- a/py_cpp_d.bdf +++ /dev/null @@ -1,4 +0,0 @@ -TargetName=py_cpp_d -TargetType=lib -SourceFiles=extclass_d.cpp init_function_d.cpp subclass_d.cpp functions_d.cpp objects_d.cpp -SourceFiles+=module_d.cpp newtypes_d.cpp py_d.cpp diff --git a/py_cpphelp.mak b/py_cpphelp.mak deleted file mode 100644 index f0367ac0..00000000 --- a/py_cpphelp.mak +++ /dev/null @@ -1,21 +0,0 @@ -ifndef PY_CPPHELP_MAK_INCLUDED -PY_CPPHELP_MAK_INCLUDED=1 - -ifndef DONT_BUILD_IMPORTS - - -PY_CPP_DIR=$(ROOT_DIR)/py_cpp -PY_CPP_LIB=$(PY_CPP_DIR)/$(OBJDIR)/py_cpp.lib -PY_CPP_D_LIB=$(PY_CPP_DIR)/$(OBJDIR)/py_cpp_d.lib - -$(PY_CPP_LIB) : FORCE - $(RECURSIVE_MAKE) -C $(PY_CPP_DIR) py_cpp - -$(PY_CPP_D_LIB) : FORCE - $(RECURSIVE_MAKE) -C $(PY_CPP_DIR) py_cpp_d - -# end ifndef DONT_BUILD_IMPORTS -endif - -# end ifndef PY_CPPHELP_MAK_INCLUDED -endif diff --git a/py_d.cpp b/py_d.cpp deleted file mode 100644 index 59a6c210..00000000 --- a/py_d.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define DEBUG_PYTHON -#include "py.cpp" diff --git a/pyconfig.h b/pyconfig.h index 35a6bf1e..cd90e7d7 100644 --- a/pyconfig.h +++ b/pyconfig.h @@ -19,7 +19,7 @@ # define PY_INLINE_FRIEND ::py # endif -# if !defined(__GNUC__) && !defined(__MWERKS__) && !defined(__BORLANDC__) && defined(_MSC_VER) +# if defined(BOOST_MSVC) # define PY_COMPILER_IS_MSVC 1 # if _MSC_VER <= 1200 # define PY_MSVC6_OR_EARLIER 1 @@ -29,6 +29,17 @@ # 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 PY_OFFSETOF(s_name, s_member) \ + ((size_t)__INTADDR__(&(((s_name *)0)->s_member))) +# else +# define PY_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(PY_MSVC6_OR_EARLIER) && !defined(__STLPORT) diff --git a/special.html b/special.html new file mode 100644 index 00000000..22ed7d04 --- /dev/null +++ b/special.html @@ -0,0 +1,401 @@ + + + Special Method Name Support + +

+

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

+

+ Overview +

+

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

    +
  • + __del__ (an oversight, to be corrected soon - but + don't worry, your underlying C++ objects are still properly destroyed!) +
  • the __r<name>__ "reversed operand" + numeric methods, and +
  • __complex__ +
+ + (more on the latter two below). So, for example, we can wrap a + std::map<std::size_t,std::string> as follows: +

+ Example +

+
+
+typedef std::map<std::size_t, std::string> StringMap;
+
+// A helper function for dealing with errors. Throw a Python exception
+// if p == m.end().
+void throw_key_error_if_end(
+        const StringMap& m, 
+        StringMap::const_iterator p, 
+        std::size_t key)
+{
+    if (p == m.end())
+    {
+        PyErr_SetObject(PyExc_KeyError, py::converters::to_python(key));
+        throw py::ErrorAlreadySet();
+    }
+}
+
+// Define some simple wrapper functions which match the Python  protocol
+// for __getitem__, __setitem__, and __delitem__.  Just as in Python, a
+// free function with a "self" first parameter makes a fine class method.
+
+const std::string& get_item(const StringMap& self, std::size_t key)
+{
+    const StringMap::const_iterator p = self.find(key);
+    throw_key_error_if_end(self, p, key);
+    return p->second;
+}
+
+// Sets the item corresponding to key in the map.
+void StringMapPythonClass::set_item(StringMap& self, std::size_t key, const std::string& value)
+{
+    self[key] = value;
+}
+
+// Deletes the item corresponding to key from the map.
+void StringMapPythonClass::del_item(StringMap& self, std::size_t key)
+{
+    const StringMap::iterator p = self.find(key);
+    throw_key_error_if_end(self, p, key);
+    self.erase(p);
+}
+
+ClassWrapper<StringMap> string_map(my_module, "StringMap");
+string_map.def(py::Constructor<>());
+string_map.def(&StringMap::size, "__len__");
+string_map.def(get_item, "__getitem__");
+string_map.def(set_item, "__setitem__");
+string_map.def(del_item, "__delitem__");
+
+
+

+ Then in Python: +

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

+ Getters and Setters +

+

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

    +
  • + __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 ClassWrapper<> and + ExtensionClass<>: +

    +
  • + 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");
+
+ClassWrapper<Pil> pair_int_long(my_module, "Pair");
+pair_int_long.def(py::Constructor<>());
+pair_int_long.def(py::Constructor<int,long>());
+pair_int_long.def_read_write(&Pil::first, "first");
+pair_int_long.def_read_write(&Pil::second, "second");
+
+
+

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

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

+ Numeric Method Support +

+

+ Py_cpp supports the following + Python special numeric method names: +

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

Where are the __r<name>__ +functions?

+ +

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

And what about __complex__?

+

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

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

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

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

+ Updated: Oct 19, 2000 +

+ diff --git a/subclass.cpp b/subclass.cpp index 3f1d25ec..3a133be0 100644 --- a/subclass.cpp +++ b/subclass.cpp @@ -38,12 +38,8 @@ PyObject* Instance::getattr(const char* name, bool use_special_function) } else { - // Suspend the error while we try special methods method (if any). -#if 0 - SuspendError suspended_error(SuspendError::discard_new_error); -#else + // Clear the error while we try special methods method (if any). PyErr_Clear(); -#endif // First we try the special method that comes from concatenating // "__getattr__" and and 2 trailing underscores. This is an @@ -67,15 +63,11 @@ PyObject* Instance::getattr(const char* name, bool use_special_function) } // If there is no such method, throw now. -#if 0 - suspended_error.throw_if_error(); -#else if (PyErr_Occurred()) { PyErr_SetString(PyExc_AttributeError, name); throw ErrorAlreadySet(); } -#endif // Take ownership of the method Ptr owner(getattr_method); @@ -224,6 +216,131 @@ void Instance::set_slice(int start, int finish, PyObject* value) 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 NamedCapability { @@ -250,7 +367,30 @@ namespace { { "__delitem__", TypeObjectBase::sequence_ass_item }, { "__getslice__", TypeObjectBase::sequence_slice }, { "__setslice__", TypeObjectBase::sequence_ass_slice }, - { "__delslice__", TypeObjectBase::sequence_ass_slice } + { "__delslice__", TypeObjectBase::sequence_ass_slice }, + { "__add__", TypeObjectBase::number_add }, + { "__sub__", TypeObjectBase::number_subtract }, + { "__mul__", TypeObjectBase::number_multiply }, + { "__div__", TypeObjectBase::number_divide }, + { "__mod__", TypeObjectBase::number_remainder }, + { "__divmod__", TypeObjectBase::number_divmod }, + { "__pow__", TypeObjectBase::number_power }, + { "__neg__", TypeObjectBase::number_negative }, + { "__pos__", TypeObjectBase::number_positive }, + { "__abs__", TypeObjectBase::number_absolute }, + { "__nonzero__", TypeObjectBase::number_nonzero }, + { "__invert__", TypeObjectBase::number_invert }, + { "__lshift__", TypeObjectBase::number_lshift }, + { "__rshift__", TypeObjectBase::number_rshift }, + { "__and__", TypeObjectBase::number_and }, + { "__xor__", TypeObjectBase::number_xor }, + { "__or__", TypeObjectBase::number_or }, + { "__coerce__", TypeObjectBase::number_coerce }, + { "__int__", TypeObjectBase::number_int }, + { "__long__", TypeObjectBase::number_long }, + { "__float__", TypeObjectBase::number_float }, + { "__oct__", TypeObjectBase::number_oct }, + { "__hex__", TypeObjectBase::number_hex } }; bool is_prefix(const char* s1, const char* s2) diff --git a/subclass.h b/subclass.h index 4d745bc0..a442ca30 100644 --- a/subclass.h +++ b/subclass.h @@ -44,7 +44,32 @@ class Instance : public PythonObject // 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&); @@ -92,6 +117,31 @@ class Class 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* instance, PyObject* args, PyObject* keywords) const; @@ -316,6 +366,144 @@ PyObject* Class::instance_call(PyObject* instance, PyObject* args, PyObject* return Downcast(instance)->call(args, keywords); } +template +PyObject* Class::instance_number_add(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->add(other); +} + +template +PyObject* Class::instance_number_subtract(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->subtract(other); +} + +template +PyObject* Class::instance_number_multiply(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->multiply(other); +} + +template +PyObject* Class::instance_number_divide(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->divide(other); +} + +template +PyObject* Class::instance_number_remainder(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->remainder(other); +} + +template +PyObject* Class::instance_number_divmod(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->divmod(other); +} + +template +PyObject* Class::instance_number_power(PyObject* instance, PyObject* exponent, PyObject* modulus) const +{ + return Downcast(instance)->power(exponent, modulus); +} + +template +PyObject* Class::instance_number_negative(PyObject* instance) const +{ + return Downcast(instance)->negative(); +} + +template +PyObject* Class::instance_number_positive(PyObject* instance) const +{ + return Downcast(instance)->positive(); +} + +template +PyObject* Class::instance_number_absolute(PyObject* instance) const +{ + return Downcast(instance)->absolute(); +} + +template +int Class::instance_number_nonzero(PyObject* instance) const +{ + return Downcast(instance)->nonzero(); +} + +template +PyObject* Class::instance_number_invert(PyObject* instance) const +{ + return Downcast(instance)->invert(); +} + +template +PyObject* Class::instance_number_lshift(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->lshift(other); +} + +template +PyObject* Class::instance_number_rshift(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->rshift(other); +} + +template +PyObject* Class::instance_number_and(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->do_and(other); +} + +template +PyObject* Class::instance_number_xor(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->do_xor(other); +} + +template +PyObject* Class::instance_number_or(PyObject* instance, PyObject* other) const +{ + return Downcast(instance)->do_or(other); +} + +template +int Class::instance_number_coerce(PyObject* instance, PyObject** x, PyObject** y) const +{ + return Downcast(instance)->coerce(x, y); +} + +template +PyObject* Class::instance_number_int(PyObject* instance) const +{ + return Downcast(instance)->as_int(); +} + +template +PyObject* Class::instance_number_long(PyObject* instance) const +{ + return Downcast(instance)->as_long(); +} + +template +PyObject* Class::instance_number_float(PyObject* instance) const +{ + return Downcast(instance)->as_float(); +} + +template +PyObject* Class::instance_number_oct(PyObject* instance) const +{ + return Downcast(instance)->oct(); +} + +template +PyObject* Class::instance_number_hex(PyObject* instance) const +{ + return Downcast(instance)->hex(); +} + template Dict& Class::dict() { diff --git a/subclass_d.cpp b/subclass_d.cpp deleted file mode 100644 index cfd27dc2..00000000 --- a/subclass_d.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define DEBUG_PYTHON -#include "subclass.cpp" diff --git a/test_extclass.py b/test_extclass.py index 1912b237..8861aa06 100644 --- a/test_extclass.py +++ b/test_extclass.py @@ -15,10 +15,11 @@ a single long parameter. File "", line 1, in ? TypeError: function requires exactly 1 argument; 0 given - >>> ext = Foo('foo') - Traceback (innermost last): - File "", line 1, in ? - TypeError: illegal argument type for built-in operation + >>> 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) @@ -129,7 +130,7 @@ But objects not derived from Bar cannot: >>> baz.pass_bar(baz) Traceback (innermost last): ... - TypeError: extension class 'Baz' is not derived from 'Bar'. + TypeError: extension class 'Baz' is not convertible into 'Bar'. The clone function on Baz returns a smart pointer; we wrap it into an ExtensionInstance and make it look just like any other Baz instance. @@ -144,6 +145,8 @@ Functions expecting an std::auto_ptr parameter will not accept a raw 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 @@ -159,6 +162,8 @@ And if the auto_ptr has given up ownership? ... 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: @@ -232,6 +237,7 @@ Overloading tests: ... 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)) @@ -258,6 +264,22 @@ Sequence tests: >>> 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): @@ -368,7 +390,7 @@ some __str__ and __repr__ tests: >>> range = Range(5, 20) >>> str(range) '(5, 20)' - >>> assert re.match('', repr(range)) + >>> assert re.match('', repr(range)) __hash__ and __cmp__ tests: @@ -406,6 +428,146 @@ Testing __call__: 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 ''' from demo import * diff --git a/todo.txt b/todo.txt index d8250858..ab9db577 100644 --- a/todo.txt +++ b/todo.txt @@ -5,22 +5,95 @@ Document error-handling Consider renaming PyPtr to Reference. Report Cygwin linker memory issues handle more arguments +MI from both ExtensionClasses and Python classes, or at least don't crash(!) Remove one level of indirection on type objects (no vtbl?). -Much more testing, especially of things in objects.h -Make multiple inheritance from real Python classes work. +Make multiple inheritance from real Python classes work Handle polymorphism (passing a Wrapped as a Base*). Specializations of Caller<> for commmon combinations of argument types (?) +special member functions for numeric types +pickling support +testing with Python 2.0 +Make abstract classes non-instantiable (?) +Much more testing, especially of things in objects.h +Support for Python LONG types in Objects.h +Concept checking for to_python() template function +Support for __del__() +Reference-counting for UniquePodSet? Documentation: + building + + overloading and the overload resolution mechanism + special member functions - adding conversions for fundamental types - generic conversions for template types (with partial spec). - extending multiple-argument support. + differences between Python classes and ExtensionClasses additional capabilities of ExtensionClasses + slice adjustment + exception handling - building - dealing with non-const reference/pointer parameters + + 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 + + Private virtual functions with default implementations + + extending multiple-argument support using gen_all.py + + limitations + 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. + + enums + + the importance of declaration order of ClassWrappers/ExtensionInstances + + out parameters and non-const pointers + + returning references to wrapped objects + + About the vc6 project and the debug build + + About doctest.py Boost remarks: diff --git a/under-the-hood.html b/under-the-hood.html new file mode 100644 index 00000000..fb17539f --- /dev/null +++ b/under-the-hood.html @@ -0,0 +1,58 @@ + + + + A Peek Under the Hood + +

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

+

+ A Peek Under the Hood +

+

+ ExtensionClass<T> is a subclass of + PyTypeObject, the struct which Python's 'C' API uses + to describe a type. An instance of + the ExtensionClass<> 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 ExtensionClass<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 + ExtensionClass<T>, it is important that an instantiation of + ExtensionClass<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 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/ReadMe.txt b/vc6_prj/ReadMe.txt deleted file mode 100644 index a6b8aea4..00000000 --- a/vc6_prj/ReadMe.txt +++ /dev/null @@ -1,41 +0,0 @@ -======================================================================== - DYNAMIC LINK LIBRARY : vc6_prj -======================================================================== - - -AppWizard has created this vc6_prj DLL for you. - -This file contains a summary of what you will find in each of the files that -make up your vc6_prj application. - -vc6_prj.dsp - This file (the project file) contains information at the project level and - is used to build a single project or subproject. Other users can share the - project (.dsp) file, but they should export the makefiles locally. - -vc6_prj.cpp - This is the main DLL source file. - - When created, this DLL does not export any symbols. As a result, it - will not produce a .lib file when it is built. If you wish this project - to be a project dependency of some other project, you will either need to - add code to export some symbols from the DLL so that an export library - will be produced, or you can check the "doesn't produce lib" checkbox in - the Linker settings page for this project. - -///////////////////////////////////////////////////////////////////////////// -Other standard files: - -StdAfx.h, StdAfx.cpp - These files are used to build a precompiled header (PCH) file - named vc6_prj.pch and a precompiled types file named StdAfx.obj. - - -///////////////////////////////////////////////////////////////////////////// -Other notes: - -AppWizard uses "TODO:" to indicate parts of the source code you -should add to or customize. - - -///////////////////////////////////////////////////////////////////////////// diff --git a/vc6_prj/StdAfx.cpp b/vc6_prj/StdAfx.cpp deleted file mode 100644 index 5a711fb5..00000000 --- a/vc6_prj/StdAfx.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// stdafx.cpp : source file that includes just the standard includes -// vc6_prj.pch will be the pre-compiled header -// stdafx.obj will contain the pre-compiled type information - -#include "stdafx.h" - -// TODO: reference any additional headers you need in STDAFX.H -// and not in this file diff --git a/vc6_prj/StdAfx.h b/vc6_prj/StdAfx.h deleted file mode 100644 index 9bd983af..00000000 --- a/vc6_prj/StdAfx.h +++ /dev/null @@ -1,24 +0,0 @@ -// stdafx.h : include file for standard system include files, -// or project specific include files that are used frequently, but -// are changed infrequently -// - -#if !defined(AFX_STDAFX_H__A41B37EF_17F7_406A_95AC_FE67BDB05B1E__INCLUDED_) -#define AFX_STDAFX_H__A41B37EF_17F7_406A_95AC_FE67BDB05B1E__INCLUDED_ - -#if _MSC_VER > 1000 -#pragma once -#endif // _MSC_VER > 1000 - - -// Insert your headers here -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers - -#include - -// TODO: reference additional headers your program requires here - -//{{AFX_INSERT_LOCATION}} -// Microsoft Visual C++ will insert additional declarations immediately before the previous line. - -#endif // !defined(AFX_STDAFX_H__A41B37EF_17F7_406A_95AC_FE67BDB05B1E__INCLUDED_) diff --git a/vc6_prj/vc6_prj.cpp b/vc6_prj/vc6_prj.cpp deleted file mode 100644 index 7d098f47..00000000 --- a/vc6_prj/vc6_prj.cpp +++ /dev/null @@ -1,13 +0,0 @@ -// vc6_prj.cpp : Defines the entry point for the DLL application. -// - -#include "stdafx.h" - -BOOL APIENTRY DllMain( HANDLE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved - ) -{ - return TRUE; -} - diff --git a/vc6_prj/vc6_prj.opt b/vc6_prj/vc6_prj.opt index fa7645c3..543c06ad 100644 Binary files a/vc6_prj/vc6_prj.opt and b/vc6_prj/vc6_prj.opt differ