From e59a0aef06be2fae88e4ad560f06ec36fdb83c98 Mon Sep 17 00:00:00 2001 From: nobody Date: Tue, 3 Dec 2002 21:38:20 +0000 Subject: [PATCH] This commit was manufactured by cvs2svn to create tag 'RC_1_29_0_last_merge'. [SVN r16498] --- doc/comparisons.html | 231 +++ doc/cross_module.html | 336 +++++ doc/data_structures.txt | 192 +++ doc/enums.html | 120 ++ doc/example1.html | 75 + doc/exporting_classes.html | 143 ++ doc/extending.html | 73 + doc/inheritance.html | 166 +++ doc/overloading.html | 155 ++ doc/overriding.html | 208 +++ doc/pickle.html | 272 ++++ doc/pointers.html | 148 ++ doc/richcmp.html | 106 ++ doc/special.html | 973 +++++++++++++ doc/under-the-hood.html | 61 + include/boost/python.hpp | 62 - .../converter/rvalue_from_python_data.hpp | 3 +- include/boost/python/errors.hpp | 2 + include/boost/python/init.hpp | 12 +- include/boost/python/operators.hpp | 837 +++++++---- test/comprehensive.cpp | 1265 ++++++++++++++++ test/comprehensive.hpp | 235 +++ test/comprehensive.py | 1281 +++++++++++++++++ 23 files changed, 6579 insertions(+), 377 deletions(-) create mode 100644 doc/comparisons.html create mode 100644 doc/cross_module.html create mode 100644 doc/data_structures.txt create mode 100644 doc/enums.html create mode 100644 doc/example1.html create mode 100644 doc/exporting_classes.html create mode 100644 doc/extending.html create mode 100644 doc/inheritance.html create mode 100644 doc/overloading.html create mode 100644 doc/overriding.html create mode 100644 doc/pickle.html create mode 100644 doc/pointers.html create mode 100644 doc/richcmp.html create mode 100644 doc/special.html create mode 100644 doc/under-the-hood.html delete mode 100644 include/boost/python.hpp create mode 100644 test/comprehensive.cpp create mode 100644 test/comprehensive.hpp create mode 100644 test/comprehensive.py diff --git a/doc/comparisons.html b/doc/comparisons.html new file mode 100644 index 00000000..57cec744 --- /dev/null +++ b/doc/comparisons.html @@ -0,0 +1,231 @@ + + + Comparisons with Other Systems + +
+

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

+ +

CXX

+

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

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

+ As far as I can tell, CXX enables one to write what is essentially + idiomatic Python code in C++, manipulating Python objects through the + same fully-generic interfaces we use in Python. While you're hardly + programming directly to the ``bare metal'' with CXX, it basically + presents a ``C++-ized'' version of the Python 'C' API. Some fraction of + that capability is available in Boost.Python through boost/python/objects.hpp, + which provides C++ objects corresponding to Python lists, tuples, + strings, and dictionaries, and through boost/python/callback.hpp, + which allows you to call back into python with C++ arguments. + +

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

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

SWIG

+

+ SWIG is an impressively mature tool + for exporting an existing ANSI 'C' interface into various scripting + languages. Swig relies on a parser to read your source code and produce + additional source code files which can be compiled into a Python (or + Perl or Tcl) extension module. It has been successfully used to create + many Python extension modules. Like Boost.Python, 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 Boost.Python doesn't have + this problem[sic]... IMHO overloaded functions are very important to + wrap correctly.''
-Prabhu Ramachandran +
+ +

+ By contrast, Boost.Python doesn't attempt to parse C++ - the problem is simply + too complex to do correctly. Technically, one does + write code by hand to use Boost.Python. 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 Boost.Python, 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 Boost.Python, this means that the system + imposes implementation details on your C++ code at the deepest level. It + is worth noting that some of the C++ names generated by ILU are supposed + to be reserved to the C++ implementation. It is unclear from the + documentation whether ILU supports overriding C++ virtual functions in Python. + +

GRAD

+

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

+

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

Zope ExtensionClasses

+

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

+ The major differences are: +

+

+ Next: A Simple Example Using Boost.Python + Previous: A Brief Introduction to writing Python Extension Modules + 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: Mar 6, 2001 +

+ diff --git a/doc/cross_module.html b/doc/cross_module.html new file mode 100644 index 00000000..08c39bfe --- /dev/null +++ b/doc/cross_module.html @@ -0,0 +1,336 @@ + + +Cross-extension-module dependencies + +
+ +c++boost.gif (8819 bytes) + +
+

Cross-extension-module dependencies

+ +It is good programming practice to organize large projects as modules +that interact with each other via well defined interfaces. With +Boost.Python it is possible to reflect this organization at the C++ +level at the Python level. This is, each logical C++ module can be +organized as a separate Python extension module. + +

+At first sight this might seem natural and straightforward. However, it +is a fairly complex problem to establish cross-extension-module +dependencies while maintaining the same ease of use Boost.Python +provides for classes that are wrapped in the same extension module. To +a large extent this complexity can be hidden from the author of a +Boost.Python extension module, but not entirely. + +


+

The recipe

+ +Suppose there is an extension module that exposes certain instances of +the C++ std::vector template library such that it can be used +from Python in the following manner: + +
+import std_vector
+v = std_vector.double([1, 2, 3, 4])
+v.push_back(5)
+v.size()
+
+ +Suppose the std_vector module is done well and reflects all +C++ functions that are useful at the Python level, for all C++ built-in +data types (std_vector.int, std_vector.long, etc.). + +

+Suppose further that there is statistic module with a C++ class that +has constructors or member functions that use or return a +std::vector. For example: + +

+class xy {
+  public:
+    xy(const std::vector<double>& x, const std::vector<double>& y) : m_x(x), m_y(y) {}
+    const std::vector<double>& x() const { return m_x; }
+    const std::vector<double>& y() const { return m_y; }
+    double correlation();
+  private:
+    std::vector<double> m_x;
+    std::vector<double> m_y;
+}
+
+ +What is more natural than reusing the std_vector extension +module to expose these constructors or functions to Python? + +

+Unfortunately, what seems natural needs a little work in both the +std_vector and the statistics module. + +

+In the std_vector extension module, +std::vector<double> is exposed to Python in the usual +way with the class_builder<> template. To also enable the +automatic conversion of std::vector<double> function +arguments or return values in other Boost.Python C++ modules, the +converters that convert a std::vector<double> C++ object +to a Python object and vice versa (i.e. the to_python() and +from_python() template functions) have to be exported. For +example: + +

+  #include <boost/python/cross_module.hpp>
+  //...
+  class_builder<std::vector<double> > v_double(std_vector_module, "double");
+  export_converters(v_double);
+
+ +In the extension module that wraps class xy we can now import +these converters with the import_converters<> template. +For example: + +
+  #include <boost/python/cross_module.hpp>
+  //...
+  import_converters<std::vector<double> > v_double_converters("std_vector", "double");
+
+ +That is all. All the attributes that are defined for +std_vector.double in the std_vector Boost.Python +module will be available for the returned objects of xy.x() +and xy.y(). Similarly, the constructor for xy will +accept objects that were created by the std_vectormodule. + +
+

Placement of import_converters<> template instantiations

+ +import_converts<> can be viewed as a drop-in replacement +for class_wrapper<>, and the recommendations for the +placement of class_wrapper<> template instantiations +also apply to to import_converts<>. In particular, it is +important that an instantiation of class_wrapper<> is +visible to any code which wraps a C++ function with a T, +T*, const T&, etc. parameter or return value. +Therefore you may want to group all class_wrapper<> and +import_converts<> instantiations at the top of your +module's init function, then def() the member functions later +to avoid problems with inter-class dependencies. + +
+

Non-copyable types

+ +export_converters() instantiates C++ template functions that +invoke the copy constructor of the wrapped type. For a type that is +non-copyable this will result in compile-time error messages. In such a +case, export_converters_noncopyable() can be used to export +the converters that do not involve the copy constructor of the wrapped +type. For example: + +
+class_builder<store> py_store(your_module, "store");
+export_converters_noncopyable(py_store);
+
+ +The corresponding import_converters<> statement does not +need any special attention: + +
+import_converters<store> py_store("noncopyable_export", "store");
+
+ +
+

Python module search path

+ +The std_vector and statistics modules can now be used +in the following way: + +
+import std_vector
+import statistics
+x = std_vector.double([1, 2, 3, 4])
+y = std_vector.double([2, 4, 6, 8])
+xy = statistics.xy(x, y)
+xy.correlation()
+
+ +In this example it is clear that Python has to be able to find both the +std_vector and the statistics extension module. In +other words, both extension modules need to be in the Python module +search path (sys.path). + +

+The situation is not always this obvious. Suppose the +statistics module has a random() function that +returns a vector of random numbers with a given length: + +

+import statistics
+x = statistics.random(5)
+y = statistics.random(5)
+xy = statistics.xy(x, y)
+xy.correlation()
+
+ +A naive user will not easily anticipate that the std_vector +module is used to pass the x and y vectors around. If +the std_vector module is in the Python module search path, +this form of ignorance is of no harm. On the contrary, we are glad +that we do not have to bother the user with details like this. + +

+If the std_vector module is not in the Python module search +path, a Python exception will be raised: + +

+Traceback (innermost last):
+  File "foo.py", line 2, in ?
+    x = statistics.random(5)
+ImportError: No module named std_vector
+
+ +As is the case with any system of a non-trivial complexity, it is +important that the setup is consistent and complete. + +
+

Two-way module dependencies

+ +Boost.Python supports two-way module dependencies. This is best +illustrated by a simple example. + +

+Suppose there is a module ivect that implements vectors of +integers, and a similar module dvect that implements vectors +of doubles. We want to be able do convert an integer vector to a double +vector and vice versa. For example: + +

+import ivect
+iv = ivect.ivect((1,2,3,4,5))
+dv = iv.as_dvect()
+
+ +The last expression will implicitly import the dvect module in +order to enable the conversion of the C++ representation of +dvect to a Python object. The analogous is possible for a +dvect: + +
+import dvect
+dv = dvect.dvect((1,2,3,4,5))
+iv = dv.as_ivect()
+
+ +Now the ivect module is imported implicitly. + +

+Note that the two-way dependencies are possible because the +dependencies are resolved only when needed. This is, the initialization +of the ivect module does not rely on the dvect +module, and vice versa. Only if as_dvect() or +as_ivect() is actually invoked will the corresponding module +be implicitly imported. This also means that, for example, the +dvect module does not have to be available at all if +as_dvect() is never used. + +


+

Clarification of compile-time and link-time dependencies

+ +Boost.Python's support for resolving cross-module dependencies at +runtime does not imply that compile-time dependencies are eliminated. +For example, the statistics extension module in the example above will +need to #include <vector>. This is immediately obvious +from the definition of class xy. + +

+If a library is wrapped that consists of both header files and compiled +components (e.g. libdvect.a, dvect.lib, etc.), both +the Boost.Python extension module with the +export_converters() statement and the module with the +import_converters<> statement need to be linked against +the object library. Ideally one would build a shared library (e.g. +libdvect.so, dvect.dll, etc.). However, this +introduces the issue of having to configure the search path for the +dynamic loading correctly. For small libraries it is therefore often +more convenient to ignore the fact that the object files are loaded +into memory more than once. + +


+

Summary of motivation for cross-module support

+ +The main purpose of Boost.Python's cross-module support is to allow for +a modular system layout. With this support it is straightforward to +reflect C++ code organization at the Python level. Without the +cross-module support, a multi-purpose module like std_vector +would be impractical because the entire wrapper code would somehow have +to be duplicated in all extension modules that use it, making them +harder to maintain and harder to build. + +

+Another motivation for the cross-module support is that two extension +modules that wrap the same class cannot both be imported into Python. +For example, if there are two modules A and B that +both wrap a given class X, this will work: + +

+import A
+x = A.X()
+
+ +This will also work: + +
+import B
+x = B.X()
+
+ +However, this will fail: + +
+import A
+import B
+python: /net/cci/rwgk/boost/boost/python/detail/extension_class.hpp:866:
+static void boost::python::detail::class_registry<X>::register_class(boost::python::detail::extension_class_base *):
+Assertion `static_class_object == 0' failed.
+Abort
+
+ +A good solution is to wrap class X only once. Depending on the +situation, this could be done by module A or B, or an +additional small extension module that only wraps and exports +class X. + +

+Finally, there can be important psychological or political reasons for +using the cross-module support. If a group of classes is lumped +together with many others in a huge module, the authors will have +difficulties in being identified with their work. The situation is +much more transparent if the work is represented by a module with a +recognizable name. This is not just a question of strong egos, but also +of getting credit and funding. + +


+

Why not use export_converters() universally?

+ +There is some overhead associated with the Boost.Python cross-module +support. Depending on the platform, the size of the code generated by +export_converters() is roughly 10%-20% of that generated +by class_builder<>. For a large extension module with +many wrapped classes, this could mean a significant difference. +Therefore the general recommendation is to use +export_converters() only for classes that are likely to +be used as function arguments or return values in other modules. + +
+© Copyright Ralf W. Grosse-Kunstleve 2001. 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: April 2001 + +

diff --git a/doc/data_structures.txt b/doc/data_structures.txt new file mode 100644 index 00000000..90e41b91 --- /dev/null +++ b/doc/data_structures.txt @@ -0,0 +1,192 @@ +Given a real Python class 'A', a wrapped C++ class 'B', and this definition: + + class C(A, B): + def __init__(self): + B.__init__(self) + self.x = 1 + ... + + c = C() + +this diagram describes the internal structure of an instance of 'C', including +its inheritance relationships. Note that ExtensionClass is derived from +Class, and is in fact identical for all intents and purposes. + + MetaClass + +---------+ +---------+ +types.ClassType: | | | | + | | | | + | | | | + +---------+ +---------+ + ^ ^ ^ + PyClassObject | ExtensionClass | | + A: +------------+ | B: +------------+ | | + | ob_type -+-+ | ob_type -+-----+ | + | | ()<--+- __bases__ | | + | | | __dict__ -+->{...} | + | | 'B'<-+- __name__ | | + +------------+ +------------+ | + ^ ^ | + | | | + +-----+ +-------------+ | + | | | + | | Class | + | | C: +------------+ | + | | | ob_type -+------------+ + tuple:(*, *)<--+- __bases__ | + | __dict__ -+->{__module__, } + 'C' <-+- __name__ | + +------------+ + ^ (in case of inheritance from more than one + | extension class, this vector would contain + +---------------+ a pointer to an instance holder for the data + | of each corresponding C++ class) + | ExtensionInstance + | c: +---------------------+ std::vector + +----+- __class__ | +---+-- + | m_wrapped_objects -+->| * | ... + {'x': 1}<-+- __dict__ | +-|-+-- + +---------------------+ | InstanceValueHolder + | +--------------------------------+ + +-->| (contains a C++ instance of B) | + +--------------------------------+ + + + + + + +In our inheritance test cases in extclass_demo.cpp/test_extclass.py, we have the +following C++ inheritance hierarchy: + + +-----+ +----+ + | A1 | | A2 | + +-----+ +----+ + ^ ^ ^ ^ ^ + | | | | | + +-----+ | +---------+-----+ + | | | | + | +---+----------+ + .......!...... | | + : A_callback : +-+--+ +-+--+ + :............: | B1 | | B2 | + +----+ +----+ + ^ + | + +-------+---------+ + | | + +-+-+ ......!....... + | C | : B_callback : + +---+ :............: + + +A_callback and B_callback are used as part of the wrapping mechanism but not +represented in Python. C is also not represented in Python but is delivered +there polymorphically through a smart pointer. + +This is the data structure in Python. + + ExtensionClass + A1: +------------+ + ()<--+- __bases__ | + | __dict__ -+->{...} + +------------+ + ^ + | ExtensionInstance + | a1: +---------------------+ vec InstanceValueHolder + +---------+- __class__ | +---+ +---------------------+ + | | m_wrapped_objects -+->| *-+-->| contains A_callback | + | +---------------------+ +---+ +---------------------+ + | + | ExtensionInstance + | pa1_a1: +---------------------+ vec InstancePtrHolder,A1> + +---------+- __class__ | +---+ +---+ + | | m_wrapped_objects -+->| *-+-->| *-+-+ A1 + | +---------------------+ +---+ +---+ | +---+ + | +->| | + | ExtensionInstance +---+ + | pb1_a1: +---------------------+ vec InstancePtrHolder,A1> + +---------+- __class__ | +---+ +---+ + | | m_wrapped_objects -+->| *-+-->| *-+-+ B1 + | +---------------------+ +---+ +---+ | +---+ + | +->| | + | ExtensionInstance +---+ + | pb2_a1: +---------------------+ vec InstancePtrHolder,A1> + +---------+- __class__ | +---+ +---+ + | | m_wrapped_objects -+->| *-+-->| *-+-+ B2 + | +---------------------+ +---+ +---+ | +---+ + | +->| | + | +---+ + | ExtensionClass + | A2: +------------+ + | ()<--+- __bases__ | + | | __dict__ -+->{...} + | +------------+ + | ^ + | | ExtensionInstance + | a2: | +---------------------+ vec InstanceValueHolder + | +-+- __class__ | +---+ +-------------+ + | | | m_wrapped_objects -+->| *-+-->| contains A2 | + | | +---------------------+ +---+ +-------------+ + | | + | | ExtensionInstance + | pa2_a2: | +---------------------+ vec InstancePtrHolder,A2> + | +-+- __class__ | +---+ +---+ + | | | m_wrapped_objects -+->| *-+-->| *-+-+ A2 + | | +---------------------+ +---+ +---+ | +---+ + | | +->| | + | | ExtensionInstance +---+ + | pb1_a2: | +---------------------+ vec InstancePtrHolder,A2> + | +-+- __class__ | +---+ +---+ + | | | m_wrapped_objects -+->| *-+-->| *-+-+ B1 + | | +---------------------+ +---+ +---+ | +---+ + | | +->| | + | | +---+ + | | + | +---------------+------------------------------+ + | | | + +------+-------------------------+-|----------------------------+ | + | | | | | + | Class | | ExtensionClass | | ExtensionClass + | DA1: +------------+ | | B1: +------------+ | | B2: +------------+ +(*,)<---+- __bases__ | (*,*)<---+- __bases__ | (*,*)<---+- __bases__ | + | __dict__ -+->{...} | __dict__ -+->{...} | __dict__ -+->{...} + +------------+ +------------+ +------------+ + ^ ^ ^ + | ExtensionInstance | | + | da1: +---------------------+ | vec InstanceValueHolder + +-------+- __class__ | | +---+ +---------------------+ | + | m_wrapped_objects -+--|-->| *-+-->| contains A_callback | | + +---------------------+ | +---+ +---------------------+ | + +--------------------------------------+ | + | ExtensionInstance | + b1: | +---------------------+ vec InstanceValueHolder | + +-+- __class__ | +---+ +---------------------+ | + | | m_wrapped_objects -+->| *-+-->| contains B_callback | | + | +---------------------+ +---+ +---------------------+ | + | | + | ExtensionInstance | +pb1_b1: | +---------------------+ vec InstancePtrHolder,B1> | + +-+- __class__ | +---+ +---+ | + | | m_wrapped_objects -+->| *-+-->| *-+-+ B1 | + | +---------------------+ +---+ +---+ | +---+ | + | +->| | | + | ExtensionInstance +---+ | + pc_b1: | +---------------------+ vec InstancePtrHolder,B1> | + +-+- __class__ | +---+ +---+ | + | | m_wrapped_objects -+->| *-+-->| *-+-+ C | + | +---------------------+ +---+ +---+ | +---+ | + | +->| | | + | +---+ | + | | + | Class +---------------------------------------+ + | DB1: +------------+ | ExtensionInstance + (*,)<---+- __bases__ | a2: | +---------------------+ vec InstanceValueHolder + | __dict__ -+->{...} +-+- __class__ | +---+ +-------------+ + +------------+ | m_wrapped_objects -+->| *-+-->| contains A2 | + ^ +---------------------+ +---+ +-------------+ + | ExtensionInstance + db1: | +---------------------+ vec InstanceValueHolder + +-+- __class__ | +---+ +----------------------+ + | m_wrapped_objects -+-->| *-+-->| contains B1_callback | + +---------------------+ +---+ +----------------------+ diff --git a/doc/enums.html b/doc/enums.html new file mode 100644 index 00000000..c58ca34d --- /dev/null +++ b/doc/enums.html @@ -0,0 +1,120 @@ + + + Wrapping enums + +
+

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

+ +

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

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

    +
  1. Explicit instantiation: + +
    +  template class boost::python::enum_as_int_converters<my_enum>;
    +
    + +Some buggy C++ implementations require a class to be instantiated in the same +namespace in which it is defined. In that case, the simple incantation above becomes: + +
    +
    +   ...
    +} // close my_namespace
    +
    +// drop into namespace python and explicitly instantiate
    +namespace boost { namespace python {
    +  template class enum_as_int_converters<my_enum_type>;
    +}} // namespace boost::python
    +
    +namespace my_namespace { // re-open my_namespace
    +   ...
    +
    +
    + + +
  2. If you have such an implementation, you may find this technique more convenient +
    +// instantiate as base class in any namespace
    +struct EnumTypeConverters
    +    : boost::python::enum_as_int_converters<EnumType>
    +{
    +};
    +
    +
+ +

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

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

This technique defines the conversions of +MyEnumType in terms of the conversions for the built-in + long type. + +You may also want to add a bunch of lines like this to your module +initialization. These bind the corresponding enum values to the appropriate +names so they can be used from Python: + +

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

+ Next: Pointers and Smart Pointers + Previous: Building an Extension Module + 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: Mar 6, 2001 +

+ diff --git a/doc/example1.html b/doc/example1.html new file mode 100644 index 00000000..ee01e72c --- /dev/null +++ b/doc/example1.html @@ -0,0 +1,75 @@ + + + A Simple Example + +
+

+ + +

+

+ A Simple Example +

+

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

+
+#include <string>
+
+namespace { // Avoid cluttering the global namespace.
+
+  // A couple of simple C++ functions that we want to expose to Python.
+  std::string greet() { return "hello, world"; }
+  int square(int number) { return number * number; }
+}
+
+
+
+

+ Here is the C++ code for a python module called getting_started1 + which exposes the API. +

+
+#include <boost/python/class_builder.hpp>
+namespace python = boost::python;
+
+BOOST_PYTHON_MODULE_INIT(getting_started1)
+{
+    // Create an object representing this extension module.
+    python::module_builder this_module("getting_started1");
+
+    // Add regular functions to the module.
+    this_module.def(greet, "greet");
+    this_module.def(square, "square");
+}
+
+
+

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

+
+>>> import getting_started1
+>>> print getting_started1.greet()
+hello, world
+>>> number = 11
+>>> print number, '*', number, '=', getting_started1.square(number)
+11 * 11 = 121
+
+

+ Next: Exporting Classes + Previous: 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. +

+ Updated: Mar 6, 2000 +

+ diff --git a/doc/exporting_classes.html b/doc/exporting_classes.html new file mode 100644 index 00000000..cbeb8a9e --- /dev/null +++ b/doc/exporting_classes.html @@ -0,0 +1,143 @@ + + + Exporting Classes + +
+

+ + +

+

+ Exporting Classes +

+

+ Now let's expose a C++ class to Python: + +

+#include <iostream>
+#include <string>
+
+namespace { // Avoid cluttering the global namespace.
+
+  // A friendly class.
+  class hello
+  {
+    public:
+      hello(const std::string& country) { this->country = country; }
+      std::string greet() const { return "Hello from " + country; }
+    private:
+      std::string country;
+  };
+
+  // A function taking a hello object as an argument.
+  std::string invite(const hello& w) {
+    return w.greet() + "! Please come soon!";
+  }
+}
+
+

+ To expose the class, we use a class_builder in addition to the + module_builder from the previous example. Class member functions + are exposed by using the def() member function on the + class_builder: +

+#include <boost/python/class_builder.hpp>
+namespace python = boost::python;
+
+BOOST_PYTHON_MODULE_INIT(getting_started2)
+{
+    // Create an object representing this extension module.
+    python::module_builder this_module("getting_started2");
+
+    // Create the Python type object for our extension class.
+    python::class_builder<hello> hello_class(this_module, "hello");
+
+    // Add the __init__ function.
+    hello_class.def(python::constructor<std::string>());
+    // Add a regular member function.
+    hello_class.def(&hello::greet, "greet");
+
+    // Add invite() as a regular function to the module.
+    this_module.def(invite, "invite");
+
+    // Even better, invite() can also be made a member of hello_class!!!
+    hello_class.def(invite, "invite");
+}
+
+

+Now we can use the class normally from Python: + +

+>>> from getting_started2 import *
+>>> hi = hello('California')
+>>> hi.greet()
+'Hello from California'
+>>> invite(hi)
+'Hello from California! Please come soon!'
+>>> hi.invite()
+'Hello from California! Please come soon!'
+
+ +Notes:
    +
  • We expose the class' constructor by calling def() on the + class_builder with an argument whose type is + constructor<params>, where params + matches the list of constructor argument types: + + +
  • Regular member functions are defined by calling def() with a + member function pointer and its Python name: + +
  • Any function added to a class whose initial argument matches the class (or +any base) will act like a member function in Python. + +
  • To define a nested class, just pass the enclosing +class_builder (instead of a module_builder) as the +first argument to the nested class_builder's constructor. + + +
+

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

+>>> class wordy(hello):
+...     def greet(self):
+...         return hello.greet(self) + ', where the weather is fine'
+...
+>>> hi2 = wordy('Florida')
+>>> hi2.greet()
+'Hello from Florida, where the weather is fine'
+>>> invite(hi2)
+'Hello from Florida! Please come soon!'
+
+

+ Pretty cool! You can't do that with an ordinary Python extension type! + + Of course, you may now have a slightly empty feeling in the pit of + your little pythonic stomach. Perhaps you wanted to see the following + wordy invitation: + +

+'Hello from Florida, where the weather is fine! Please come soon!'
+
+ + After all, invite calls hello::greet(), and you + reimplemented that in your Python subclass, wordy. If so, read on... + +

+ Next: Overridable virtual functions + Previous: A Simple Example 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: Mar 6, 2001 +

+ diff --git a/doc/extending.html b/doc/extending.html new file mode 100644 index 00000000..8839ab43 --- /dev/null +++ b/doc/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. The Boost Python Library 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. Boost.Python 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. Boost.Python 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/doc/inheritance.html b/doc/inheritance.html new file mode 100644 index 00000000..56e96872 --- /dev/null +++ b/doc/inheritance.html @@ -0,0 +1,166 @@ + + + Inheritance + +
+

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

+ +

Inheritance in Python

+ +

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

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

Reflecting C++ Inheritance Relationships

+

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

+
+#include <memory> // for std::auto_ptr<>
+
+struct Base {
+    virtual ~Base() {}
+    virtual const char* name() const { return "Base"; }
+};
+
+struct Derived : Base {
+    Derived() : x(-1) {}
+    virtual const char* name() const { return "Derived"; }
+    int x;
+};
+
+std::auto_ptr<Base> derived_as_base() {
+    return std::auto_ptr<Base>(new Derived);
+}
+
+const char* get_name(const Base& b) {
+    return b.name();
+}
+
+int get_derived_x(const Derived& d) {
+    return d.x;
+}
+    
+#include <boost/python/class_builder.hpp> + +// namespace alias for code brevity +namespace python = boost::python; + +BOOST_PYTHON_MODULE_INIT(my_module) +{ +    python::module_builder my_module("my_module"); + +    python::class_builder<Base> base_class(my_module, "Base"); +    base_class.def(python::constructor<>()); + +    python::class_builder<Derived> derived_class(my_module, "Derived"); +    derived_class.def(python::constructor<>()); + // Establish the inheritance relationship between Base and Derived + derived_class.declare_base(base_class); + + my_module.def(derived_as_base, "derived_as_base"); + my_module.def(get_name, "get_name"); + my_module.def(get_derived_x, "get_derived_x"); +} +
+
+ +

+ Then, in Python: +

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

Inheritance Without Virtual Functions

+ +

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

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

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

+ Next: Special Method and Operator Support + 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: Nov 26, 2000 +

+ diff --git a/doc/overloading.html b/doc/overloading.html new file mode 100644 index 00000000..242e023f --- /dev/null +++ b/doc/overloading.html @@ -0,0 +1,155 @@ + + + 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;
+};
+  ...
+
+BOOST_PYTHON_MODULE_INIT(overload_demo)
+{
+    try
+    {
+        boost::python::module_builder overload_demo("overload_demo");
+        // Overloaded functions at module scope
+        overload_demo.def(f1, "f");
+        overload_demo.def(f2, "f");
+
+        boost::python::class_builder<X> x_class(overload_demo, "X");
+        // Overloaded constructors
+        x_class.def(boost::python::constructor<>());
+        x_class.def(boost::python::constructor<int>());
+
+        // Overloaded member functions
+        x_class.def((int (X::*)() const)&X::value, "value");
+        x_class.def((void (X::*)(int))&X::value, "value");
+  ...
+
+
+ +

+ Now in Python: +

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

Discussion

+

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

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

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

An Alternative to Casting

+

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

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

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

Overload Resolution

+

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

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

    + +

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

+ Next: Inheritance + Previous: Overridable Virtual Functions + Up: Top +

+ © Copyright David Abrahams 2001. 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: Mar 6, 2001 +

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

Overridable Virtual Functions

+ +

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

Example

+ +

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

+class hello
+{
+ public:
+    hello(const std::string& country) { this->country = country; }
+    virtual std::string greet() const { return "Hello from " + country; }
+    virtual ~hello(); // Good practice 
+    ...
+};
+
+ +

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

    + +
  1. A PyObject* data member (usually + called self) that holds a pointer to the Python object corresponding + to our C++ hello instance. + +
  2. For each exposed constructor of the + base class T, a constructor which takes the same parameters preceded by an initial + PyObject* argument. The initial argument should be stored in the self data + member described above. + +
  3. If the class being wrapped is ever returned by + value from a wrapped function, be sure you do the same for the + T's copy constructor: you'll need a constructor taking arguments + (PyObject*, const T&). + +
  4. An implementation of each virtual function you may + wish to override in Python which uses + callback<return-type>::call_method(self, "name", args...) to call + the Python override. + +
  5. For each non-pure virtual function meant to be + overridable from Python, a static member function (or a free function) taking + a reference or pointer to the T as the first parameter and which + forwards any additional parameters neccessary to the default + implementation of the virtual function. See also this + note if the base class virtual function is private. + +
+ +
+struct hello_callback : hello
+{
+    // hello constructor storing initial self_ parameter
+    hello_callback(PyObject* self_, const std::string& x) // 2
+        : hello(x), self(self_) {}
+
+    // In case hello is returned by-value from a wrapped function
+    hello_callback(PyObject* self_, const hello& x) // 3
+        : hello(x), self(self_) {}
+
+    // Override greet to call back into Python
+    std::string greet() const // 4
+        { return boost::python::callback<std::string>::call_method(self, "greet"); }
+
+    // Supplies the default implementation of greet
+    static std::string default_greet(const hello& self_) const // 5
+        { return self_.hello::greet(); }
+ private:
+    PyObject* self; // 1
+};
+
+ +

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

+// Create the Python type object for our extension class
+boost::python::class_builder<hello,hello_callback> hello_class(hello, "hello");
+// Add a virtual member function
+hello_class.def(&hello::greet, "greet", &hello_callback::default_greet);
+
+ +

+ Now our Python subclass of hello behaves as expected: + +

+>>> class wordy(hello):
+...     def greet(self):
+...         return hello.greet(self) + ', where the weather is fine'
+...
+>>> hi2 = wordy('Florida')
+>>> hi2.greet()
+'Hello from Florida, where the weather is fine'
+>>> invite(hi2)
+'Hello from Florida, where the weather is fine! Please come soon!'
+
+

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

Pure Virtual Functions

+ +

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

+
+struct baz {
+    virtual int pure(int) = 0;
+    int calls_pure(int x) { return pure(x) + 1000; }
+};
+
+struct baz_callback {
+    int pure(int x) { boost::python::callback<int>::call_method(m_self, "pure", x); }
+};
+
+BOOST_PYTHON_MODULE_INIT(foobar)
+{
+     boost::python::module_builder foobar("foobar");                          
+     boost::python::class_builder<baz,baz_callback> baz_class("baz");   
+     baz_class.def(&baz::calls_pure, "calls_pure"); 
+}
+
+
+

+ Now in Python: +

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

Private Non-Pure Virtual Functions

+ +

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


+

+ Next: Function Overloading + Previous: Exporting Classes + Up: Top +

+ © Copyright David Abrahams 2001. 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: Mar 21, 2001 + diff --git a/doc/pickle.html b/doc/pickle.html new file mode 100644 index 00000000..994a78ab --- /dev/null +++ b/doc/pickle.html @@ -0,0 +1,272 @@ + + +Boost.Python Pickle Support + +

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

Boost.Python Pickle Support

+ +Pickle is a Python module for object serialization, also known +as persistence, marshalling, or flattening. + +

+It is often necessary to save and restore the contents of an object to +a file. One approach to this problem is to write a pair of functions +that read and write data from a file in a special format. A powerful +alternative approach is to use Python's pickle module. Exploiting +Python's ability for introspection, the pickle module recursively +converts nearly arbitrary Python objects into a stream of bytes that +can be written to a file. + +

+The Boost Python Library supports the pickle module by emulating the +interface implemented by Jim Fulton's ExtensionClass module that is +included in the +ZOPE +distribution. +This interface is similar to that for regular Python classes as +described in detail in the +Python Library Reference for pickle. + +


+

The Boost.Python Pickle Interface

+ +At the user level, the Boost.Python pickle interface involves three special +methods: + +
+
+__getinitargs__ +
+ When an instance of a Boost.Python extension class is pickled, the + pickler tests if the instance has a __getinitargs__ method. + This method must return a Python tuple (it is most convenient to use + a boost::python::tuple). When the instance is restored by the + unpickler, the contents of this tuple are used as the arguments for + the class constructor. + +

+ If __getinitargs__ is not defined, the class constructor + will be called without arguments. + +

+

+__getstate__ + +
+ When an instance of a Boost.Python extension class is pickled, the + pickler tests if the instance has a __getstate__ method. + This method should return a Python object representing the state of + the instance. + +

+ If __getstate__ is not defined, the instance's + __dict__ is pickled (if it is not empty). + +

+

+__setstate__ + +
+ When an instance of a Boost.Python extension class is restored by the + unpickler, it is first constructed using the result of + __getinitargs__ as arguments (see above). Subsequently the + unpickler tests if the new instance has a __setstate__ + method. If so, this method is called with the result of + __getstate__ (a Python object) as the argument. + +

+ If __setstate__ is not defined, the result of + __getstate__ must be a Python dictionary. The items of this + dictionary are added to the instance's __dict__. + +

+ +If both __getstate__ and __setstate__ are defined, +the Python object returned by __getstate__ need not be a +dictionary. The __getstate__ and __setstate__ methods +can do what they want. + +
+

Pitfalls and Safety Guards

+ +In Boost.Python extension modules with many extension classes, +providing complete pickle support for all classes would be a +significant overhead. In general complete pickle support should only be +implemented for extension classes that will eventually be pickled. +However, the author of a Boost.Python extension module might not +anticipate correctly which classes need support for pickle. +Unfortunately, the pickle protocol described above has two important +pitfalls that the end user of a Boost.Python extension module might not +be aware of: + +
+
+Pitfall 1: +Both __getinitargs__ and __getstate__ are not defined. + +
+ In this situation the unpickler calls the class constructor without + arguments and then adds the __dict__ that was pickled by + default to that of the new instance. + +

+ However, most C++ classes wrapped with Boost.Python will have member + data that are not restored correctly by this procedure. To alert the + user to this problem, a safety guard is provided. If both + __getinitargs__ and __getstate__ are not defined, + Boost.Python tests if the class has an attribute + __dict_defines_state__. An exception is raised if this + attribute is not defined: + +

+    RuntimeError: Incomplete pickle support (__dict_defines_state__ not set)
+
+ + In the rare cases where this is not the desired behavior, the safety + guard can deliberately be disabled. The corresponding C++ code for + this is, e.g.: + +
+    class_builder<your_class> py_your_class(your_module, "your_class");
+    py_your_class.dict_defines_state();
+
+ + It is also possible to override the safety guard at the Python level. + E.g.: + +
+    import your_bpl_module
+    class your_class(your_bpl_module.your_class):
+      __dict_defines_state__ = 1
+
+ +

+

+Pitfall 2: +__getstate__ is defined and the instance's __dict__ is not empty. + +
+ The author of a Boost.Python extension class might provide a + __getstate__ method without considering the possibilities + that: + +

+

    +
  • + his class is used in Python as a base class. Most likely the + __dict__ of instances of the derived class needs to be + pickled in order to restore the instances correctly. + +

    +

  • + the user adds items to the instance's __dict__ directly. + Again, the __dict__ of the instance then needs to be + pickled. + +
+

+ + To alert the user to this highly unobvious problem, a safety guard is + provided. If __getstate__ is defined and the instance's + __dict__ is not empty, Boost.Python tests if the class has + an attribute __getstate_manages_dict__. An exception is + raised if this attribute is not defined: + +

+    RuntimeError: Incomplete pickle support (__getstate_manages_dict__ not set)
+
+ + To resolve this problem, it should first be established that the + __getstate__ and __setstate__ methods manage the + instances's __dict__ correctly. Note that this can be done + both at the C++ and the Python level. Finally, the safety guard + should intentionally be overridden. E.g. in C++: + +
+    class_builder<your_class> py_your_class(your_module, "your_class");
+    py_your_class.getstate_manages_dict();
+
+ + In Python: + +
+    import your_bpl_module
+    class your_class(your_bpl_module.your_class):
+      __getstate_manages_dict__ = 1
+      def __getstate__(self):
+        # your code here
+      def __setstate__(self, state):
+        # your code here
+
+
+ +
+

Practical Advice

+ +
    +
  • + Avoid using __getstate__ if the instance can also be + reconstructed by way of __getinitargs__. This automatically + avoids Pitfall 2. + +

    +

  • + If __getstate__ is required, include the instance's + __dict__ in the Python object that is returned. + +
+ +
+

Examples

+ +There are three files in boost/libs/python/example that +show how so provide pickle support. + +

pickle1.cpp

+ + The C++ class in this example can be fully restored by passing the + appropriate argument to the constructor. Therefore it is sufficient + to define the pickle interface method __getinitargs__. + +

pickle2.cpp

+ + The C++ class in this example contains member data that cannot be + restored by any of the constructors. Therefore it is necessary to + provide the __getstate__/__setstate__ pair of + pickle interface methods. + +

+ For simplicity, the __dict__ is not included in the result + of __getstate__. This is not generally recommended, but a + valid approach if it is anticipated that the object's + __dict__ will always be empty. Note that the safety guards + will catch the cases where this assumption is violated. + +

pickle3.cpp

+ + This example is similar to pickle2.cpp. However, the + object's __dict__ is included in the result of + __getstate__. This requires more code but is unavoidable + if the object's __dict__ is not always empty. + +
+© Copyright Ralf W. Grosse-Kunstleve 2001. 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: March 21, 2001 +

diff --git a/doc/pointers.html b/doc/pointers.html new file mode 100644 index 00000000..11cfd8d9 --- /dev/null +++ b/doc/pointers.html @@ -0,0 +1,148 @@ + + + Pointers + +
+

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

+ +

The Problem With Pointers

+ +

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

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

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

Can you avoid the problem?

+ +

My first piece of advice to anyone with a case not covered above is +``find a way to avoid the problem.'' For example, if you have just one +or two functions that return a pointer to an individual const +T, and T is a wrapped class, you may be able to write a ``thin +converting wrapper'' over those two functions as follows: + +

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

+Foo must have a public copy constructor for this technique to work, since Boost.Python +converts const T& values to_python by copying the T +value into a new extension instance. + +

Dealing with the problem

+ +

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

Returning a pointer to a wrapped type

+ +

Returning a const pointer

+ +

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

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

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

+ +

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

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

[In/]Out Parameters and Immutable Types

+ +

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

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

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

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

Now, in Python: +

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

+ Previous: Enums + Up: Top +

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

+ Updated: Nov 26, 2000 +

+ diff --git a/doc/richcmp.html b/doc/richcmp.html new file mode 100644 index 00000000..d9ab7044 --- /dev/null +++ b/doc/richcmp.html @@ -0,0 +1,106 @@ + + +Rich Comparisons + +
+ +c++boost.gif (8819 bytes) + +
+

Rich Comparisons

+ +
+In Python versions up to and including Python 2.0, support for +implementing comparisons on user-defined classes and extension types +was quite simple. Classes could implement a __cmp__ method +that was given two instances of a class as arguments, and could only +return 0 if they were equal or +1 or -1 if +they were not. The method could not raise an exception or return +anything other than an integer value. +In Python 2.1, Rich Comparisons were added (see +PEP 207). +Python classes can now individually overload each of the <, <=, +>, >=, ==, and != operations. + +

+For more detailed information, search for "rich comparison" +here. + +

+Boost.Python supports both automatic overloading and manual overloading +of the Rich Comparison operators. The compile-time support is +independent of the Python version that is used when compiling +Boost.Python extension modules. That is, op_lt for example can +always be used, and the C++ operator< will always be bound +to the Python method __lt__. However, the run-time +behavior will depend on the Python version. + +

+With Python versions before 2.1, the Rich Comparison operators will not +be called by Python when any of the six comparison operators +(<, <=, ==, !=, +>, >=) is used in an expression. The only way +to access the corresponding methods is to call them explicitly, e.g. +a.__lt__(b). Only with Python versions 2.1 or higher will +expressions like a < b work as expected. + +

+To support Rich Comparisions, the Python C API was modified between +Python versions 2.0 and 2.1. A new slot was introduced in the +PyTypeObject structure: tp_richcompare. For backwards +compatibility, a flag (Py_TPFLAGS_HAVE_RICHCOMPARE) has to be +set to signal to the Python interpreter that Rich Comparisions are +supported by a particular type. +There is only one flag for all the six comparison operators. +When any of the six operators is wrapped automatically or +manually, Boost.Python will set this flag. Attempts to use comparison +operators at the Python level that are not defined at the C++ level +will then lead to an AttributeError when the Python 2.1 +(or higher) interpreter tries, e.g., a.__lt__(b). That +is, in general all six operators should be supplied. Automatically +wrapped operators and manually wrapped operators can be mixed. For +example:

+    boost::python::class_builder<code> py_code(this_module, "code");
+
+    py_code.def(boost::python::constructor<>());
+    py_code.def(boost::python::constructor<int>());
+    py_code.def(boost::python::operators<(  boost::python::op_eq
+                                          | boost::python::op_ne)>());
+    py_code.def(NotImplemented, "__lt__");
+    py_code.def(NotImplemented, "__le__");
+    py_code.def(NotImplemented, "__gt__");
+    py_code.def(NotImplemented, "__ge__");
+
+ +NotImplemented is a simple free function that (currently) has +to be provided by the user. For example:
+  boost::python::ref
+  NotImplemented(const code&, const code&) {
+    return
+    boost::python::ref(Py_NotImplemented, boost::python::ref::increment_count);
+  }
+
+ +See also: + + +
+© Copyright Nicholas K. Sauter & Ralf W. Grosse-Kunstleve 2001. +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: July 2001 + +

diff --git a/doc/special.html b/doc/special.html new file mode 100644 index 00000000..d53ec712 --- /dev/null +++ b/doc/special.html @@ -0,0 +1,973 @@ + + + Special Method and Operator Support + +
+

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

+

+ Overview +

+

+ Boost.Python supports all of the standard + special method names supported by real Python class instances + except __complex__ (more on the reasons below). In addition, it can quickly and easily expose + suitable C++ functions and operators as Python operators. The following + categories of special method names are supported: +

+ +

Basic Customization

+ + +

+ Python provides a number of special operators for basic customization of a + class. Only a brief description is provided below; more complete + documentation can be found here. + +

+
+ __init__(self) +
+ Initialize the class instance. For extension classes not subclassed in + Python, __init__ is defined by + +
    my_class.def(boost::python::constructor<...>())
+ + (see section "A Simple Example Using Boost.Python").

+

+ __del__(self) +
+ Called when the extension instance is about to be destroyed. For extension classes + not subclassed in Python, __del__ is always defined automatically by + means of the class' destructor. +
+ __repr__(self) +
+ Create a string representation from which the object can be + reconstructed. +
+ __str__(self) +
+ Create a string representation which is suitable for printing. +
+ __lt__(self, other) +
+ __le__(self, other) +
+ __eq__(self, other) +
+ __ne__(self, other) +
+ __gt__(self, other) +
+ __ge__(self, other) +
+ Rich Comparison methods. + New in Python 2.1. + See Rich Comparisons. +
+ __cmp__(self, other) +
+ Three-way compare function. + See Rich Comparisons. +
+ __hash__(self) +
+ Called for the key object for dictionary operations, and by the + built-in function hash(). Should return a 32-bit integer usable as a + hash value for dictionary operations (only allowed if __cmp__ is also + defined) +
+ __nonzero__(self) +
+ called if the object is used as a truth value (e.g. in an if + statement) +
+ __call__ (self[, args...]) +
+Called when the instance is ``called'' as a function; if this method +is defined, x(arg1, arg2, ...) is a shorthand for +x.__call__(arg1, arg2, ...). +
+ + If we have a suitable C++ function that supports any of these features, + we can export it like any other function, using its Python special name. + For example, suppose that class Foo provides a string + conversion function: +
+std::string to_string(Foo const& f)
+{
+    std::ostringstream s;
+    s << f;
+    return s.str();
+}
+
+ This function would be wrapped like this: +
+boost::python::class_builder<Foo> foo_class(my_module, "Foo");
+foo_class.def(&to_string, "__str__");
+
+ Note that Boost.Python also supports automatic wrapping of + __str__ and __cmp__. This is explained in the next section and the Table of + Automatically Wrapped Methods. + +

Numeric Operators

+ +

+ Numeric operators can be exposed manually, by defing C++ + [member] functions that support the standard Python numeric + protocols. This is the same basic technique used to expose + to_string() as __str__() above, and is covered in detail below. Boost.Python also supports + automatic wrapping of numeric operators whenever they have already + been defined in C++. + +

Exposing C++ Operators Automatically

+ +

+Supose we wanted to expose a C++ class + BigNum which supports addition. That is, in C++ we can write: +

+BigNum a, b, c;
+...
+c = a + b;
+
+

+ To enable the same functionality in Python, we first wrap the + BigNum class as usual: +

+boost::python::class_builder<BigNum> bignum_class(my_module, "BigNum");
+bignum_class.def(boost::python::constructor<>());
+...
+
+ Then we export the addition operator like this: + +
+bignum_class.def(boost::python::operators<boost::python::op_add>());
+
+ + Since BigNum also supports subtraction, multiplication, and division, we + want to export those also. This can be done in a single command by + ``or''ing the operator identifiers together (a complete list of these + identifiers and the corresponding operators can be found in the Table of Automatically Wrapped Methods): +
+bignum_class.def(boost::python::operators<(boost::python::op_sub | boost::python::op_mul | boost::python::op_div)>());
+
+ [Note that the or-expression must be enclosed in parentheses.] + +

This form of operator definition can be used to wrap unary and + homogeneous binary operators (a homogeneous operator has left and + right operands of the same type). Now suppose that our C++ library also + supports addition of BigNums and plain integers: + +

+BigNum a, b;
+int i;
+...
+a = b + i;
+a = i + b;
+
+ To wrap these heterogeneous operators, we need to specify a different type for + one of the operands. This is done using the right_operand + and left_operand templates: +
+bignum_class.def(boost::python::operators<boost::python::op_add>(), boost::python::right_operand<int>());
+bignum_class.def(boost::python::operators<boost::python::op_add>(), boost::python::left_operand<int>());
+
+ Boost.Python uses overloading to register several variants of the same + operation (more on this in the context of + coercion). Again, several operators can be exported at once: +
+bignum_class.def(boost::python::operators<(boost::python::op_sub | boost::python::op_mul | boost::python::op_div)>(),
+                 boost::python::right_operand<int>());
+bignum_class.def(boost::python::operators<(boost::python::op_sub | boost::python::op_mul | boost::python::op_div)>(), 
+                 boost::python::left_operand<int>());
+
+ The type of the operand not mentioned is taken from the class being wrapped. In + our example, the class object is bignum_class, and thus the + other operand's type is ``BigNum const&''. You can override + this default by explicitly specifying a type in the + operators template: +
+bignum_class.def(boost::python::operators<boost::python::op_add, BigNum>(), boost::python::right_operand<int>());
+
+

+ Note that automatic wrapping uses the expression + ``left + right'' and can be used uniformly + regardless of whether the C++ operators are supplied as free functions + +

+BigNum operator+(BigNum, BigNum)
+
+ + or as member functions + +
+BigNum::operator+(BigNum).
+
+ +

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

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

Wrapping Numeric Operators Manually

+

+ In some cases, automatic wrapping of operators may be impossible or + undesirable. Suppose, for example, that the modulo operation for BigNums + is defined by a set of functions called mod(): + +

+BigNum mod(BigNum const& left, BigNum const& right);
+BigNum mod(BigNum const& left, int right);
+BigNum mod(int left, BigNum const& right);
+
+ +

+ For automatic wrapping of the modulo function, operator%() would be needed. + Therefore, the mod()-functions must be wrapped manually. That is, we have + to export them explicitly with the Python special name "__mod__": + +

+bignum_class.def((BigNum (*)(BigNum const&, BigNum const&))&mod, "__mod__");
+bignum_class.def((BigNum (*)(BigNum const&, int))&mod, "__mod__");
+
+ +

+ The third form of mod() (with int as left operand) cannot + be wrapped directly. We must first create a function rmod() with the + operands reversed: + +

+BigNum rmod(BigNum const& right, int left)
+{
+    return mod(left, right);
+}
+
+ + This function must be wrapped under the name "__rmod__" (standing for "reverse mod"): + +
+bignum_class.def(&rmod,  "__rmod__");
+
+ + Many of the possible operator names can be found in the Table of Automatically Wrapped Methods. Special treatment is + necessary to export the ternary pow operator. + +

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

Inplace Operators

+

+ Boost.Python can also be used to expose inplace numeric operations + (i.e., += and so forth). These operators must be wrapped + manually, as described in the previous section. For example, suppose + the class BigNum has an operator+=: + +

+BigNum& operator+= (BigNum const& right);
+
+ + This can be exposed by first writing a wrapper function: + +
+BigNum& iadd (BigNum& self, const BigNum& right)
+{
+  return self += right;
+}
+
+ + and then exposing the wrapper with + +
+bignum_class.def(&iadd, "__iadd__");
+
+ + + + +

Coercion

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

+ Boost.Python solves this problem the same way that C++ does: with overloading. This technique drastically + simplifies the code neccessary to support operators: you just register + operators for all desired type combinations, and Boost.Python automatically + ensures that the correct function is called in each case; there is no + need for user-defined coercion functions. To enable operator + overloading, Boost.Python provides a standard coercion which is implicitly + registered whenever automatic operator wrapping is used. +

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

+// this is not necessary if automatic operator wrapping is used
+bignum_class.def_standard_coerce();
+
+ + If you encounter a situation where you absolutely need a customized + coercion, you can still define the "__coerce__" operator manually. The signature + of a coercion function should look like one of the following (the first is + the safest): + +
+boost::python::tuple custom_coerce(boost::python::reference left, boost::python::reference right);
+boost::python::tuple custom_coerce(PyObject* left, PyObject* right);
+PyObject* custom_coerce(PyObject* left, PyObject* right);
+
+ + The resulting tuple must contain two elements which + represent the values of left and right + converted to the same type. Such a function is wrapped as usual: + +
+// this must be called before any use of automatic operator  
+// wrapping or a call to some_class.def_standard_coerce()
+some_class.def(&custom_coerce, "__coerce__");
+
+ + Note that the standard coercion (defined by use of automatic + operator wrapping on a class_builder or a call to + class_builder::def_standard_coerce()) will never be applied if + a custom coercion function has been registered. Therefore, in + your coercion function you should call + +
+boost::python::standard_coerce(left, right);
+
+ + for all cases that you don't want to handle yourself. + +

The Ternary pow() Operator

+ +

+ In addition to the usual binary pow(x, y) operator (meaning + xy), Python also provides a ternary variant that implements + xy mod z, presumably using a more efficient algorithm than + concatenation of power and modulo operators. Automatic operator wrapping + can only be used with the binary variant. Ternary pow() must + always be wrapped manually. For a homgeneous ternary pow(), + this is done as usual: + +

+BigNum power(BigNum const& first, BigNum const& second, BigNum const& modulus);
+typedef BigNum (ternary_function1)(const BigNum&, const BigNum&, const BigNum&);
+...
+bignum_class.def((ternary_function1)&power,  "__pow__");
+
+ + If you want to support this function with non-uniform argument + types, wrapping is a little more involved. Suppose you have to wrap: + +
+BigNum power(BigNum const& first, int second, int modulus);
+BigNum power(int first, BigNum const& second, int modulus);
+BigNum power(int first, int second, BigNum const& modulus);
+
+ + The first variant can be wrapped as usual: + +
+typedef BigNum (ternary_function2)(const BigNum&, int, int);
+bignum_class.def((ternary_function2)&power,  "__pow__");
+
+ + In the second variant, however, BigNum appears only as second + argument, and in the last one it's the third argument. These functions + must be presented to Boost.Python such that that the BigNum + argument appears in first position: + +
+BigNum rpower(BigNum const& second, int first, int modulus)
+{
+    return power(first, second, modulus);
+}
+
+BigNum rrpower(BigNum const& modulus, int first, int second)
+{
+    return power(first, second, modulus);
+}
+
+ +

These functions must be wrapped under the names "__rpow__" and "__rrpow__" + respectively: + +

+bignum_class.def((ternary_function2)&rpower,  "__rpow__");
+bignum_class.def((ternary_function2)&rrpower,  "__rrpow__");
+
+ +Note that "__rrpow__" is an extension not present in plain Python. + +

Table of Automatically Wrapped Methods

+

+ Boost.Python can automatically wrap the following + special methods: + +

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

Sequence and Mapping Operators

+ +

+ Sequence and mapping operators let wrapped objects behave in accordance + to Python's iteration and access protocols. These protocols differ + considerably from the ones found in C++. For example, Python's typical + iteration idiom looks like + +

+for i in S:
+
+ + while in C++ one writes + +
+for (iterator i = S.begin(), end = S.end(); i != end; ++i)
+
+ +

One could try to wrap C++ iterators in order to carry the C++ idiom into + Python. However, this does not work very well because + +

    +
  1. It leads to + non-uniform Python code (wrapped sequences support a usage different from + Python built-in sequences) and + +
  2. Iterators (e.g. std::vector::iterator) are often implemented as plain C++ + pointers which are problematic for any automatic + wrapping system. +
+ +

+ It is a better idea to support the standard Python + sequence and mapping protocols for your wrapped containers. These + operators have to be wrapped manually because there are no corresponding + C++ operators that could be used for automatic wrapping. The Python + documentation lists the relevant + container operators. In particular, expose __getitem__, __setitem__ + and remember to raise the appropriate Python exceptions + (PyExc_IndexError for sequences, + PyExc_KeyError for mappings) when the requested item is not + present. + +

+ In the following example, we expose std::map<std::size_t,std::string>: +

+
+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, boost::python::converters::to_python(key));
+        boost::python::throw_error_already_set();
+    }
+}
+
+// Define some simple wrapper functions which match the Python  protocol
+// for __getitem__, __setitem__, and __delitem__.  Just as in Python, a
+// free function with a ``self'' first parameter makes a fine class method.
+
+const std::string& get_item(const StringMap& self, std::size_t key)
+{
+    const StringMap::const_iterator p = self.find(key);
+    throw_key_error_if_end(self, p, key);
+    return p->second;
+}
+
+// Sets the item corresponding to key in the map.
+void StringMapPythonClass::set_item(StringMap& self, std::size_t key, const std::string& value)
+{
+    self[key] = value;
+}
+
+// Deletes the item corresponding to key from the map.
+void StringMapPythonClass::del_item(StringMap& self, std::size_t key)
+{
+    const StringMap::iterator p = self.find(key);
+    throw_key_error_if_end(self, p, key);
+    self.erase(p);
+}
+
+class_builder<StringMap> string_map(my_module, "StringMap");
+string_map.def(boost::python::constructor<>());
+string_map.def(&StringMap::size, "__len__");
+string_map.def(get_item, "__getitem__");
+string_map.def(set_item, "__setitem__");
+string_map.def(del_item, "__delitem__");
+
+
+

+ Then in Python: +

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

Customized Attribute Access

+ +

+ Just like built-in Python classes, Boost.Python extension classes support special + the usual attribute access methods __getattr__, + __setattr__, and __delattr__. + Because writing these functions can + be tedious in the common case where the attributes being accessed are + known statically, Boost.Python 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(AnyBoost.PythonExtensionClass):
+...    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 +

+

+ Boost.Python uses the special + __xxxattr__<name>__ functionality described above + to allow direct access to data members through the following special + functions on class_builder<> and + extension_class<>: +

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

+ Note that the first two functions, used alone, may produce surprising + behavior. For example, when def_getter() is used, the + default functionality for setattr() and + delattr() remains in effect, operating on items in the extension + instance's name-space (i.e., its __dict__). For that + reason, you'll usually want to stick with def_readonly and + def_read_write. +

+ For example, to expose a std::pair<int,long> we + might write: +

+
+typedef std::pair<int,long> Pil;
+int first(const Pil& x) { return x.first; }
+long second(const Pil& x) { return x.second; }
+   ...
+my_module.def(first, "first");
+my_module.def(second, "second");
+
+class_builder<Pil> pair_int_long(my_module, "Pair");
+pair_int_long.def(boost::python::constructor<>());
+pair_int_long.def(boost::python::constructor<int,long>());
+pair_int_long.def_read_write(&Pil::first, "first");
+pair_int_long.def_read_write(&Pil::second, "second");
+
+
+

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

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

+ And what about __complex__? +

+

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

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

+Next: A Peek Under the Hood +Previous: Inheritance +Up: Top +

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

+ Updated: Nov 26, 2000 +

+ diff --git a/doc/under-the-hood.html b/doc/under-the-hood.html new file mode 100644 index 00000000..ee0ecdfb --- /dev/null +++ b/doc/under-the-hood.html @@ -0,0 +1,61 @@ + + + + A Peek Under the Hood + +

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

+

+ A Peek Under the Hood +

+

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

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

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

+ Next: Building a Module with Boost.Python + Previous: Special Method and Operator Support + Up: Top +

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

+ Updated: Nov 26, 2000 + diff --git a/include/boost/python.hpp b/include/boost/python.hpp deleted file mode 100644 index ca81cce2..00000000 --- a/include/boost/python.hpp +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright David Abrahams 2002. 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. -#ifndef PYTHON_DWA2002810_HPP -# define PYTHON_DWA2002810_HPP - -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include - -#endif PYTHON_DWA2002810_HPP diff --git a/include/boost/python/converter/rvalue_from_python_data.hpp b/include/boost/python/converter/rvalue_from_python_data.hpp index c9e9ff03..7f512830 100644 --- a/include/boost/python/converter/rvalue_from_python_data.hpp +++ b/include/boost/python/converter/rvalue_from_python_data.hpp @@ -94,8 +94,7 @@ struct rvalue_from_python_data : rvalue_from_python_storage { # if (!defined(__MWERKS__) || __MWERKS__ >= 0x3000) \ && (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 245) \ - && (!defined(__DECCXX_VER) || __DECCXX_VER > 60590014) \ - && !(defined(__APPLE__) && defined(__MACH__) && __APPLE_CC__ <= 1161) + && (!defined(__DECCXX_VER) || __DECCXX_VER > 60590014) // This must always be a POD struct with m_data its first member. BOOST_STATIC_ASSERT(BOOST_PYTHON_OFFSETOF(rvalue_from_python_storage,stage1) == 0); # endif diff --git a/include/boost/python/errors.hpp b/include/boost/python/errors.hpp index cd0b3586..4c47aca8 100644 --- a/include/boost/python/errors.hpp +++ b/include/boost/python/errors.hpp @@ -46,9 +46,11 @@ inline T* expect_non_null(T* x) return x; } +# ifdef BOOST_PYTHON_V2 // Return source if it is an instance of pytype; throw an appropriate // exception otherwise. BOOST_PYTHON_DECL PyObject* pytype_check(PyTypeObject* pytype, PyObject* source); +# endif }} // namespace boost::python diff --git a/include/boost/python/init.hpp b/include/boost/python/init.hpp index 2707f7b7..589f8ac0 100644 --- a/include/boost/python/init.hpp +++ b/include/boost/python/init.hpp @@ -138,16 +138,16 @@ struct init_base init_base(char const* doc_, detail::keyword_range const& keywords_) : m_doc(doc_), m_keywords(keywords_) {} - + init_base(char const* doc_) : m_doc(doc_) {} - + DerivedT const& derived() const { return *static_cast(this); } - + char const* doc_string() const { return m_doc; @@ -162,7 +162,7 @@ struct init_base { return default_call_policies(); } - + private: // data members char const* m_doc; detail::keyword_range m_keywords; @@ -192,7 +192,7 @@ class init_with_call_policies { return this->m_policies; } - + private: // data members CallPoliciesT m_policies; }; @@ -355,7 +355,7 @@ namespace detail if (keywords.second > keywords.first) --keywords.second; - + typename mpl::pop_front::type next; define_class_init_helper::apply(cl, policies, next, doc, keywords); } diff --git a/include/boost/python/operators.hpp b/include/boost/python/operators.hpp index 7087f83a..df401f85 100644 --- a/include/boost/python/operators.hpp +++ b/include/boost/python/operators.hpp @@ -1,332 +1,555 @@ -// Copyright David Abrahams 2002. 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. -#ifndef OPERATORS_DWA2002530_HPP -# define OPERATORS_DWA2002530_HPP +// (C) Copyright Ullrich Koethe and David Abrahams 2000-2001. 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 authors gratefully acknowlege the support of Dragon Systems, Inc., in +// producing this work. +// +// Revision History: +// 23 Jan 2001 - Another stupid typo fix by Ralf W. Grosse-Kunstleve (David Abrahams) +// 20 Jan 2001 - Added a fix from Ralf W. Grosse-Kunstleve (David Abrahams) +#ifndef OPERATORS_UK112000_H_ +# define OPERATORS_UK112000_H_ +# ifdef BOOST_PYTHON_V2 -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include +# include -namespace boost { namespace python { +# else + +# include +# include + +// When STLport is used with native streams, _STL::ostringstream().str() is not +// _STL::string, but std::string. This confuses to_python(), so we'll use +// strstream instead. Also, GCC 2.95.2 doesn't have sstream. +# if defined(__SGI_STL_PORT) ? defined(__SGI_STL_OWN_IOSTREAMS) : (!defined(__GNUC__) || __GNUC__ > 2) +# define BOOST_PYTHON_USE_SSTREAM +# endif + +# if defined(BOOST_PYTHON_USE_SSTREAM) +# include +# else +# include +# endif + +namespace boost { namespace python { + +BOOST_PYTHON_DECL tuple standard_coerce(ref l, ref r); + +namespace detail { + + // helper class for automatic operand type detection + // during operator wrapping. + struct auto_operand {}; +} + +// Define operator ids that can be or'ed together +// (boost::python::op_add | boost::python::op_sub | boost::python::op_mul). +// This allows to wrap several operators in one line. +enum operator_id +{ + op_add = 0x1, + op_sub = 0x2, + op_mul = 0x4, + op_div = 0x8, + op_mod = 0x10, + op_divmod =0x20, + op_pow = 0x40, + op_lshift = 0x80, + op_rshift = 0x100, + op_and = 0x200, + op_xor = 0x400, + op_or = 0x800, + op_neg = 0x1000, + op_pos = 0x2000, + op_abs = 0x4000, + op_invert = 0x8000, + op_int = 0x10000, + op_long = 0x20000, + op_float = 0x40000, + op_str = 0x80000, + op_cmp = 0x100000, + op_gt = 0x200000, + op_ge = 0x400000, + op_lt = 0x800000, + op_le = 0x1000000, + op_eq = 0x2000000, + op_ne = 0x4000000 +}; + +// Wrap the operators given by "which". Usage: +// foo_class.def(boost::python::operators<(boost::python::op_add | boost::python::op_sub)>()); +template +struct operators {}; + +// Wrap heterogeneous operators with given left operand type. Usage: +// foo_class.def(boost::python::operators<(boost::python::op_add | boost::python::op_sub)>(), +// boost::python::left_operand()); +template +struct left_operand {}; + +// Wrap heterogeneous operators with given right operand type. Usage: +// foo_class.def(boost::python::operators<(boost::python::op_add | boost::python::op_sub)>(), +// boost::python::right_operand()); +template +struct right_operand {}; namespace detail { - // This is essentially the old v1 to_python(). It will be eliminated - // once the public interface for to_python is settled on. - template - PyObject* convert_result(T const& x) + template + struct operand_select { - return converter::arg_to_python(x).release(); - } + template + struct wrapped + { + typedef Specified type; + }; + }; - // Operator implementation template declarations. The nested apply - // declaration here keeps MSVC6 happy. - template struct operator_l + template <> + struct operand_select { - template struct apply; + template + struct wrapped + { + typedef const wrapped_type& type; + }; + }; + + template struct define_operator; + + // Base class which grants access to extension_class_base::add_method() to its derived classes + struct add_operator_base + { + protected: + static inline void add_method(extension_class_base* target, function* method, const char* name) + { target->add_method(method, name); } + }; + +// +// choose_op, choose_unary_op, and choose_rop +// +// These templates use "poor man's partial specialization" to generate the +// appropriate add_method() call (if any) for a given operator and argument set. +// +// Usage: +// choose_op<(which & op_add)>::template args::add(ext_class); +// +// (see extension_class<>::def_operators() for more examples). +// + template + struct choose_op + { + template + struct args : add_operator_base + { + static inline void add(extension_class_base* target) + { + typedef define_operator def_op; + add_method(target, + new typename def_op::template operator_function(), + def_op::name()); + } + + }; + }; + + // specialization for 0 has no effect + template <> + struct choose_op<0> + { + template + struct args + { + static inline void add(extension_class_base*) + { + } + + }; }; - template struct operator_r + template + struct choose_unary_op { - template struct apply; - }; - - template struct operator_1 - { - template struct apply; - }; - - // MSVC6 doesn't want us to do this sort of inheritance on a nested - // class template, so we use this layer of indirection to avoid - // ::template<...> on the nested apply functions below - template - struct operator_l_inner - : operator_l::template apply - {}; - - template - struct operator_r_inner - : operator_r::template apply - {}; - - template - struct operator_1_inner - : operator_1::template apply - {}; - - // Define three different binary_op templates which take care of - // these cases: - // self op self - // self op R - // L op self - // - // The inner apply metafunction is used to adjust the operator to - // the class type being defined. Inheritance of the outer class is - // simply used to provide convenient access to the operation's - // name(). - - // self op self - template - struct binary_op : operator_l - { - template - struct apply : operator_l_inner + template + struct args : add_operator_base { + static inline void add(extension_class_base* target) + { + typedef define_operator def_op; + add_method(target, + new typename def_op::template operator_function(), + def_op::name()); + } + }; }; - - // self op R - template - struct binary_op_l : operator_l - { - template - struct apply : operator_l_inner - { - }; - }; - - // L op self - template - struct binary_op_r : operator_r - { - template - struct apply : operator_r_inner - { - }; - }; - - template - struct unary_op : operator_1 - { - template - struct apply : operator_1_inner - { - }; - }; - - // This type is what actually gets returned from operators used on - // self_t - template - struct operator_ - : mpl::if_< - is_same - , typename mpl::if_< - is_same - , binary_op - , binary_op_l::type> - >::type - , typename mpl::if_< - is_same - , unary_op - , binary_op_r::type> - >::type - >::type - { - }; -} - -# define BOOST_PYTHON_BINARY_OPERATION(id, rid, expr) \ -namespace detail \ -{ \ - template <> \ - struct operator_l \ - { \ - template \ - struct apply \ - { \ - static inline PyObject* execute(L const& l, R const& r) \ - { \ - return detail::convert_result(expr); \ - } \ - }; \ - static char const* name() { return "__" #id "__"; } \ - }; \ - \ - template <> \ - struct operator_r \ - { \ - template \ - struct apply \ - { \ - static inline PyObject* execute(R const& r, L const& l) \ - { \ - return detail::convert_result(expr); \ - } \ - }; \ - static char const* name() { return "__" #rid "__"; } \ - }; \ -} - -# define BOOST_PYTHON_BINARY_OPERATOR(id, rid, op) \ -BOOST_PYTHON_BINARY_OPERATION(id, rid, l op r) \ -namespace self_ns \ -{ \ - template \ - inline detail::operator_ \ - operator##op(L const&, R const&) \ - { \ - return detail::operator_(); \ - } \ -} -BOOST_PYTHON_BINARY_OPERATOR(add, radd, +) -BOOST_PYTHON_BINARY_OPERATOR(sub, rsub, -) -BOOST_PYTHON_BINARY_OPERATOR(mul, rmul, *) -BOOST_PYTHON_BINARY_OPERATOR(div, rdiv, /) -BOOST_PYTHON_BINARY_OPERATOR(mod, rmod, %) -BOOST_PYTHON_BINARY_OPERATOR(lshift, rlshift, <<) -BOOST_PYTHON_BINARY_OPERATOR(rshift, rrshift, >>) -BOOST_PYTHON_BINARY_OPERATOR(and, rand, &) -BOOST_PYTHON_BINARY_OPERATOR(xor, rxor, ^) -BOOST_PYTHON_BINARY_OPERATOR(or, ror, |) -BOOST_PYTHON_BINARY_OPERATOR(gt, lt, >) -BOOST_PYTHON_BINARY_OPERATOR(ge, le, >=) -BOOST_PYTHON_BINARY_OPERATOR(lt, gt, <) -BOOST_PYTHON_BINARY_OPERATOR(le, ge, <=) -BOOST_PYTHON_BINARY_OPERATOR(eq, eq, ==) -BOOST_PYTHON_BINARY_OPERATOR(ne, ne, !=) -# undef BOOST_PYTHON_BINARY_OPERATOR - -// pow isn't an operator in C++; handle it specially. -BOOST_PYTHON_BINARY_OPERATION(pow, rpow, pow(l,r)) -# undef BOOST_PYTHON_BINARY_OPERATION - -namespace self_ns -{ -# ifndef BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP - template - inline detail::operator_ - pow(L const&, R const&) + // specialization for 0 has no effect + template <> + struct choose_unary_op<0> { - return detail::operator_(); - } -# else - // When there's no argument-dependent lookup, we need these - // overloads to handle the case when everything is imported into the - // global namespace. Note that the plain overload below does /not/ - // take const& arguments. This is needed by MSVC6 at least, or it - // complains of ambiguities, since there's no partial ordering. - inline detail::operator_ - pow(self_t, self_t) + template + struct args + { + static inline void add(extension_class_base*) + { + } + + }; + }; + + template + struct choose_rop { - return detail::operator_(); - } - template - inline detail::operator_ - pow(self_t const&, R const&) + template + struct args : add_operator_base + { + static inline void add(extension_class_base* target) + { + typedef define_operator def_op; + add_method(target, + new typename def_op::template roperator_function(), + def_op::rname()); + } + + }; + }; + + // specialization for 0 has no effect + template <> + struct choose_rop<0> { - return detail::operator_(); - } - template - inline detail::operator_ - pow(L const&, self_t const&) - { - return detail::operator_(); - } -# endif -} + template + struct args + { + static inline void add(extension_class_base*) + { + } + + }; + }; -# define BOOST_PYTHON_INPLACE_OPERATOR(id, op) \ -namespace detail \ -{ \ - template <> \ - struct operator_l \ - { \ - template \ - struct apply \ - { \ - static inline PyObject* \ - execute(back_reference l, R const& r) \ - { \ - l.get() op r; \ - return python::incref(l.source().ptr()); \ - } \ - }; \ - static char const* name() { return "__" #id "__"; } \ - }; \ -} \ -namespace self_ns \ -{ \ - template \ - inline detail::operator_ \ - operator##op(self_t const&, R const&) \ - { \ - return detail::operator_(); \ - } \ -} +// Fully specialize define_operator for all operators defined in operator_id above. +// Every specialization defines one function object for normal operator calls and one +// for operator calls with operands reversed ("__r*__" function variants). +// Specializations for most operators follow a standard pattern: execute the expression +// that uses the operator in question. This standard pattern is realized by the following +// macros so that the actual specialization can be done by just calling a macro. +# define PY_DEFINE_BINARY_OPERATORS(id, oper) \ + template <> \ + struct define_operator \ + { \ + template \ + struct operator_function : function \ + { \ + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const \ + { \ + tuple args(ref(arguments, ref::increment_count)); \ + \ + return BOOST_PYTHON_CONVERSION::to_python( \ + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()) oper \ + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type())); \ + } \ + \ + const char* description() const \ + { return "__" #id "__"; } \ + }; \ + \ + template \ + struct roperator_function : function \ + { \ + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const \ + { \ + tuple args(ref(arguments, ref::increment_count)); \ + \ + return BOOST_PYTHON_CONVERSION::to_python( \ + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()) oper \ + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type())); \ + } \ + \ + const char* description() const \ + { return "__r" #id "__"; } \ + \ + }; \ + \ + static const char * name() { return "__" #id "__"; } \ + static const char * rname() { return "__r" #id "__"; } \ + } -BOOST_PYTHON_INPLACE_OPERATOR(iadd,+=) -BOOST_PYTHON_INPLACE_OPERATOR(isub,-=) -BOOST_PYTHON_INPLACE_OPERATOR(imul,*=) -BOOST_PYTHON_INPLACE_OPERATOR(idiv,/=) -BOOST_PYTHON_INPLACE_OPERATOR(imod,%=) -BOOST_PYTHON_INPLACE_OPERATOR(ilshift,<<=) -BOOST_PYTHON_INPLACE_OPERATOR(irshift,>>=) -BOOST_PYTHON_INPLACE_OPERATOR(iand,&=) -BOOST_PYTHON_INPLACE_OPERATOR(ixor,^=) -BOOST_PYTHON_INPLACE_OPERATOR(ior,|=) - -# define BOOST_PYTHON_UNARY_OPERATOR(id, op, func_name) \ -namespace detail \ -{ \ - template <> \ - struct operator_1 \ - { \ - template \ - struct apply \ - { \ - static PyObject* execute(T const& x) \ - { \ - return detail::convert_result(op(x)); \ - } \ - }; \ - static char const* name() { return "__" #id "__"; } \ - }; \ -} \ -namespace self_ns \ -{ \ - inline detail::operator_ \ - func_name(self_t const&) \ - { \ - return detail::operator_(); \ - } \ -} -# undef BOOST_PYTHON_INPLACE_OPERATOR +# define PY_DEFINE_UNARY_OPERATORS(id, oper) \ + template <> \ + struct define_operator \ + { \ + template \ + struct operator_function : function \ + { \ + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const \ + { \ + tuple args(ref(arguments, ref::increment_count)); \ + \ + return BOOST_PYTHON_CONVERSION::to_python( \ + oper(BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()))); \ + } \ + \ + const char* description() const \ + { return "__" #id "__"; } \ + }; \ + \ + static const char * name() { return "__" #id "__"; } \ + } -BOOST_PYTHON_UNARY_OPERATOR(neg, -, operator-) -BOOST_PYTHON_UNARY_OPERATOR(pos, +, operator+) -BOOST_PYTHON_UNARY_OPERATOR(abs, abs, abs) -BOOST_PYTHON_UNARY_OPERATOR(invert, ~, operator~) -BOOST_PYTHON_UNARY_OPERATOR(int, long, int_) -BOOST_PYTHON_UNARY_OPERATOR(long, PyLong_FromLong, long_) -BOOST_PYTHON_UNARY_OPERATOR(float, double, float_) -BOOST_PYTHON_UNARY_OPERATOR(complex, std::complex, complex_) -BOOST_PYTHON_UNARY_OPERATOR(str, lexical_cast, str) -# undef BOOST_PYTHON_UNARY_OPERATOR + PY_DEFINE_BINARY_OPERATORS(add, +); + PY_DEFINE_BINARY_OPERATORS(sub, -); + PY_DEFINE_BINARY_OPERATORS(mul, *); + PY_DEFINE_BINARY_OPERATORS(div, /); + PY_DEFINE_BINARY_OPERATORS(mod, %); + PY_DEFINE_BINARY_OPERATORS(lshift, <<); + PY_DEFINE_BINARY_OPERATORS(rshift, >>); + PY_DEFINE_BINARY_OPERATORS(and, &); + PY_DEFINE_BINARY_OPERATORS(xor, ^); + PY_DEFINE_BINARY_OPERATORS(or, |); + PY_DEFINE_BINARY_OPERATORS(gt, >); + PY_DEFINE_BINARY_OPERATORS(ge, >=); + PY_DEFINE_BINARY_OPERATORS(lt, <); + PY_DEFINE_BINARY_OPERATORS(le, <=); + PY_DEFINE_BINARY_OPERATORS(eq, ==); + PY_DEFINE_BINARY_OPERATORS(ne, !=); + + PY_DEFINE_UNARY_OPERATORS(neg, -); + PY_DEFINE_UNARY_OPERATORS(pos, +); + PY_DEFINE_UNARY_OPERATORS(abs, abs); + PY_DEFINE_UNARY_OPERATORS(invert, ~); + PY_DEFINE_UNARY_OPERATORS(int, long); + PY_DEFINE_UNARY_OPERATORS(long, PyLong_FromLong); + PY_DEFINE_UNARY_OPERATORS(float, double); + +# undef PY_DEFINE_BINARY_OPERATORS +# undef PY_DEFINE_UNARY_OPERATORS + +// Some operators need special treatment, e.g. because there is no corresponding +// expression in C++. These are specialized manually. + +// pow(): Manual specialization needed because an error message is required if this +// function is called with three arguments. The "power modulo" operator is not +// supported by define_operator, but can be wrapped manually (see special.html). + template <> + struct define_operator + { + template + struct operator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + + if (args.size() == 3 && args[2]->ob_type != Py_None->ob_type) + { + PyErr_SetString(PyExc_TypeError, "expected 2 arguments, got 3"); + throw_argument_error(); + } + + return BOOST_PYTHON_CONVERSION::to_python( + pow(BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()), + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()))); + } + + const char* description() const + { return "__pow__"; } + + }; + + template + struct roperator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + + if (args.size() == 3 && args[2]->ob_type != Py_None->ob_type) + { + PyErr_SetString(PyExc_TypeError, "bad operand type(s) for pow()"); + throw_argument_error(); + } + + return BOOST_PYTHON_CONVERSION::to_python( + pow(BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()), + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()))); + } + + const char* description() const + { return "__rpow__"; } + + }; + + static const char * name() { return "__pow__"; } + static const char * rname() { return "__rpow__"; } + }; + +// divmod(): Manual specialization needed because we must actually call two operators and +// return a tuple containing both results + template <> + struct define_operator + { + template + struct operator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + PyObject * res = PyTuple_New(2); + + PyTuple_SET_ITEM(res, 0, + BOOST_PYTHON_CONVERSION::to_python( + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()) / + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()))); + PyTuple_SET_ITEM(res, 1, + BOOST_PYTHON_CONVERSION::to_python( + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()) % + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()))); + + return res; + } + + const char* description() const + { return "__divmod__"; } + + }; + + template + struct roperator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + PyObject * res = PyTuple_New(2); + + PyTuple_SET_ITEM(res, 0, + BOOST_PYTHON_CONVERSION::to_python( + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()) / + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()))); + PyTuple_SET_ITEM(res, 1, + BOOST_PYTHON_CONVERSION::to_python( + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()) % + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()))); + + return res; + } + + const char* description() const + { return "__rdivmod__"; } + + }; + + static const char * name() { return "__divmod__"; } + static const char * rname() { return "__rdivmod__"; } + }; + +// cmp(): Manual specialization needed because there is no three-way compare in C++. +// It is implemented by two one-way comparisons with operators reversed in the second. + template <> + struct define_operator + { + template + struct operator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + + return BOOST_PYTHON_CONVERSION::to_python( + (BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()) < + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type())) ? + - 1 : + (BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()) < + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type())) ? + 1 : + 0) ; + } + + const char* description() const + { return "__cmp__"; } + + }; + + template + struct roperator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject* /* keywords */) const + { + tuple args(ref(arguments, ref::increment_count)); + + return BOOST_PYTHON_CONVERSION::to_python( + (BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()) < + BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type())) ? + - 1 : + (BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()) < + BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type())) ? + 1 : + 0) ; + } + + const char* description() const + { return "__rcmp__"; } + + }; + + static const char * name() { return "__cmp__"; } + static const char * rname() { return "__rcmp__"; } + }; + +# ifndef BOOST_PYTHON_USE_SSTREAM + class unfreezer { + public: + unfreezer(std::ostrstream& s) : m_stream(s) {} + ~unfreezer() { m_stream.freeze(false); } + private: + std::ostrstream& m_stream; + }; +# endif + +// str(): Manual specialization needed because the string conversion does not follow +// the standard pattern relized by the macros. + template <> + struct define_operator + { + template + struct operator_function : function + { + PyObject* do_call(PyObject* arguments, PyObject*) const + { + tuple args(ref(arguments, ref::increment_count)); + +// When STLport is used with native streams, _STL::ostringstream().str() is not +// _STL::string, but std::string. +# ifdef BOOST_PYTHON_USE_SSTREAM + std::ostringstream s; + s << BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()); + return BOOST_PYTHON_CONVERSION::to_python(s.str()); +# else + std::ostrstream s; + s << BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()) << char(); + auto unfreezer unfreeze(s); + return BOOST_PYTHON_CONVERSION::to_python(const_cast(s.str())); +# endif + } + + const char* description() const + { return "__str__"; } + + }; + + static const char * name() { return "__str__"; } + }; + + +} // namespace detail }} // namespace boost::python -# ifdef BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP -using boost::python::self_ns::abs; -using boost::python::self_ns::int_; -using boost::python::self_ns::long_; -using boost::python::self_ns::float_; -using boost::python::self_ns::complex_; -using boost::python::self_ns::str; -using boost::python::self_ns::pow; -# endif - -#endif // OPERATORS_DWA2002530_HPP +# undef BOOST_PYTHON_USE_SSTREAM +# endif +#endif /* OPERATORS_UK112000_H_ */ diff --git a/test/comprehensive.cpp b/test/comprehensive.cpp new file mode 100644 index 00000000..e3a756b9 --- /dev/null +++ b/test/comprehensive.cpp @@ -0,0 +1,1265 @@ +// (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. + +// Revision History: +// 04 Mar 01 Changed name of extension module so it would work with DebugPython, +// eliminated useless test that aggravated MSVC (David Abrahams) +#include "comprehensive.hpp" +#include +#include // used for portability on broken compilers +#include // for pow() +#include + +#if defined(__sgi) \ + && ( (defined(_COMPILER_VERSION) && _COMPILER_VERSION <= 730) \ + && !defined(__GNUC__)) +inline double pow(int x, int y) { return pow(static_cast(x), y); } +#endif + +namespace bpl_test { + +FooCallback::FooCallback(PyObject* self, int x) + : Foo(x), m_self(self) +{ +} + +int FooCallback::add_len(const char* x) const +{ + // Try to call the "add_len" method on the corresponding Python object. + return boost::python::callback::call_method(m_self, "add_len", x); +} + +// A function which Python can call in case bar is not overridden from +// Python. In true Python style, we use a free function taking an initial self +// parameter. This function anywhere needn't be a static member of the callback +// class. The only reason to do it this way is that Foo::add_len is private, and +// FooCallback is already a friend of Foo. +int FooCallback::default_add_len(const Foo* self, const char* x) +{ + // Don't forget the Foo:: qualification, or you'll get an infinite + // recursion! + return self->Foo::add_len(x); +} + +// Since Foo::pure() is pure virtual, we don't need a corresponding +// default_pure(). A failure to override it in Python will result in an +// exception at runtime when pure() is called. +std::string FooCallback::pure() const +{ + return boost::python::callback::call_method(m_self, "pure"); +} + +Foo::PythonClass::PythonClass(boost::python::module_builder& m) + : boost::python::class_builder(m, "Foo") +{ + def(boost::python::constructor()); + def(&Foo::mumble, "mumble"); + def(&Foo::set, "set"); + def(&Foo::call_pure, "call_pure"); + def(&Foo::call_add_len, "call_add_len"); + + // This is the way we add a virtual function that has a default implementation. + def(&Foo::add_len, "add_len", &FooCallback::default_add_len); + + // Since pure() is pure virtual, we are leaving it undefined. + + // And the nested classes. + boost::python::class_builder foo_a(*this, "Foo_A"); + foo_a.def(boost::python::constructor<>()); + foo_a.def(&Foo::Foo_A::mumble, "mumble"); + + boost::python::class_builder foo_b(get_extension_class(), + "Foo_B"); + foo_b.def(boost::python::constructor<>()); + foo_b.def(&Foo::Foo_B::mumble, "mumble"); +} + +BarPythonClass::BarPythonClass(boost::python::module_builder& m) + : boost::python::class_builder(m, "Bar") +{ + def(boost::python::constructor()); + def(&Bar::first, "first"); + def(&Bar::second, "second"); + def(&Bar::pass_baz, "pass_baz"); +} + +BazPythonClass::BazPythonClass(boost::python::module_builder& m) + : boost::python::class_builder(m, "Baz") // optional +{ + def(boost::python::constructor<>()); + def(&Baz::pass_bar, "pass_bar"); + def(&Baz::clone, "clone"); + def(&Baz::create_foo, "create_foo"); + def(&Baz::get_foo_value, "get_foo_value"); + def(&Baz::eat_baz, "eat_baz"); +} + +StringMapPythonClass::StringMapPythonClass(boost::python::module_builder& m) + : boost::python::class_builder(m, "StringMap") +{ + def(boost::python::constructor<>()); + // Some compilers make the target of this function + // pointer the same type as the class in which it is defined (some + // standard library class), instead of StringMap. + def((std::size_t (StringMap::*)()const)&StringMap::size, "__len__"); + + def(&get_item, "__getitem__"); + def(&set_item, "__setitem__"); + def(&del_item, "__delitem__"); +} + +int get_first(const IntPair& p) +{ + return p.first; +} + +void set_first(IntPair& p, int value) +{ + p.first = -value; +} + +void del_first(const IntPair&) +{ + PyErr_SetString(PyExc_AttributeError, "first can't be deleted!"); + boost::python::throw_error_already_set(); +} + +IntPairPythonClass::IntPairPythonClass(boost::python::module_builder& m) + : boost::python::class_builder(m, "IntPair") +{ + def(boost::python::constructor()); + def(&getattr, "__getattr__"); + def(&setattr, "__setattr__"); + def(&delattr, "__delattr__"); + def(&get_first, "__getattr__first__"); + def(&set_first, "__setattr__first__"); + def(&del_first, "__delattr__first__"); +} + +void IntPairPythonClass::setattr(IntPair& x, const std::string& name, int value) +{ + if (name == "second") + { + x.second = value; + } + else + { + PyErr_SetString(PyExc_AttributeError, name.c_str()); + boost::python::throw_error_already_set(); + } +} + +void IntPairPythonClass::delattr(IntPair&, const char*) +{ + PyErr_SetString(PyExc_AttributeError, "Attributes can't be deleted!"); + boost::python::throw_error_already_set(); +} + +int IntPairPythonClass::getattr(const IntPair& p, const std::string& s) +{ + if (s == "second") + { + return p.second; + } + else + { + PyErr_SetString(PyExc_AttributeError, s.c_str()); + boost::python::throw_error_already_set(); + } + return 0; +} + +namespace { namespace file_local { +void throw_key_error_if_end(const StringMap& m, StringMap::const_iterator p, std::size_t key) +{ + if (p == m.end()) + { + PyErr_SetObject(PyExc_KeyError, BOOST_PYTHON_CONVERSION::to_python(key)); + boost::python::throw_error_already_set(); + } +} +}} // namespace ::file_local + +const std::string& StringMapPythonClass::get_item(const StringMap& m, std::size_t key) +{ + const StringMap::const_iterator p = m.find(key); + file_local::throw_key_error_if_end(m, p, key); + return p->second; +} + +void StringMapPythonClass::set_item(StringMap& m, std::size_t key, const std::string& value) +{ + m[key] = value; +} + +void StringMapPythonClass::del_item(StringMap& m, std::size_t key) +{ + const StringMap::iterator p = m.find(key); + file_local::throw_key_error_if_end(m, p, key); + m.erase(p); +} + +// +// Show that polymorphism can work. a DerivedFromFoo object will be passed to +// Python in a smart pointer object. +// +class DerivedFromFoo : public Foo +{ +public: + DerivedFromFoo(int x) : Foo(x) {} + +private: + std::string pure() const + { return "this was never pure!"; } + + int add_len(const char*) const + { return 1000; } +}; + +// +// function implementations +// + +IntPair make_pair(int x, int y) +{ + return std::make_pair(x, y); +} + +const char* Foo::mumble() +{ + return "mumble"; +} + +const char* Foo::Foo_A::mumble() +{ + return "mumble a"; +} + +const char* Foo::Foo_B::mumble() +{ + return "mumble b"; +} + +void Foo::set(long x) +{ + m_x = x; +} + +std::string Foo::call_pure() +{ + return this->pure(); +} + +int Foo::call_add_len(const char* s) const +{ + return this->add_len(s); +} + +int Foo::add_len(const char* s) const // sum the held value and the length of s +{ + return BOOST_CSTD_::strlen(s) + static_cast(m_x); +} + +boost::shared_ptr Baz::create_foo() +{ + return boost::shared_ptr(new DerivedFromFoo(0)); +} + +// Used to check conversion to None +boost::shared_ptr foo_factory(bool create) +{ + return boost::shared_ptr(create ? new DerivedFromFoo(0) : 0); +} + +// Used to check conversion from None +bool foo_ptr_is_null(Foo* p) +{ + return p == 0; +} + +bool foo_shared_ptr_is_null(boost::shared_ptr p) +{ + return p.get() == 0; +} + +// We can accept smart pointer parameters +int Baz::get_foo_value(boost::shared_ptr foo) +{ + return foo->call_add_len(""); +} + +// Show what happens in python when we take ownership from an auto_ptr +void Baz::eat_baz(std::auto_ptr baz) +{ + baz->clone(); // just do something to show that it is valid. +} + +Baz Bar::pass_baz(Baz b) +{ + return b; +} + +std::string stringpair_repr(const StringPair& sp) +{ + return "('" + sp.first + "', '" + sp.second + "')"; +} + +int stringpair_compare(const StringPair& sp1, const StringPair& sp2) +{ + return sp1 < sp2 ? -1 : sp2 < sp1 ? 1 : 0; +} + +boost::python::string range_str(const Range& r) +{ + char buf[200]; + sprintf(buf, "(%d, %d)", r.m_start, r.m_finish); + return boost::python::string(buf); +} + +int range_compare(const Range& r1, const Range& r2) +{ + int d = r1.m_start - r2.m_start; + if (d == 0) + d = r1.m_finish - r2.m_finish; + return d; +} + +long range_hash(const Range& r) +{ + return r.m_start * 123 + r.m_finish; +} + +/************************************************************/ +/* */ +/* some functions to test overloading */ +/* */ +/************************************************************/ + +static std::string testVoid() +{ + return std::string("Hello world!"); +} + +static int testInt(int i) +{ + return i; +} + +static std::string testString(std::string i) +{ + return i; +} + +static int test2(int i1, int i2) +{ + return i1+i2; +} + +static int test3(int i1, int i2, int i3) +{ + return i1+i2+i3; +} + +static int test4(int i1, int i2, int i3, int i4) +{ + return i1+i2+i3+i4; +} + +static int test5(int i1, int i2, int i3, int i4, int i5) +{ + return i1+i2+i3+i4+i5; +} + +/************************************************************/ +/* */ +/* a class to test overloading */ +/* */ +/************************************************************/ + +struct OverloadTest +{ + OverloadTest(): x_(1000) {} + OverloadTest(int x): x_(x) {} + OverloadTest(int x,int y): x_(x+y) { } + OverloadTest(int x,int y,int z): x_(x+y+z) {} + OverloadTest(int x,int y,int z, int a): x_(x+y+z+a) {} + OverloadTest(int x,int y,int z, int a, int b): x_(x+y+z+a+b) {} + + int x() const { return x_; } + void setX(int x) { x_ = x; } + + int p1(int x) { return x; } + int p2(int x, int y) { return x + y; } + int p3(int x, int y, int z) { return x + y + z; } + int p4(int x, int y, int z, int a) { return x + y + z + a; } + int p5(int x, int y, int z, int a, int b) { return x + y + z + a + b; } + private: + int x_; +}; + +static int getX(OverloadTest* u) +{ + return u->x(); +} + + +/************************************************************/ +/* */ +/* classes to test base declarations and conversions */ +/* */ +/************************************************************/ + +struct Dummy +{ + virtual ~Dummy() {} + int dummy_; +}; + +struct Base +{ + virtual int x() const { return 999; }; + virtual ~Base() {} +}; + +// inherit Dummy so that the Base part of Concrete starts at an offset +// otherwise, typecast tests wouldn't be very meaningful +struct Derived1 : public Dummy, public Base +{ + Derived1(int x): x_(x) {} + virtual int x() const { return x_; } + + private: + int x_; +}; + +struct Derived2 : public Dummy, public Base +{ + Derived2(int x): x_(x) {} + virtual int x() const { return x_; } + + private: + int x_; +}; + +static int testUpcast(Base* b) +{ + return b->x(); +} + +static std::auto_ptr derived1Factory(int i) +{ + return std::auto_ptr(i < 0 ? 0 : 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 boost::python::callback::call_method(m_self, "callback", x); + } + std::string callbackString(std::string const & x) + { + return boost::python::callback::call_method(m_self, "callback", x); + } + + static int default_callback(CallbackTest* self, int x) + { + return self->CallbackTest::callback(x); + } + static std::string default_callbackString(CallbackTest* self, std::string x) + { + return self->CallbackTest::callbackString(x); + } + + PyObject* m_self; +}; + +int testCallback(CallbackTestBase* b, int i) +{ + return b->testCallback(i); +} + +/************************************************************/ +/* */ +/* test classes for interaction of method lookup */ +/* in the context of inheritance */ +/* */ +/************************************************************/ + +struct A1 { + virtual ~A1() {} + virtual std::string overrideA1() const { return "A1::overrideA1"; } + virtual std::string inheritA1() const { return "A1::inheritA1"; } +}; + +struct A2 { + virtual ~A2() {} + virtual std::string inheritA2() const { return "A2::inheritA2"; } +}; + +struct B1 : A1, A2 { + std::string overrideA1() const { return "B1::overrideA1"; } + virtual std::string overrideB1() const { return "B1::overrideB1"; } +}; + +struct B2 : A1, A2 { + std::string overrideA1() const { return "B2::overrideA1"; } + virtual std::string inheritB2() const { return "B2::inheritB2"; } +}; + +struct C : B1 { + std::string overrideB1() const { return "C::overrideB1"; } +}; + +std::string call_overrideA1(const A1& a) { return a.overrideA1(); } +std::string call_overrideB1(const B1& b) { return b.overrideB1(); } +std::string call_inheritA1(const A1& a) { return a.inheritA1(); } + +std::auto_ptr factoryA1asA1() { return std::auto_ptr(new A1); } +std::auto_ptr factoryB1asA1() { return std::auto_ptr(new B1); } +std::auto_ptr factoryB2asA1() { return std::auto_ptr(new B2); } +std::auto_ptr factoryCasA1() { return std::auto_ptr(new C); } +std::auto_ptr factoryA2asA2() { return std::auto_ptr(new A2); } +std::auto_ptr factoryB1asA2() { return std::auto_ptr(new B1); } +std::auto_ptr factoryB1asB1() { return std::auto_ptr(new B1); } +std::auto_ptr factoryCasB1() { return std::auto_ptr(new C); } + +struct B_callback : B1 { + B_callback(PyObject* self) : m_self(self) {} + + std::string overrideA1() const { return boost::python::callback::call_method(m_self, "overrideA1"); } + std::string overrideB1() const { return boost::python::callback::call_method(m_self, "overrideB1"); } + + static std::string default_overrideA1(B1& x) { return x.B1::overrideA1(); } + static std::string default_overrideB1(B1& x) { return x.B1::overrideB1(); } + + PyObject* m_self; +}; + +struct A_callback : A1 { + A_callback(PyObject* self) : m_self(self) {} + + std::string overrideA1() const { return boost::python::callback::call_method(m_self, "overrideA1"); } + std::string inheritA1() const { return boost::python::callback::call_method(m_self, "inheritA1"); } + + static std::string default_overrideA1(A1& x) { return x.A1::overrideA1(); } + static std::string default_inheritA1(A1& x) { return x.A1::inheritA1(); } + + PyObject* m_self; +}; + +/************************************************************/ +/* */ +/* RawTest */ +/* (test passing of raw arguments to C++) */ +/* */ +/************************************************************/ + +struct RawTest +{ + RawTest(int i) : i_(i) {} + + int i_; +}; + +PyObject* raw(boost::python::tuple const & args, boost::python::dictionary const & keywords); + +int raw1(PyObject* args, PyObject* keywords) +{ + return PyTuple_Size(args) + PyDict_Size(keywords); +} + +int raw2(boost::python::ref args, boost::python::ref keywords) +{ + return PyTuple_Size(args.get()) + PyDict_Size(keywords.get()); +} + + + +/************************************************************/ +/* */ +/* Ratio */ +/* */ +/************************************************************/ + +typedef boost::rational Ratio; + +boost::python::string ratio_str(const Ratio& r) +{ + char buf[200]; + + if (r.denominator() == 1) + sprintf(buf, "%d", r.numerator()); + else + sprintf(buf, "%d/%d", r.numerator(), r.denominator()); + + return boost::python::string(buf); +} + +boost::python::string ratio_repr(const Ratio& r) +{ + char buf[200]; + sprintf(buf, "Rational(%d, %d)", r.numerator(), r.denominator()); + return boost::python::string(buf); +} + +boost::python::tuple ratio_coerce(const Ratio& r1, int r2) +{ + return boost::python::tuple(r1, Ratio(r2)); +} + +// The most reliable way, across compilers, to grab the particular abs function +// we're interested in. +Ratio ratio_abs(const Ratio& r) +{ + return boost::abs(r); +} + +// An experiment, to be integrated into the py_cpp library at some point. +template +struct StandardOps +{ + static T add(const T& x, const T& y) { return x + y; } + static T sub(const T& x, const T& y) { return x - y; } + static T mul(const T& x, const T& y) { return x * y; } + static T div(const T& x, const T& y) { return x / y; } + static T cmp(const T& x, const T& y) { return std::less()(x, y) ? -1 : std::less()(y, x) ? 1 : 0; } +}; + +// This helps us prove that we can now pass non-const reference parameters to constructors +struct Fubar { + Fubar(Foo&) {} + Fubar(int) {} +}; + +/************************************************************/ +/* */ +/* Int */ +/* this class tests operator export */ +/* */ +/************************************************************/ + +#ifndef NDEBUG +int total_Ints = 0; +#endif + +struct Int +{ + explicit Int(int i) : i_(i), j_(0) { +#ifndef NDEBUG + ++total_Ints; +#endif + } + +#ifndef NDEBUG + ~Int() { --total_Ints; } + Int(const Int& rhs) : i_(rhs.i_), j_(rhs.j_) { ++total_Ints; } +#endif + + int i() const { return i_; } + int j() const { return j_; } + + int i_; + int j_; + + Int& operator +=(Int const& r) { ++j_; i_ += r.i_; return *this; } + Int& operator -=(Int const& r) { ++j_; i_ -= r.i_; return *this; } + Int& operator *=(Int const& r) { ++j_; i_ *= r.i_; return *this; } + Int& operator /=(Int const& r) { ++j_; i_ /= r.i_; return *this; } + Int& operator %=(Int const& r) { ++j_; i_ %= r.i_; return *this; } + Int& ipow (Int const& r) { ++j_; + int o=i_; + for (int k=1; k>=(Int const& r) { ++j_; i_ >>= r.i_; return *this; } + Int& operator &=(Int const& r) { ++j_; i_ &= r.i_; return *this; } + Int& operator |=(Int const& r) { ++j_; i_ |= r.i_; return *this; } + Int& operator ^=(Int const& r) { ++j_; i_ ^= r.i_; return *this; } +}; + +Int operator+(Int const & l, Int const & r) { return Int(l.i_ + r.i_); } +Int operator+(Int const & l, int const & r) { return Int(l.i_ + r); } +Int operator+(int const & l, Int const & r) { return Int(l + r.i_); } + +Int operator-(Int const & l, Int const & r) { return Int(l.i_ - r.i_); } +Int operator-(Int const & l, int const & r) { return Int(l.i_ - r); } +Int operator-(int const & l, Int const & r) { return Int(l - r.i_); } +Int operator-(Int const & r) { return Int(- r.i_); } + +Int mul(Int const & l, Int const & r) { return Int(l.i_ * r.i_); } +Int imul(Int const & l, int const & r) { return Int(l.i_ * r); } +Int rmul(Int const & r, int const & l) { return Int(l * r.i_); } + +Int operator/(Int const & l, Int const & r) { return Int(l.i_ / r.i_); } + +Int operator%(Int const & l, Int const & r) { return Int(l.i_ % r.i_); } + +bool operator<(Int const & l, Int const & r) { return l.i_ < r.i_; } +bool operator<(Int const & l, int const & r) { return l.i_ < r; } +bool operator<(int const & l, Int const & r) { return l < r.i_; } + +Int pow(Int const & l, Int const & r) { return Int(static_cast(::pow(l.i_, r.i_))); } +Int powmod(Int const & l, Int const & r, Int const & m) { return Int((int)::pow(l.i_, r.i_) % m.i_); } +Int pow(Int const & l, int const & r) { return Int(static_cast(::pow(l.i_, r))); } + +std::ostream & operator<<(std::ostream & o, Int const & r) { return (o << r.i_); } + +/************************************************************/ +/* */ +/* double tests from Mark Evans() */ +/* */ +/************************************************************/ +double sizelist(boost::python::list list) { return list.size(); } +void vd_push_back(std::vector& vd, const double& x) +{ + vd.push_back(x); +} + +/************************************************************/ +/* */ +/* What if I want to return a pointer? */ +/* */ +/************************************************************/ + +// +// This example exposes the pointer by copying its referent +// +struct Record { + Record(int x) : value(x){} + int value; +}; + +const Record* get_record() +{ + static Record v(1234); + return &v; +} + +} // namespace bpl_test + +namespace boost { namespace python { + template class class_builder; // explicitly instantiate +}} // namespace boost::python + +BOOST_PYTHON_BEGIN_CONVERSION_NAMESPACE +inline PyObject* to_python(const bpl_test::Record* p) +{ + return to_python(*p); +} +BOOST_PYTHON_END_CONVERSION_NAMESPACE + +/************************************************************/ +/* */ +/* Enums and non-method class attributes */ +/* */ +/************************************************************/ + +namespace bpl_test { + +struct EnumOwner +{ + public: + enum enum_type { one = 1, two = 2, three = 3 }; + + EnumOwner(enum_type a1, const enum_type& a2) + : m_first(a1), m_second(a2) {} + + void set_first(const enum_type& x) { m_first = x; } + void set_second(const enum_type& x) { m_second = x; } + + enum_type first() { return m_first; } + enum_type second() { return m_second; } + private: + enum_type m_first, m_second; +}; + +} + +namespace boost { namespace python { + template class enum_as_int_converters; + using bpl_test::pow; +}} // namespace boost::python + +// This is just a way of getting the converters instantiated +//struct EnumOwner_enum_type_Converters +// : boost::python::py_enum_as_int_converters +//{ +//}; + +namespace bpl_test { + +/************************************************************/ +/* */ +/* pickling support */ +/* */ +/************************************************************/ + class world + { + private: + std::string country; + int secret_number; + public: + world(const std::string& country) : secret_number(0) { + this->country = country; + } + std::string greet() const { return "Hello from " + country + "!"; } + std::string get_country() const { return country; } + void set_secret_number(int number) { secret_number = number; } + int get_secret_number() const { return secret_number; } + }; + + // Support for pickle. + boost::python::tuple world_getinitargs(const world& w) + { + boost::python::tuple result(1); + result.set_item(0, w.get_country()); + return result; + } + + boost::python::tuple world_getstate(const world& w) + { + boost::python::tuple result(1); + result.set_item(0, w.get_secret_number()); + return result; + } + + void world_setstate(world& w, boost::python::tuple state) + { + if (state.size() != 1) { + PyErr_SetString(PyExc_ValueError, + "Unexpected argument in call to __setstate__."); + boost::python::throw_error_already_set(); + } + + const int number = BOOST_PYTHON_CONVERSION::from_python(state[0].get(), boost::python::type()); + if (number != 42) + w.set_secret_number(number); + } + + // Test plain char converters. + char get_plain_char() { return 'x'; } + std::string use_plain_char(char c) { return std::string(3, c); } + + // This doesn't test anything but the compiler, since it has the same signature as the above. + // Since MSVC is broken and gets the signature wrong, we'll skip it. + std::string use_const_plain_char( +#if !defined(BOOST_MSVC) || BOOST_MSVC > 1300 + const +#endif + char c) { return std::string(5, c); } + + // Test std::complex converters. + std::complex dpolar(double rho, double theta) { + return std::polar(rho, theta); + } + double dreal(const std::complex& c) { return c.real(); } + double dimag(std::complex c) { return c.imag(); } + + // Test std::complex converters. + std::complex fpolar(float rho, float theta) { + return std::polar(rho, theta); + } + double freal(const std::complex& c) { return c.real(); } + double fimag(std::complex c) { return c.imag(); } + + // Wrappers for inplace operators. + Int& int_iadd(Int& self, const Int& r) { self += r; return self; } + Int& int_isub(Int& self, const Int& r) { self -= r; return self; } + Int& int_imul(Int& self, const Int& r) { self *= r; return self; } + Int& int_idiv(Int& self, const Int& r) { self /= r; return self; } + Int& int_imod(Int& self, const Int& r) { self %= r; return self; } + Int& int_ipow(Int& self, const Int& r) { self.ipow (r); return self; } + Int& int_ilshift(Int& self, const Int& r) { self <<= r; return self; } + Int& int_irshift(Int& self, const Int& r) { self >>= r; return self; } + Int& int_iand(Int& self, const Int& r) { self &= r; return self; } + Int& int_ior(Int& self, const Int& r) { self |= r; return self; } + Int& int_ixor(Int& self, const Int& r) { self ^= r; return self; } + +/************************************************************/ +/* */ +/* init the module */ +/* */ +/************************************************************/ + +void init_module(boost::python::module_builder& m) +{ + m.def(get_record, "get_record"); + boost::python::class_builder record_class(m, "Record"); + record_class.def_readonly(&Record::value, "value"); + + m.def(sizelist, "sizelist"); + + boost::python::class_builder > vector_double(m, "vector_double"); + vector_double.def(boost::python::constructor<>()); + vector_double.def(vd_push_back, "push_back"); + + boost::python::class_builder fubar(m, "Fubar"); + fubar.def(boost::python::constructor()); + fubar.def(boost::python::constructor()); + + Foo::PythonClass foo(m); + BarPythonClass bar(m); + BazPythonClass baz(m); + StringMapPythonClass string_map(m); + IntPairPythonClass int_pair(m); + m.def(make_pair, "make_pair"); + CompareIntPairPythonClass compare_int_pair(m); + + boost::python::class_builder string_pair(m, "StringPair"); + string_pair.def(boost::python::constructor()); + string_pair.def_readonly(&StringPair::first, "first"); + string_pair.def_read_write(&StringPair::second, "second"); + string_pair.def(&stringpair_repr, "__repr__"); + string_pair.def(&stringpair_compare, "__cmp__"); + m.def(first_string, "first_string"); + m.def(second_string, "second_string"); + + // This shows the wrapping of a 3rd-party numeric type. + boost::python::class_builder > rational(m, "Rational"); + rational.def(boost::python::constructor()); + rational.def(boost::python::constructor()); + rational.def(boost::python::constructor<>()); + rational.def(StandardOps::add, "__add__"); + rational.def(StandardOps::sub, "__sub__"); + rational.def(StandardOps::mul, "__mul__"); + rational.def(StandardOps::div, "__div__"); + rational.def(StandardOps::cmp, "__cmp__"); + rational.def(ratio_coerce, "__coerce__"); + rational.def(ratio_str, "__str__"); + rational.def(ratio_repr, "__repr__"); + rational.def(ratio_abs, "__abs__"); + + boost::python::class_builder range(m, "Range"); + range.def(boost::python::constructor()); + range.def(boost::python::constructor()); + range.def((std::size_t (Range::*)() const)&Range::length, "__len__"); + range.def((void (Range::*)(std::size_t))&Range::length, "__len__"); + range.def(&Range::operator[], "__getitem__"); + range.def(&Range::slice, "__getslice__"); + range.def(&range_str, "__str__"); + range.def(&range_compare, "__cmp__"); + range.def(&range_hash, "__hash__"); + range.def_readonly(&Range::m_start, "start"); + range.def_readonly(&Range::m_finish, "finish"); + + m.def(&testVoid, "overloaded"); + m.def(&testInt, "overloaded"); + m.def(&testString, "overloaded"); + m.def(&test2, "overloaded"); + m.def(&test3, "overloaded"); + m.def(&test4, "overloaded"); + m.def(&test5, "overloaded"); + + boost::python::class_builder over(m, "OverloadTest"); + over.def(boost::python::constructor<>()); + over.def(boost::python::constructor()); + over.def(boost::python::constructor()); + over.def(boost::python::constructor()); + over.def(boost::python::constructor()); + over.def(boost::python::constructor()); + over.def(boost::python::constructor()); + over.def(&getX, "getX"); + over.def(&OverloadTest::setX, "setX"); + over.def(&OverloadTest::x, "overloaded"); + over.def(&OverloadTest::p1, "overloaded"); + over.def(&OverloadTest::p2, "overloaded"); + over.def(&OverloadTest::p3, "overloaded"); + over.def(&OverloadTest::p4, "overloaded"); + over.def(&OverloadTest::p5, "overloaded"); + + boost::python::class_builder base(m, "Base"); + base.def(&Base::x, "x"); + + boost::python::class_builder derived1(m, "Derived1"); + // this enables conversions between Base and Derived1 + // and makes wrapped methods of Base available + derived1.declare_base(base); + derived1.def(boost::python::constructor()); + + boost::python::class_builder derived2(m, "Derived2"); + // don't enable downcast from Base to Derived2 + derived2.declare_base(base, boost::python::without_downcast); + derived2.def(boost::python::constructor()); + + m.def(&testUpcast, "testUpcast"); + m.def(&derived1Factory, "derived1Factory"); + m.def(&derived2Factory, "derived2Factory"); + m.def(&testDowncast1, "testDowncast1"); + m.def(&testDowncast2, "testDowncast2"); + + boost::python::class_builder callbackTestBase(m, "CallbackTestBase"); + callbackTestBase.def(&CallbackTestBase::testCallback, "testCallback"); + m.def(&testCallback, "testCallback"); + + boost::python::class_builder callbackTest(m, "CallbackTest"); + callbackTest.def(boost::python::constructor<>()); + callbackTest.def(&CallbackTest::callback, "callback", + &CallbackTestCallback::default_callback); + callbackTest.def(&CallbackTest::callbackString, "callback", + &CallbackTestCallback::default_callbackString); + + callbackTest.declare_base(callbackTestBase); + + boost::python::class_builder a1_class(m, "A1"); + a1_class.def(boost::python::constructor<>()); + a1_class.def(&A1::overrideA1, "overrideA1", &A_callback::default_overrideA1); + a1_class.def(&A1::inheritA1, "inheritA1", &A_callback::default_inheritA1); + + boost::python::class_builder a2_class(m, "A2"); + a2_class.def(boost::python::constructor<>()); + a2_class.def(&A2::inheritA2, "inheritA2"); + + boost::python::class_builder b1_class(m, "B1"); + b1_class.declare_base(a1_class); + b1_class.declare_base(a2_class); + + b1_class.def(boost::python::constructor<>()); + b1_class.def(&B1::overrideA1, "overrideA1", &B_callback::default_overrideA1); + b1_class.def(&B1::overrideB1, "overrideB1", &B_callback::default_overrideB1); + + boost::python::class_builder b2_class(m, "B2"); + b2_class.declare_base(a1_class); + b2_class.declare_base(a2_class); + + b2_class.def(boost::python::constructor<>()); + b2_class.def(&B2::overrideA1, "overrideA1"); + b2_class.def(&B2::inheritB2, "inheritB2"); + + m.def(call_overrideA1, "call_overrideA1"); + m.def(call_overrideB1, "call_overrideB1"); + m.def(call_inheritA1, "call_inheritA1"); + + m.def(factoryA1asA1, "factoryA1asA1"); + m.def(factoryB1asA1, "factoryB1asA1"); + m.def(factoryB2asA1, "factoryB2asA1"); + m.def(factoryCasA1, "factoryCasA1"); + m.def(factoryA2asA2, "factoryA2asA2"); + m.def(factoryB1asA2, "factoryB1asA2"); + m.def(factoryB1asB1, "factoryB1asB1"); + m.def(factoryCasB1, "factoryCasB1"); + + boost::python::class_builder rawtest_class(m, "RawTest"); + rawtest_class.def(boost::python::constructor()); + rawtest_class.def_raw(&raw, "raw"); + + m.def_raw(&raw, "raw"); + m.def_raw(&raw1, "raw1"); + m.def_raw(&raw2, "raw2"); + + boost::python::class_builder int_class(m, "Int"); + int_class.def(boost::python::constructor()); + int_class.def(&Int::i, "i"); + int_class.def(&Int::j, "j"); + + // wrap homogeneous operators + int_class.def(boost::python::operators<(boost::python::op_add | boost::python::op_sub | boost::python::op_neg | + boost::python::op_cmp | boost::python::op_str | boost::python::op_divmod | boost::python::op_pow )>()); + // export non-operator functions as homogeneous operators + int_class.def(&mul, "__mul__"); + int_class.def(&powmod, "__pow__"); + + // wrap heterogeneous operators (lhs: Int const &, rhs: int const &) + int_class.def(boost::python::operators<(boost::python::op_add | boost::python::op_sub | boost::python::op_cmp | boost::python::op_pow)>(), + boost::python::right_operand()); + // export non-operator function as heterogeneous operator + int_class.def(&imul, "__mul__"); + + // wrap heterogeneous operators (lhs: int const &, rhs: Int const &) + int_class.def(boost::python::operators<(boost::python::op_add | boost::python::op_sub | boost::python::op_cmp)>(), + boost::python::left_operand()); + // export non-operator function as heterogeneous reverse-argument operator + int_class.def(&rmul, "__rmul__"); + +#if PYTHON_API_VERSION >= 1010 + // inplace operators. + int_class.def(&int_iadd, "__iadd__"); + int_class.def(&int_isub, "__isub__"); + int_class.def(&int_imul, "__imul__"); + int_class.def(&int_idiv, "__idiv__"); + int_class.def(&int_imod, "__imod__"); + int_class.def(&int_ipow, "__ipow__"); + int_class.def(&int_ilshift, "__ilshift__"); + int_class.def(&int_irshift, "__irshift__"); + int_class.def(&int_iand, "__iand__"); + int_class.def(&int_ior, "__ior__"); + int_class.def(&int_ixor, "__ixor__"); +#endif + + + boost::python::class_builder enum_owner(m, "EnumOwner"); + enum_owner.def(boost::python::constructor()); + enum_owner.def(&EnumOwner::set_first, "__setattr__first__"); + enum_owner.def(&EnumOwner::set_second, "__setattr__second__"); + enum_owner.def(&EnumOwner::first, "__getattr__first__"); + enum_owner.def(&EnumOwner::second, "__getattr__second__"); + enum_owner.add(PyInt_FromLong(EnumOwner::one), "one"); + enum_owner.add(PyInt_FromLong(EnumOwner::two), "two"); + enum_owner.add(PyInt_FromLong(EnumOwner::three), "three"); + + // pickling support + + // Create the Python type object for our extension class. + boost::python::class_builder world_class(m, "world"); + + // Add the __init__ function. + world_class.def(boost::python::constructor()); + // Add a regular member function. + world_class.def(&world::greet, "greet"); + world_class.def(&world::get_secret_number, "get_secret_number"); + world_class.def(&world::set_secret_number, "set_secret_number"); + + // Support for pickle. + world_class.def(world_getinitargs, "__getinitargs__"); + world_class.def(world_getstate, "__getstate__"); + world_class.def(world_setstate, "__setstate__"); + + // Test plain char converters. + m.def(get_plain_char, "get_plain_char"); + m.def(use_plain_char, "use_plain_char"); + m.def(use_const_plain_char, "use_const_plain_char"); + + // Test std::complex converters. + m.def(dpolar, "dpolar"); + m.def(dreal, "dreal"); + m.def(dimag, "dimag"); + + // Test std::complex converters. + m.def(fpolar, "fpolar"); + m.def(freal, "freal"); + m.def(fimag, "fimag"); + + // Test new null-pointer<->None conversions + m.def(foo_factory, "foo_factory"); + m.def(foo_ptr_is_null, "foo_ptr_is_null"); + m.def(foo_shared_ptr_is_null, "foo_shared_ptr_is_null"); +} + +PyObject* raw(const boost::python::tuple& args, const boost::python::dictionary& keywords) +{ + if(args.size() != 2 || keywords.size() != 2) + { + PyErr_SetString(PyExc_TypeError, "wrong number of arguments"); + boost::python::throw_argument_error(); + } + + RawTest* first = BOOST_PYTHON_CONVERSION::from_python(args[0].get(), boost::python::type()); + int second = BOOST_PYTHON_CONVERSION::from_python(args[1].get(), boost::python::type()); + + int third = BOOST_PYTHON_CONVERSION::from_python(keywords[boost::python::string("third")].get(), boost::python::type()); + int fourth = BOOST_PYTHON_CONVERSION::from_python(keywords[boost::python::string("fourth")].get(), boost::python::type()); + + return BOOST_PYTHON_CONVERSION::to_python(first->i_ + second + third + fourth); +} + +BOOST_PYTHON_MODULE(boost_python_test) +{ + boost::python::module_builder boost_python_test("boost_python_test"); + init_module(boost_python_test); + + // Just for giggles, add a raw metaclass. + boost_python_test.add(new boost::python::meta_class); +} + +CompareIntPairPythonClass::CompareIntPairPythonClass(boost::python::module_builder& m) + : boost::python::class_builder(m, "CompareIntPair") +{ + def(boost::python::constructor<>()); + def(&CompareIntPair::operator(), "__call__"); +} + +} // namespace bpl_test + + +#if defined(_WIN32) +# ifdef __MWERKS__ +# pragma ANSI_strict off +# endif +# include +# ifdef __MWERKS__ +# pragma ANSI_strict reset +# endif +extern "C" BOOL WINAPI DllMain ( HINSTANCE hInst, DWORD wDataSeg, LPVOID lpvReserved ); + +# ifdef BOOST_MSVC +extern "C" void structured_exception_translator(unsigned int, EXCEPTION_POINTERS*) +# if BOOST_MSVC > 1200 + throw(...) +# endif +{ + throw; +} +# endif + +#ifndef NDEBUG +namespace boost { namespace python { namespace detail { + extern int total_Dispatchers; +}}} // namespace boost::python::detail +#endif + +BOOL WINAPI DllMain( + HINSTANCE, //hDllInst + DWORD fdwReason, + LPVOID // lpvReserved + ) +{ +# ifdef BOOST_MSVC + _set_se_translator(structured_exception_translator); +#endif + (void)fdwReason; // warning suppression. + +#ifndef NDEBUG + switch(fdwReason) + { + case DLL_PROCESS_DETACH: + assert(bpl_test::total_Ints == 0); + } +#endif + + return 1; +} +#endif // _WIN32 diff --git a/test/comprehensive.hpp b/test/comprehensive.hpp new file mode 100644 index 00000000..370f7d04 --- /dev/null +++ b/test/comprehensive.hpp @@ -0,0 +1,235 @@ +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +#ifndef BPL_TEST_DWA052200_H_ +# define BPL_TEST_DWA052200_H_ +// +// Example code demonstrating extension class usage +// + +# include +# include +# include +# include +# include +# include +# include +# include + +namespace bpl_test { + +// +// example: Foo, Bar, and Baz are C++ classes we want to wrap. +// + +class Foo // prohibit copying, proving that it doesn't choke + : private boost::noncopyable // our generation of to_python(). +{ + public: // constructor/destructor + Foo(int x) : m_x(x) {} + virtual ~Foo() {} + + public: // non-virtual functions + const char* mumble(); // mumble something + void set(long x); // change the held value + + // These two call virtual functions + std::string call_pure(); // call a pure virtual fuction + int call_add_len(const char* s) const; // virtual function with a default implementation + + // A couple nested classs. + struct Foo_A { const char* mumble(); }; + struct Foo_B { const char* mumble(); }; + + private: + // by default, sum the held value and the length of s + virtual int add_len(const char* s) const; + + // Derived classes can do whatever they want here, but they must do something! + virtual std::string pure() const = 0; + + public: // friend declarations + // If you have private virtual functions such as add_len which you want to + // override in Python and have default implementations, they must be + // accessible by the thing making the def() call on the extension_class (in + // this case, the nested PythonClass itself), and by the C++ derived class + // which is used to cause the Python callbacks (in this case, + // FooCallback). See the definition of FooCallback::add_len() + struct PythonClass; + friend struct PythonClass; + friend class FooCallback; + + private: + int m_x; // the held value +}; + +// +// Bar and Baz have mutually-recursive type conversion dependencies (see +// pass_xxx functions). I've done this to prove that it doesn't cause a +// problem for Python class definitions, which happen later. +// +// Bar and Baz functions are only virtual to increase the likelihood of a crash +// if I inadvertently use a pointer to garbage memory (a likely thing to test +// for considering the amount of type casting needed to translate to and from +// Python). +struct Baz; +struct Bar +{ + Bar(int x, int y) : m_first(x), m_second(y) {} + virtual int first() const { return m_first; } + virtual int second() const { return m_second; } + virtual Baz pass_baz(Baz x); + + int m_first, m_second; +}; + +struct Baz +{ + virtual Bar pass_bar(const Bar& x) { return x; } + + // We can return smart pointers + virtual std::auto_ptr clone() { return std::auto_ptr(new Baz(*this)); } + + // This illustrates creating a polymorphic derived class of Foo + virtual boost::shared_ptr create_foo(); + + // We can accept smart pointer parameters + virtual int get_foo_value(boost::shared_ptr); + + // Show what happens in python when we take ownership from an auto_ptr + virtual void eat_baz(std::auto_ptr); +}; + +typedef std::map StringMap; +typedef std::pair IntPair; + +IntPair make_pair(int, int); + +typedef std::less CompareIntPair; +typedef std::pair StringPair; + +inline std::string first_string(const StringPair& x) +{ + return x.first; +} + +inline std::string second_string(const StringPair& x) +{ + return x.second; +} + +struct Range +{ + Range(int x) + : m_start(x), m_finish(x) {} + + Range(int start, int finish) + : m_start(start), m_finish(finish) {} + + std::size_t length() const + { return m_finish < m_start ? 0 : m_finish - m_start; } + + void length(std::size_t new_length) + { m_finish = m_start + new_length; } + + int operator[](std::size_t n) + { return m_start + n; } + + Range slice(std::size_t start, std::size_t end) + { + if (start > length()) + start = length(); + if (end > length()) + end = length(); + return Range(m_start + start, m_start + end); + } + + int m_start, m_finish; +}; + +//////////////////////////////////////////////////////////////////////// +// // +// Begin wrapping code. Usually this would live in a separate header. // +// // +//////////////////////////////////////////////////////////////////////// + +// Since Foo has virtual functions which we want overriden in Python, we must +// derive FooCallback. +class FooCallback : public Foo +{ + public: + // Note the additional constructor parameter "self", which is needed to + // allow function overriding from Python. + FooCallback(PyObject* self, int x); + + friend struct PythonClass; // give it access to the functions below + + private: // implementations of Foo virtual functions that are overridable in python. + int add_len(const char* x) const; + + // A function which Python can call in case bar is not overridden from + // Python. In true Python style, we use a free function taking an initial + // self parameter. You can put this function anywhere; it needn't be a + // static member of the wrapping class. + static int default_add_len(const Foo* self, const char* x); + + // Since Foo::pure() is pure virtual, we don't need a corresponding + // default_pure(). A failure to override it in Python will result in an + // exception at runtime when pure() is called. + std::string pure() const; + + private: // Required boilerplate if functions will be overridden + PyObject* m_self; // No, we don't want a boost::python::ref here, or we'd get an ownership cycle. +}; + +// Define the Python base class +struct Foo::PythonClass : boost::python::class_builder { PythonClass(boost::python::module_builder&); }; + +// No virtual functions on Bar or Baz which are actually supposed to behave +// virtually from C++, so we'll rely on the library to define a wrapper for +// us. Even so, Python class_t types for each type we're wrapping should be +// _defined_ here in a header where they can be seen by other extension class +// definitions, since it is the definition of the boost::python::class_builder<> that +// causes to_python/from_python conversion functions to be generated. +struct BarPythonClass : boost::python::class_builder { BarPythonClass(boost::python::module_builder&); }; +struct BazPythonClass : boost::python::class_builder { BazPythonClass(boost::python::module_builder&); }; + +struct StringMapPythonClass + : boost::python::class_builder +{ + StringMapPythonClass(boost::python::module_builder&); + + // These static functions implement the right argument protocols for + // implementing the Python "special member functions" for mapping on + // StringMap. Could just as easily be global functions. + static const std::string& get_item(const StringMap& m, std::size_t key); + static void set_item(StringMap& m, std::size_t key, const std::string& value); + static void del_item(StringMap& m, std::size_t key); +}; + +struct IntPairPythonClass + : boost::python::class_builder +{ + IntPairPythonClass(boost::python::module_builder&); + + // The following could just as well be a free function; it implements the + // getattr functionality for IntPair. + static int getattr(const IntPair&, const std::string& s); + static void setattr(IntPair&, const std::string& name, int value); + static void delattr(IntPair&, const char* name); +}; + +struct CompareIntPairPythonClass + : boost::python::class_builder +{ + CompareIntPairPythonClass(boost::python::module_builder&); +}; + +} // namespace bpl_test + +#endif // BPL_TEST_DWA052200_H_ diff --git a/test/comprehensive.py b/test/comprehensive.py new file mode 100644 index 00000000..f64ed661 --- /dev/null +++ b/test/comprehensive.py @@ -0,0 +1,1281 @@ +r''' +// (C) Copyright David Abrahams 2000. Permission to copy, use, modify, sell and +// distribute this software is granted provided this copyright notice appears +// in all copies. This software is provided "as is" without express or implied +// warranty, and with no claim as to its suitability for any purpose. +// +// The author gratefully acknowleges the support of Dragon Systems, Inc., in +// producing this work. + +// Revision History: +// 2001 Nov 01 Python 2.2 pickle problems fixed (rwgk) +// 04 Mar 01 Changed name of extension module so it would work with DebugPython, +// fixed exception message checking to work with Python 2.0 +// (Dave Abrahams) + +Automatic checking of the number and type of arguments. Foo's constructor takes +a single long parameter. + + >>> try: + ... ext = Foo() + ... except TypeError, err: + ... assert re.match(r'function .* exactly 1 argument;? \(?0 given\)?', + ... str(err)) + ... else: + ... print 'no exception' + + >>> try: ext = Foo('foo') + ... except TypeError, err: + ... assert_integer_expected(err) + ... else: + ... print 'no exception' + + >>> ext = Foo(1) + +Call a virtual function. This call takes a trip into C++ where +FooCallback::add_len() looks up the Python "add_len" attribute and finds the +wrapper for FooCallback::default_add_len(), which in turn calls Foo::add_len(). + + >>> ext.add_len('hello') + 6 + >>> ext.set(3) + >>> ext.add_len('hello') + 8 + +Call a pure virtual function which should have been overridden, but was not. + + >>> ext.call_pure() + Traceback (innermost last): + File "", line 1, in ? + AttributeError: pure + +We can subclass Foo. + + >>> class Subclass(Foo): + ... def __init__(self, seq): + ... Foo.__init__(self, len(seq)) + ... + ... def pure(self): + ... return 'not pure anymore!' + ... + ... def get(self): + ... return Foo.add_len(self, '') + ... + ... def add_len(self, s): + ... print 'called add_len()' + ... return self.get() + len(s) + ... + >>> b = Subclass('yippee') + >>> b.get() + 6 + >>> b.mumble() + 'mumble' + >>> b.call_pure() + 'not pure anymore!' + +None corresponds to a NULL pointer or smart pointer + >>> f = foo_factory(1) + >>> f.add_len('xxx') + 1000 + >>> foo_factory(0) is None + 1 + >>> foo_ptr_is_null(None) + 1 + >>> foo_ptr_is_null(f) + 0 + >>> foo_shared_ptr_is_null(None) + 1 + >>> foo_shared_ptr_is_null(f) + 0 + +If no __init__ function is defined, the one from the base class takes effect, just +like in a Python class. + + >>> class DemonstrateInitPassthru(Foo): pass + ... + >>> q = DemonstrateInitPassthru(1) + >>> q.add_len("x") + 2 + +If we don't initialize the base class, we'll get a RuntimeError when we try to +use its methods. The test illustrates the kind of error to expect. + + >>> class BadSubclass(Foo): + ... def __init__(self): pass + ... + >>> barf = BadSubclass() + >>> barf.set(4) + Traceback (innermost last): + ... + RuntimeError: __init__ function for extension class 'Foo' was never called. + +Here we are tesing that the simple definition procedure used in the C++ demo +file for classes without any virtual functions actually worked. + + >>> bar = Bar(3, 4) + >>> bar.first() + 3 + >>> bar.second() + 4 + >>> baz = Baz() + +We can actually return the wrapped classes by value + + >>> baz.pass_bar(bar).first() + 3 + >>> bar.pass_baz(baz) is baz # A copy of the return value is made. + 0 + >>> type(bar.pass_baz(baz)) is type(baz) + 1 + +And, yes, we can multiply inherit from these classes. + + >>> class MISubclass(Subclass, Bar): + ... def __init__(self, s): + ... Subclass.__init__(self, s) + ... Bar.__init__(self, 0, len(s)) + ... + >>> mi = MISubclass('xx') + >>> mi.first() + 0 + >>> mi.second() + 2 + >>> mi.mumble() + 'mumble' + +We can even mulitply inherit from built-in Python classes, even if they are +first in the list of bases + + >>> class RealPythonClass: + ... def real_python_method(self): + ... print 'RealPythonClass.real_python_method()' + ... def other_first(self, other): + ... return other.first() + + >>> class MISubclass2(RealPythonClass, Bar): + ... def new_method(self): + ... print 'MISubclass2.new_method()' + ... bound_function = RealPythonClass().other_first + ... + >>> mi2 = MISubclass2(7, 8) + >>> mi2.first() # we can call inherited member functions from Bar + 7 + >>> mi2.real_python_method() # we can call inherited member functions from RealPythonClass + RealPythonClass.real_python_method() + + >>> mi2.new_method() # we can call methods on the common derived class + MISubclass2.new_method() + + We can call unbound methods from the base class accessed through the derived class + >>> MISubclass2.real_python_method(mi2) + RealPythonClass.real_python_method() + + We have not interfered with ordinary python bound methods + >>> MISubclass2.bound_function(mi2) + 7 + >>> mi2.bound_function() + 7 + +Any object whose class is derived from Bar can be passed to a function expecting +a Bar parameter: + + >>> baz.pass_bar(mi).first() + 0 + +But objects not derived from Bar cannot: + + >>> baz.pass_bar(baz) + Traceback (innermost last): + ... + TypeError: extension class 'Baz' is not convertible into 'Bar'. + +The clone function on Baz returns a smart pointer; we wrap it into an +extension_instance and make it look just like any other Baz obj. + + >>> baz_clone = baz.clone() + >>> baz_clone.pass_bar(mi).first() + 0 + +Functions expecting an std::auto_ptr parameter will not accept a raw Baz + + >>> try: baz.eat_baz(Baz()) + ... except RuntimeError, err: + ... assert re.match("Object of extension class 'Baz' does not wrap <.*>.", + ... str(err)) + ... else: + ... print 'no exception' + +We can pass std::auto_ptr where it is expected + + >>> baz.eat_baz(baz_clone) + +And if the auto_ptr has given up ownership? + + # MSVC6 ships with an outdated auto_ptr that doesn't get zeroed out when it + # gives up ownership. If you are using MSVC6 without the new Dinkumware + # library, SGI STL or the STLport, expect this test to crash unless you put + # --broken-auto-ptr on the command line. + >>> if not '--broken-auto-ptr' in sys.argv: + ... try: baz_clone.clone() + ... except RuntimeError, err: + ... assert re.match('Converting from python, pointer or smart pointer to <.*> is NULL.', str(err)) + ... else: + ... print 'no exeption' + +Polymorphism also works: + + >>> polymorphic_foo = baz.create_foo() + >>> polymorphic_foo.call_pure() + 'this was never pure!' + >>> baz.get_foo_value(polymorphic_foo) + 1000 + +Simple nested class test: + >>> foo_a = Foo.Foo_A() + >>> foo_a.mumble() + 'mumble a' + >>> foo_b = Foo.Foo_B() + >>> foo_b.mumble() + 'mumble b' + +Pickling tests: + + >>> world.__module__ + 'boost_python_test' + >>> world.__safe_for_unpickling__ + 1 + >>> world.__reduce__() + 'world' + >>> reduced = world('Hello').__reduce__() + >>> reduced[0] == world + 1 + >>> reduced[1:] + (('Hello',), (0,)) + >>> import StringIO + >>> import cPickle + >>> pickle = cPickle + >>> for number in (24, 42): + ... wd = world('California') + ... wd.set_secret_number(number) + ... # Dump it out and read it back in. + ... f = StringIO.StringIO() + ... pickle.dump(wd, f) + ... f = StringIO.StringIO(f.getvalue()) + ... wl = pickle.load(f) + ... # + ... print wd.greet(), wd.get_secret_number() + ... print wl.greet(), wl.get_secret_number() + ... + Hello from California! 24 + Hello from California! 24 + Hello from California! 42 + Hello from California! 0 + +Pickle safety measures: + >>> r=Rational(3, 4) + >>> r + Rational(3, 4) + >>> try: s=pickle.dumps(r) + ... except RuntimeError, err: print err[0] + ... + Incomplete pickle support (__dict_defines_state__ not set) + >>> r=myrational(3, 4) + >>> r + Rational(3, 4) + >>> s=pickle.dumps(r) + >>> u=pickle.loads(s) + + >>> w = myworld() + >>> w.greet() + 'Hello from anywhere!' + >>> w.__dict__ + {'x': 1} + >>> try: s=pickle.dumps(w) + ... except RuntimeError, err: print err[0] + ... + Incomplete pickle support (__getstate_manages_dict__ not set) + + >>> w = myunsafeworld() + >>> w.greet() + 'Hello from anywhere!' + >>> w.__dict__ + {'x': 1} + >>> s=pickle.dumps(w) + +Special member attributes. Tests courtesy of Barry Scott + + >>> class DerivedFromFoo(Foo): + ... def __init__(self): + ... Foo.__init__( self, 1 ) + ... def fred(self): + ... 'Docs for DerivedFromFoo.fred' + ... print 'Barry.fred' + ... def __del__(self): + ... print 'Deleting DerivedFromFoo' + + >>> class Base: + ... i_am_base = 'yes' + ... def fred(self): + ... 'Docs for Base.fred' + ... pass + + + >>> class DerivedFromBase(Base): + ... i_am_derived_from_base = 'yes' + ... def fred(self): + ... 'Docs for DerivedFromBase.fred' + ... pass + + >>> dir(DerivedFromFoo) + ['__del__', '__doc__', '__init__', '__module__', 'fred'] + + >>> df = DerivedFromFoo() + >>> df.__dict__ + {} + >>> df.fred.__doc__ + 'Docs for DerivedFromFoo.fred' + + >>> db = DerivedFromBase() + >>> db.__dict__ + {} + >>> db.fred.__doc__ + 'Docs for DerivedFromBase.fred' + + >>> import sys + >>> if not sys.__dict__.has_key('version_info') or \ + ... sys.version_info[0] < 2 or ( sys.version_info[0] == 2 and + ... sys.version_info[1] < 2 ): + ... assert dir(df) == [] + ... assert dir(db) == [] + ... assert dir(DerivedFromBase) == [ + ... '__doc__', '__module__', 'fred', 'i_am_derived_from_base'] + ... else: + ... assert dir(df) == [ + ... 'Foo_A', 'Foo_B', '__del__', '__doc__', '__init__', '__module__', 'add_len', + ... 'call_add_len', 'call_pure', 'fred', 'mumble', 'set'] + ... assert dir(db) == ['__doc__', '__module__', 'fred' + ... , 'i_am_base', 'i_am_derived_from_base'] + ... assert dir(DerivedFromBase) == [ + ... '__doc__', '__module__', 'fred', 'i_am_base', 'i_am_derived_from_base'] + +Special member functions in action + >>> del df + Deleting DerivedFromFoo + + # force method table sharing + >>> class DerivedFromStringMap(StringMap): pass + ... + + >>> m = StringMap() + +__getitem__() + >>> m[1] + Traceback (innermost last): + File "", line 1, in ? + KeyError: 1 + +__setitem__() + + >>> m[1] = 'hello' + +__getitem__() + >>> m[1] + 'hello' + +__delitem__() + >>> del m[1] + >>> m[1] # prove that it's gone + Traceback (innermost last): + File "", line 1, in ? + KeyError: 1 + +__delitem__() + >>> del m[2] + Traceback (innermost last): + File "", line 1, in ? + KeyError: 2 + +__length__() + >>> len(m) + 0 + >>> m[3] = 'farther' + >>> len(m) + 1 + +Check for sequence/mapping confusion: + >>> for x in m: + ... print x + ... + Traceback (innermost last): + File "", line 1, in ? + KeyError: 0 + +Check for the ability to pass a non-const reference as a constructor parameter + >>> x = Fubar(Foo(1)) + +Some simple overloading tests: + >>> r = Range(3) + >>> print str(r) + (3, 3) + >>> r.start + 3 + >>> r.finish + 3 + >>> r.__len__() + 0 + >>> r.__len__(4) + >>> r.finish + 7 + >>> try: r = Range('yikes') + ... except TypeError, e: + ... assert re.match( + ... 'No overloaded functions match [(]Range, str[a-z]*[)]\. Candidates are:\n.*\n.*', + ... str(e)) + ... else: print 'no exception' + +Sequence tests: + >>> len(Range(3, 10)) + 7 + + >>> map(lambda x:x, Range(3, 10)) + [3, 4, 5, 6, 7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[-2:]) + [8, 9] + + >>> map(lambda x:x, Range(3, 10)[:-4]) + [3, 4, 5] + + >>> map(lambda x:x, Range(3, 10)[4:]) + [7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[4:100]) + [7, 8, 9] + + >>> map(lambda x:x, Range(3, 10)[20:]) + [] + + >>> map(lambda x:x, Range(3, 10)[0:4]) + [3, 4, 5, 6] + +Numeric tests: + >>> x = Rational(2,3) + >>> y = Rational(1,4) + >>> print x + y + 11/12 + >>> print x - y + 5/12 + >>> print x * y + 1/6 + >>> print x / y + 8/3 + >>> print x + 1 # testing coercion + 5/3 + >>> print 1 + x # coercion the other way + 5/3 + +delete non-existent attribute: + del m.foobar + Traceback (innermost last): + File "", line 1, in ? + AttributeError: delete non-existing obj attribute + +Testing __getattr__ and __getattr__: + + >>> n = IntPair(1, 2) + >>> n.first + 1 + >>> n.second + 2 + >>> n.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: third + +Testing __setattr__ and __setattr__: + >>> n.first = 33 # N.B __setattr__first sets first to + >>> n.first # the negative of its argument. + -33 + >>> n.second = 66 + >>> n.second + 66 + +Testing __delattr__ and __delattr__: + >>> del n.first + Traceback (innermost last): + File "", line 1, in ? + AttributeError: first can't be deleted! + >>> del n.second + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + >>> del n.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + + # Now show that we can override it. + + >>> class IntTriple(IntPair): + ... def __getattr__(self, s): + ... if s in ['first', 'second']: + ... return IntPair.__getattr__(self, s) + ... elif s == 'third': + ... return 3 + ... else: + ... raise AttributeError(s) + ... + ... # Also show that __setattr__ is supported + ... def __setattr__(self, name, value): + ... raise AttributeError('no writable attributes') + ... + >>> p = IntTriple(0, 1) + >>> p.first + 0 + >>> p.second + 1 + >>> p.third + 3 + >>> p.bax + Traceback (innermost last): + File "", line 1, in ? + AttributeError: bax + >>> p.third = 'yes' + Traceback (innermost last): + File "", line 1, in ? + AttributeError: no writable attributes + >>> del p.third + Traceback (innermost last): + File "", line 1, in ? + AttributeError: Attributes can't be deleted! + +demonstrate def_readonly, def_read_write: + >>> sp = StringPair("hello", "world") + >>> sp.first # first is read-only + 'hello' + >>> first_string(sp) # prove that we're not just looking in sp's __dict__ + 'hello' + >>> sp.first = 'hi' # we're not allowed to change it + Traceback (innermost last): + File "", line 1, in ? + AttributeError: 'first' attribute is read-only + >>> first_string(sp) # prove that it hasn't changed + 'hello' + + >>> sp.second # second is read/write + 'world' + >>> second_string(sp) + 'world' + >>> sp.second = 'universe' # set the second attribute + >>> sp.second + 'universe' + >>> second_string(sp) # this proves we didn't just set it in sp's __dict__ + 'universe' + +some __str__ and __repr__ tests: + >>> sp + ('hello', 'universe') + >>> repr(sp) + "('hello', 'universe')" + >>> str(sp) + "('hello', 'universe')" + + Range has a __str__ function but not a __repr__ function + >>> range = Range(5, 20) + >>> str(range) + '(5, 20)' + >>> assert re.match('', repr(range)) + +__hash__ and __cmp__ tests: + # Range has both __hash__ and __cmp__, thus is hashable + >>> colors = { Range(3,4): 'blue', Range(7,9): 'red' } + >>> colors[Range(3,4)] + 'blue' + + # StringPair has only __cmp__ + >>> { StringPair('yo', 'eddy'): 1 } + Traceback (innermost last): + File "", line 1, in ? + TypeError: unhashable type + + # But it can be sorted + >>> stringpairs = [ StringPair('yo', 'eddy'), StringPair('yo', 'betty'), sp ] + >>> stringpairs.sort() + >>> stringpairs + [('hello', 'universe'), ('yo', 'betty'), ('yo', 'eddy')] + +make_pair is a global function in the module. + + >>> couple = make_pair(3,12) + >>> couple.first + 3 + >>> couple.second + 12 + +Testing __call__: + >>> couple2 = make_pair(3, 7) + >>> comparator = CompareIntPair() + >>> comparator(couple, couple) + 0 + >>> comparator(couple, couple2) + 0 + >>> comparator(couple2, couple) + 1 + +Testing overloaded free functions + >>> overloaded() + 'Hello world!' + >>> overloaded(1) + 1 + >>> overloaded('foo') + 'foo' + >>> overloaded(1,2) + 3 + >>> overloaded(1,2,3) + 6 + >>> overloaded(1,2,3,4) + 10 + >>> overloaded(1,2,3,4,5) + 15 + >>> try: overloaded(1, 'foo') + ... except TypeError, err: + ... assert re.match("No overloaded functions match \(int, str[a-z]*\)\. 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, str[a-z]*\)\. 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, str[a-z]*\)\. 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 + + >>> try: c.testCallback('foo') + ... except TypeError, err: assert_integer_expected(err) + ... else: print 'no exception' + + >>> 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' + + >>> try: r.testCallback('foo') + ... except TypeError, err: assert_integer_expected(err) + ... else: print 'no exception' + + >>> r.testCallback(1) + -1 + >>> testCallback(r, 1) + -1 + +Regression test for a reference-counting bug thanks to Mark Evans +() + >>> sizelist([]) + 0.0 + >>> sizelist([1, 2, 4]) + 3.0 + +And another for doubles + >>> vector_double().push_back(3.0) + +Tests for method lookup in the context of inheritance +Set up the tests + + >>> a1 = A1() + >>> a2 = A2() + >>> b1 = B1() + >>> b2 = B2() + >>> pa1_a1 = factoryA1asA1() + >>> pb1_a1 = factoryB1asA1() + >>> pb2_a1 = factoryB2asA1() + >>> pc_a1 = factoryCasA1() + >>> pa2_a2 = factoryA2asA2() + >>> pb1_a2 = factoryB1asA2() + >>> pb1_b1 = factoryB1asB1() + >>> pc_b1 = factoryCasB1() + >>> class DA1(A1): + ... def overrideA1(self): + ... return 'DA1.overrideA1' + ... + >>> da1 = DA1() + >>> class DB1(B1): + ... def overrideA1(self): + ... return 'DB1.overrideA1' + ... def overrideB1(self): + ... return 'DB1.overrideB1' + ... + >>> db1 = DB1() + >>> class DB2(B2): pass + ... + >>> db2 = DB2() + +test overrideA1 + + >>> a1.overrideA1() + 'A1::overrideA1' + >>> b1.overrideA1() + 'B1::overrideA1' + >>> b2.overrideA1() + 'B2::overrideA1' + >>> da1.overrideA1() + 'DA1.overrideA1' + >>> db1.overrideA1() + 'DB1.overrideA1' + >>> pa1_a1.overrideA1() + 'A1::overrideA1' + >>> pb1_a1.overrideA1() + 'B1::overrideA1' + >>> pb2_a1.overrideA1() + 'B2::overrideA1' + >>> pb1_b1.overrideA1() + 'B1::overrideA1' + >>> pc_a1.overrideA1() + 'B1::overrideA1' + >>> pc_b1.overrideA1() + 'B1::overrideA1' + +test call_overrideA1 + + >>> call_overrideA1(a1) + 'A1::overrideA1' + >>> call_overrideA1(b1) + 'B1::overrideA1' + >>> call_overrideA1(b2) + 'B2::overrideA1' + >>> call_overrideA1(da1) + 'DA1.overrideA1' + >>> call_overrideA1(db1) + 'DB1.overrideA1' + >>> call_overrideA1(pa1_a1) + 'A1::overrideA1' + >>> call_overrideA1(pb1_a1) + 'B1::overrideA1' + >>> call_overrideA1(pb2_a1) + 'B2::overrideA1' + >>> call_overrideA1(pb1_b1) + 'B1::overrideA1' + >>> call_overrideA1(pc_a1) + 'B1::overrideA1' + >>> call_overrideA1(pc_b1) + 'B1::overrideA1' + +test inheritA1 + + >>> a1.inheritA1() + 'A1::inheritA1' + >>> b1.inheritA1() + 'A1::inheritA1' + >>> b2.inheritA1() + 'A1::inheritA1' + >>> da1.inheritA1() + 'A1::inheritA1' + >>> db1.inheritA1() + 'A1::inheritA1' + >>> pa1_a1.inheritA1() + 'A1::inheritA1' + >>> pb1_a1.inheritA1() + 'A1::inheritA1' + >>> pb2_a1.inheritA1() + 'A1::inheritA1' + >>> pb1_b1.inheritA1() + 'A1::inheritA1' + >>> pc_a1.inheritA1() + 'A1::inheritA1' + >>> pc_b1.inheritA1() + 'A1::inheritA1' + +test call_inheritA1 + + >>> call_inheritA1(a1) + 'A1::inheritA1' + >>> call_inheritA1(b1) + 'A1::inheritA1' + >>> call_inheritA1(b2) + 'A1::inheritA1' + >>> call_inheritA1(da1) + 'A1::inheritA1' + >>> call_inheritA1(db1) + 'A1::inheritA1' + >>> call_inheritA1(pa1_a1) + 'A1::inheritA1' + >>> call_inheritA1(pb1_a1) + 'A1::inheritA1' + >>> call_inheritA1(pb2_a1) + 'A1::inheritA1' + >>> call_inheritA1(pb1_b1) + 'A1::inheritA1' + >>> call_inheritA1(pc_a1) + 'A1::inheritA1' + >>> call_inheritA1(pc_b1) + 'A1::inheritA1' + +test inheritA2 + + >>> a2.inheritA2() + 'A2::inheritA2' + >>> b1.inheritA2() + 'A2::inheritA2' + >>> b2.inheritA2() + 'A2::inheritA2' + >>> db1.inheritA2() + 'A2::inheritA2' + >>> pa2_a2.inheritA2() + 'A2::inheritA2' + >>> pb1_a2.inheritA2() + 'A2::inheritA2' + >>> pb1_b1.inheritA2() + 'A2::inheritA2' + +test overrideB1 + + >>> b1.overrideB1() + 'B1::overrideB1' + >>> db1.overrideB1() + 'DB1.overrideB1' + >>> pb1_b1.overrideB1() + 'B1::overrideB1' + >>> pc_b1.overrideB1() + 'C::overrideB1' + +test call_overrideB1 + + >>> call_overrideB1(b1) + 'B1::overrideB1' + >>> call_overrideB1(db1) + 'DB1.overrideB1' + >>> call_overrideB1(pb1_a1) + 'B1::overrideB1' + >>> call_overrideB1(pc_a1) + 'C::overrideB1' + >>> call_overrideB1(pb1_b1) + 'B1::overrideB1' + >>> call_overrideB1(pc_b1) + 'C::overrideB1' + +test inheritB2 + + >>> b2.inheritB2() + 'B2::inheritB2' + >>> db2.inheritB2() + 'B2::inheritB2' + +========= test the new def_raw() feature ========== + + >>> r = RawTest(1) + >>> raw(r,1,third=1,fourth=1) + 4 + >>> r.raw(1,third=1,fourth=1) + 4 + >>> raw(r,1,third=1,f=1) + Traceback (innermost last): + KeyError: fourth + >>> raw(r,1,third=1) + Traceback (innermost last): + TypeError: wrong number of arguments + >>> raw(r,1) + Traceback (innermost last): + TypeError: wrong number of arguments + >>> raw() + Traceback (innermost last): + TypeError: wrong number of arguments + >>> raw1(1,second=1) + 2 + >>> raw1(1) + 1 + >>> raw1(second=1) + 1 + >>> raw1() + 0 + >>> raw2(1,second=1) + 2 + >>> raw2(1) + 1 + >>> raw2(second=1) + 1 + >>> raw2() + 0 + +========= test export of operators ========== + + >>> i = Int(2) + >>> j = i+i + >>> j.i() + 4 + >>> j = i-i + >>> j.i() + 0 + >>> j = i*i + >>> j.i() + 4 + >>> i>> cmp(i,i) + 0 + >>> k = Int(5) + >>> j = divmod(k, i) + >>> j[0].i() + 2 + >>> j[1].i() + 1 + >>> j = pow(i, k) + >>> j.i() + 32 + >>> j = pow(i, k, k) + >>> j.i() + 2 + >>> j = -i + >>> j.i() + -2 + >>> str(i) + '2' + >>> try: j = i/i + ... except TypeError, err: + ... assert re.match(r'(bad|unsupported) operand type\(s\) for /', + ... str(err)) + ... else: print 'no exception' + + >>> j = abs(i) + Traceback (innermost last): + TypeError: bad operand type for abs() + >>> j = i+1 + >>> j.i() + 3 + >>> j = i-1 + >>> j.i() + 1 + >>> j = i*1 + >>> j.i() + 2 + >>> i<1 + 0 + >>> cmp(i,1) + 1 + >>> j = pow(i, 5) + >>> j.i() + 32 + >>> j = pow(i, 5, k) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + >>> j = pow(i, 5, 5) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + >>> j = i/1 + Traceback (innermost last): + TypeError: bad operand type(s) for / + >>> j = 1+i + >>> j.i() + 3 + >>> j = 1-i + >>> j.i() + -1 + >>> j = 1*i + >>> j.i() + 2 + >>> 1>> cmp(1,i) + -1 + >>> j = 1/i + Traceback (innermost last): + TypeError: bad operand type(s) for / + >>> pow(1,i) + Traceback (innermost last): + TypeError: bad operand type(s) for pow() + +Test operator export to a subclass + + # force method table sharing + >>> class IntDerived1(Int): pass + ... + + >>> class IntDerived(Int): + ... def __init__(self, i): + ... Int.__init__(self, i) + ... def __str__(self): + ... return 'IntDerived: ' + str(self.i()) + ... + >>> f = IntDerived(3) + >>> str(f) + 'IntDerived: 3' + >>> j = f * f + >>> j.i() + 9 + >>> j = f * i + >>> j.i() + 6 + >>> j = f * 5 + >>> j.i() + 15 + >>> j = i * f + >>> j.i() + 6 + >>> j = 5 * f + >>> j.i() + 15 + + +========= Prove that the "phantom base class" issue is resolved ========== + + >>> assert pa1_a1.__class__ == A1 + >>> assert pb1_a1.__class__ == A1 + >>> assert pb2_a1.__class__ == A1 + >>> assert pc_a1.__class__ == A1 + >>> assert pa2_a2.__class__ == A2 + >>> assert pb1_a2.__class__ == A2 + >>> assert pb1_b1.__class__ == B1 + >>> assert pc_b1.__class__ == B1 + >>> assert A1 in B1.__bases__ + >>> assert A2 in B1.__bases__ + >>> assert A1 in B2.__bases__ + >>> assert A2 in B2.__bases__ + >>> assert A1 in DA1.__bases__ + >>> assert B1 in DB1.__bases__ + >>> assert B2 in DB2.__bases__ + +=============================================================== +test methodologies for wrapping functions that return a pointer + + >>> get_record().value + 1234 + + In this methodology, the referent is copied + >>> get_record() == get_record() + 0 + +======== Enums and non-method class attributes ============== + >>> eo = EnumOwner(EnumOwner.one, EnumOwner.two) + >>> eo.first + 1 + >>> eo.second + 2 + >>> eo.first = EnumOwner.three + >>> eo.second = EnumOwner.one + >>> eo.first + 3 + >>> eo.second + 1 + +======== test [plain] char converters ============== + >>> get_plain_char() + 'x' + >>> use_plain_char('a') + 'aaa' + >>> use_const_plain_char('b') + 'bbbbb' + +======== test std::complex converters ============== + >>> c = dpolar(3, 5) + >>> type(c) + + >>> '%.3g' % (dreal(c)) + '0.851' + >>> '%.3g' % (dimag(c)) + '-2.88' + >>> '%.3g' % (freal(c)) + '0.851' + >>> '%.3g' % (fimag(c)) + '-2.88' + >>> c = fpolar(7, 13) + >>> type(c) + + >>> '%.3g' % (fimag(c)) + '2.94' + >>> '%.3g' % (freal(c)) + '6.35' + >>> '%.3g' % (dimag(c)) + '2.94' + >>> '%.3g' % (dreal(c)) + '6.35' + >>> '%.3g' % (dreal(3)) + '3' + >>> '%.3g' % (dreal(3L)) + '3' + >>> '%.3g' % (dreal(3.)) + '3' + >>> '%.3g' % (freal(3)) + '3' + >>> '%.3g' % (freal(3L)) + '3' + >>> '%.3g' % (freal(3.)) + '3' + +''' +#' + +__test__ = {} +import sys + +# Inplace ops only exist in python 2.1 or later. +if sys.hexversion >= 0x02010000: + __test__['inplacetests'] = r''' + >>> ii = Int(1) + >>> ii += Int(2) + >>> ii.i() + 3 + >>> ii -= Int(1) + >>> ii.i() + 2 + >>> ii *= Int(3) + >>> ii.i() + 6 + >>> ii /= Int(2) + >>> ii.i() + 3 + >>> ii <<= Int(2) + >>> ii.i() + 12 + >>> ii >>= Int(1) + >>> ii.i() + 6 + >>> ii &= Int(5) + >>> ii.i() + 4 + >>> ii |= Int(9) + >>> ii.i() + 13 + >>> ii ^= Int(7) + >>> ii.i() + 10 + >>> ii %= Int(4) + >>> ii.i() + 2 + >>> ii **= Int(3) + >>> ii.i() + 8 + >>> ii.j() + 11 +''' + +from boost_python_test import * + +# pickle requires these derived classes to be +# at the global scope of the module + +class myrational(Rational): + __dict_defines_state__ = 1 # this is a lie but good enough for testing. + +class myworld(world): + def __init__(self): + world.__init__(self, 'anywhere') + self.x = 1 + +class myunsafeworld(myworld): + __getstate_manages_dict__ = 1 # this is a lie but good enough for testing. + + +def assert_integer_expected(err): + """Handle a common error report which appears differently in Python 1.5.x and 2.0""" + assert isinstance(err, TypeError) + message = str(err) + assert (message == "illegal argument type for built-in operation" + or message == "an integer is required") + +import string +import re +import sys + +def run(args = None): + if args is not None: + sys.argv = args + + import doctest, comprehensive + return doctest.testmod(comprehensive) + +if __name__ == '__main__': + sys.exit(run()[0])