Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Integrating C++ and Python with Boost.Python

abingham
September 19, 2014

Integrating C++ and Python with Boost.Python

These are slides from a workshop on how to develop Python extension modules using Boost.Python.

abingham

September 19, 2014
Tweet

More Decks by abingham

Other Decks in Programming

Transcript

  1. Integrating C++ and Python with Boost.Python Austin Bingham @austin_bingham @sixty_north

    You can download the IPython notebook for this presentation from our bitbucket repo. (https://bitbucket.org/sixty-north/boost_python_workshop)
  2. What is Python / C++ Integration? A lot of language

    integration can happen between processes
  3. For this presentation, in-process interaction with Python is what we're

    interested in. This interaction is based on using l i b p y t h o n . Integration is fundamentally just using l i b p y t h o n as a library Extension Embedding Everything Python does, you can do, too! This is great because...
  4. ...it gives you a lot of power. ...it can help

    with performance. ...it provides interoperability with a broad base of existing software. This is terrible because... ...the C API can be hard to work with. ...you are responsible for doing things correctly. ...it introduces the possibility for bugs that are impossible in pure Python. Are you sure you still want to do this?
  5. PyObject P y O b j e c t is

    the central type for everything that happens in the Python C API. Every "thing" in Python is represented as a P y O b j e c t . P y O b j e c t manages two central details in Python: an object's type and object's reference count Examples of P y O b j e c t in the C API: P y O b j e c t * P y O b j e c t _ G e t A t t r ( P y O b j e c t * o , P y O b j e c t * a t t r _ n a m e ) P y O b j e c t * P y E v a l _ G e t C a l l S t a t s ( P y O b j e c t * s e l f ) P y O b j e c t * P y E r r _ S e t F r o m E r r n o W i t h F i l e n a m e O b j e c t ( P y O b j e c t * t y p e , P y O b j e c t * f i l e n a m e O b j e c t ) Reference counting Every object contains a count of references to that object. The garbage collector uses this number to determine when an object can be de/reallocated.
  6. This is generally invisible to users at the Python level,

    but it's critical to users of the C API. Reference count example I n [ 1 ] : f r o m s y s i m p o r t g e t r e f c o u n t x = ' a s i m p l e s t r i n g ' p r i n t ( ' R e f - c o u n t o f s t r i n g w i t h o n r e f e r e n c e : { } ' . f o r m a t ( g e t r e f c o u n t ( x ) ) ) y = x p r i n t ( ' R e f - c o u n t o f s t r i n g w i t h a n a l i a s : { } ' . f o r m a t ( g e t r e f c o u n t ( x ) ) ) y = N o n e p r i n t ( ' R e f - c o u n t o f s t r i n g a f t e r r e m o v i n g a l i a s : { } ' . f o r m a t ( g e t r e f c o u n t ( x ) ) ) Reference count example illustrated R e f - c o u n t o f s t r i n g w i t h o n r e f e r e n c e : 3 R e f - c o u n t o f s t r i n g w i t h a n a l i a s : 4 R e f - c o u n t o f s t r i n g a f t e r r e m o v i n g a l i a s : 3
  7. Everything has a ref-count I n [ 2 ] :

    i m p o r t s y s p r i n t ( ' R e f - c o u n t o f N o n e : { } ' . f o r m a t ( g e t r e f c o u n t ( N o n e ) ) ) p r i n t ( ' R e f - c o u n t o f s y s : { } ' . f o r m a t ( g e t r e f c o u n t ( s y s ) ) ) p r i n t ( ' R e f - c o u n t o f s y s . g e t r e f c o u n t : { } ' . f o r m a t ( g e t r e f c o u n t ( g e t r e f c o u n t ) ) ) What can go wrong with reference counting? You can forget to increment it. You can forget to decrement it. You can increment it too many times. R e f - c o u n t o f N o n e : 1 1 8 9 6 R e f - c o u n t o f s y s : 2 4 4 R e f - c o u n t o f s y s . g e t r e f c o u n t : 5
  8. You can decrement it too many times. Reference counting API

    The primary functions (really macros) for doing reference counting are: P y _ I N C R E F ( ) P y _ X I N C R E F ( ) - checks for NULL first P y _ D E C R E F ( ) P y _ X D E C R E F ( ) - checks for NULL first P y _ C L E A R ( ) - checks for NULL first See the documentation (http://docs.python.org/3/c-api/refcounting.html) for full details. Exceptions and error handling C doesn't have exceptions, so Python's exceptions are expressed purely in terms of the C language. What this ultimately means is that there are some global exception variables which indicate if an exception has been thrown. It is up to callers to check and set these as appropriate. Raising exceptions There are a number of ways to raise exceptions with the C API. The most common is P y E r r _ S e t S t r i n g : v o i d P y E r r _ S e t S t r i n g ( P y O b j e c t * t y p e , c o n s t c h a r * m e s s a g e ) The exception API can be tricky Many C API calls will clear the exception registers.
  9. Some functions require the registers to be set. Read up

    on the exception API in the Python docs (http://docs.python.org/3/c-api/exceptions.html). Protocols The various protocols in Python each have their own set of API. For example: P y S e q u e n c e _ * for working with sequences P y I t e r _ * for working with iterators and iterables P y F u n c t i o n _ * for working with functions (not actually a protocol, but a concrete type) Read all about it The Python C API is well documented. It's a large API, but the documentation is well-structured enough to generally guide you to where you need to be. You can the full details of the C API at: http://docs.python.org/3/c-api/index.html (http://docs.python.org/3/c-api/index.html) Core Boost.Python
  10. b o o s t : : p y t

    h o n : : o b j e c t and b o o s t : : p y t h o n : : h a n d l e b p : : h a n d l e < > is used to take initial ownership of a P y O b j e c t . b p : : o b j e c t is constructed from a b p : : h a n d l e < > . b p : : o b j e c t uses RAII to manage reference counting on a P y O b j e c t so that it's harder (but not impossible) for you mess it up.
  11. b o o s t : : p y t

    h o n : : o b j e c t usage example Taking ownership of a so-called new refererence: / / T h i s c r e a t e s a " n e w " r e f e r e n c e . W e a r e r e s p o n s i b l e f o r d e c r e f ' i n g i t a t s o m e p o i n t . P y O b j e c t * p y o b j = P y L i s t _ N e w ( 1 0 ) ; / / h a n d l e < > a n d o b j e c t d o n o t i n c r e f a n o b j e c t n o r m a l l y . b o o s t : : p y t h o n : : o b j e c t o b j ( b o o s t : : p y t h o n : : h a n d l e < > ( p y o b j ) ; Taking a borrowed reference: / / G e t t i n g a l i s t i t e m r e t u r n s a b o r r o w e d r e f e r e n c e P y O b j e c t * l i s t _ i t e m = P y L i s t _ G e t I t e m ( l i s t _ o b j , 3 ) ; / / S o w e u s e b o r r o w e d ( ) t o i n c r e f i t . b o o s t : : p y t h o n : : o b j e c t o b j ( b o o s t : : p y t h o n : : h a n d l e < > ( b o o s t : : p y t h o n : : b o r r o w e d ( l i s t _ i t e m ) ) ) ;
  12. Attribute access with b o o s t : :

    p y t h o n : : o b j e c t : : a t t r ( ) To access attributes on an o b j e c t use the a t t r ( ) member function. b o o s t : : p y t h o n : : o b j e c t o b j ( . . . ) ; / / A c c e s s t h e " f o o " a t t r i b u t e o n t h e o b j e c t . E q u i v a l e n t t o " x = o b j . f o o " i n P y t h o n . b o o s t : : p y t h o n : : o b j e c t x = o b j . a t t r ( " f o o " ) ; Calling objects You can call b o o s t : : p y t h o n : : o b j e c t s . This can fail if the object is not a callable. b o o s t : : p y t h o n : : o b j e c t o b j = . . . ; / / C a l l t h e o b j e c t t o g e t a n o t h e r o b j e c t b o o s t : : p y t h o n : : o b j e c t r e s u l t = o b j ( ) ; / / A n d o f c o u r s e y o u c a n p a s s a r g u m e n t s t o t h e c a l l b o o s t : : p y t h o n : : o b j e c t n e x t _ r e s u l t = r e s u l t ( 4 2 , " l l a m a s " ) ; Constructing o b j e c t s from C++ objects You can construct o b j e c t instances from arbitrary C++ objects. This will always compile, but it may fail at runtime if Boost.Python can't find a type converter. It looks like this:
  13. c l a s s F o o { }

    ; v o i d s o m e _ f u n c t i o n ( ) { F o o f ; / / T h i s i n v o k e s a t y p e - c o n v e r t e r l o o k u p a n d , i f s u c c e s s f u l , c o n v e r s i o n . b o o s t : : p y t h o n : : o b j e c t o b j ( f ) ; . . . } We will look at type-conversion in more detail later. Extracting C++ objects from Python objects with e x t r a c t < > You can convert b o o s t : : p y t h o n : : o b j e c t instances into other C++ types with b o o s t : : p y t h o n : : e x t r a c t < > . e x t r a c t < > is a class, not a function, but its general use looks like a function call: b o o s t : : p y t h o n : : o b j e c t o b j = . . . ; / / U s e e x t r a c t < > t o c o n v e r t o b j i n t o a m o r e u s e f u l t y p e . F o o f = b o o s t : : p y t h o n : : e x t r a c t < F o o > ( o b j . a t t r ( " g e t _ f o o " ) ( ) ) ; Exceptions with Boost.Python Translating C++ exceptions into Python By default Boost.Python will translate C++ exceptions into a R u n t i m e E r r o r with the message "Unidentifiable C++ exception." There are a few exceptions with special built-in handling: s t d : : b a d _ a l l o c -> M e m o r y E r r o r
  14. s t d : : o u t _ o

    f _ r a n g e -> I n d e x E r r o r s t d : : i n v a l i d _ a r g u m e n t -> V a l u e E r r o r b o o s t : : b a d _ n u m e r i c _ c a s t -> O v e r f l o w E r r o r To provide better custom exception translation, you can use b o o s t : : p y t h o n : : r e g i s t e r _ e x c e p t i o n _ t r a n s l a t o r ( ) . Read about it in the Boost.Python documentation at http://www.boost.org/doc/libs/1_41_0/libs/python/doc/v2/exception_translator.html (http://www.boost.org/doc/libs/1_41_0/libs/python/doc/v2/exception_translator.html) Translating Python exceptions into C++ Boost.Python will translate all Python exceptions into a b o o s t : : p y t h o n : : e r r o r _ a l r e a d y _ s e t exception. You can catch these, interrogate the Python C API exception registers, and take the appropriate action. Handy debugging tip! Boost.Python always throws e r r o r _ a l r e a d y _ s e t exceptions using b o o s t : : p y t h o n : : t h r o w _ e r r o r _ a l r e a d y _ s e t ( ) . Setting a breakpoint here can be very useful when debugging exceptions. System validation Notebook setup For the C++ code examples in this notebook, we need to import some special "magic". I n [ 3 ] : % r e l o a d _ e x t b p _ m a g i c Plumbing test First we need to verify that your system can compile and link against Boost.Python. To do this, first compile and run p l u m b i n g _ t e s t . c p p : I n [ 4 ] : % % b p _ p r o g r a m p l u m b i n g _ t e s t
  15. # i n c l u d e < P

    y t h o n . h > # i n c l u d e < i o s t r e a m > # i n c l u d e < s t r i n g > # i n c l u d e < b o o s t / p y t h o n . h p p > n a m e s p a c e b p = b o o s t : : p y t h o n ; i n t m a i n ( i n t , c h a r * * ) { P y _ I n i t i a l i z e ( ) ; b p : : o b j e c t s y s = b p : : i m p o r t ( " s y s " ) ; b p : : o b j e c t v e r s i o n = s y s . a t t r ( " v e r s i o n " ) ; s t d : : c o u t < < b p : : e x t r a c t < s t d : : s t r i n g > ( v e r s i o n ) ( ) < < s t d : : e n d l ; r e t u r n 0 ; } If successful, this will print out the version of Python that you're Boost.Python is built against. Smoke-test module Once the plumbing test works, we need to ensure that you can build a basic Boost.Python-based extension module. Compile s m o k e _ t e s t . c p p into a properly named shared library: I n [ 5 ] : % % b p _ m o d u l e s m o k e _ t e s t # i n c l u d e < b o o s t / p y t h o n . h p p > i n t t e s t ( ) { r e t u r n 4 2 ; } B O O S T _ P Y T H O N _ M O D U L E ( s m o k e _ t e s t ) { b o o s t : : p y t h o n : : d e f ( " t e s t " , t e s t ) ; O u t [ 4 ] : " [ ' 2 . 7 . 6 ( d e f a u l t , M a r 1 0 2 0 1 4 , 2 1 : 5 0 : 2 7 ) ' , ' [ G C C 4 . 2 . 1 C o m p a t i b l e A p p l e L L V M 5 . 0 ( c l a n g - 5 0 0 . 2 . 7 9 ) ] ' ] "
  16. } and ensure that it can be imported into Python:

    I n [ 6 ] : i m p o r t s m o k e _ t e s t s m o k e _ t e s t . t e s t ( ) If you see 4 2 output from s m o k e _ t e s t . t e s t ( ) , then everything is probably working fine. Hello world! So let's try our hand and making a very simple module. We'll expose a simple g r e e t ( ) function in a module called h e l l o _ w o r l d , and then show how we can make that function progressively more complex. Before we can do that, we need to look at some basic Boost.Python API... The B O O S T _ P Y T H O N _ M O D U L E macro Python modules are required to expose a specific set of symbols, the naming of which are based upon the module name itself. These symbols refer to tables and structures telling Python how to make names to functions, classes, and so forth. Boost.Python goes to great lengths to make this much easier and more intuitive than the C API. A critical thing that you must do for every module is use the B O O S T _ P Y T H O N _ M O D U L E macro to generate the required boilerplate. Here's how an empty module looks: I n [ 7 ] : % % b p _ m o d u l e e m p t y _ m o d u l e # i n c l u d e < b o o s t / p y t h o n . h p p > B O O S T _ P Y T H O N _ M O D U L E ( e m p t y _ m o d u l e ) { / / m o d u l e - l e v e l s y m b o l s a r e d e f i n e d h e r e . } O u t [ 6 ] : 4 2
  17. If we import that into Python, we'll see that the

    module has no contents: I n [ 8 ] : i m p o r t e m p t y _ m o d u l e d i r ( e m p t y _ m o d u l e ) The B O O S T _ P Y T H O N _ M O D U L E macro: don't pass a quoted string First, don't pass the module name as a string in quotes. Don't do this: B O O S T _ P Y T H O N _ M O D U L E ( " m o d u l e _ n a m e " ) / / W R O N G ! but instead leave the quotes off: B O O S T _ P Y T H O N _ M O D U L E ( m o d u l e _ n a m e ) / / C o r r e c t The B O O S T _ P Y T H O N _ M O D U L E macro: module name must match library Second, make sure that the name you use for the module matches the library name you generate. So if you use: B O O S T _ P Y T H O N _ M O D U L E ( f o o b a r ) then make sure that your module is named "foobar". This means that your library will be named f o o b a r . s o (or f o o b a r . d l l or whatever.) Here's what happens when things don't match up. I n [ 1 0 ] : % % b p _ m o d u l e d i f f e r e n t _ n a m e s # i n c l u d e < b o o s t / p y t h o n . h p p > B O O S T _ P Y T H O N _ M O D U L E ( f o r s k j e l l i g e _ n a v n ) { } I n [ 1 1 ] : i m p o r t d i f f e r e n t _ n a m e s O u t [ 8 ] : [ ' _ _ d o c _ _ ' , ' _ _ f i l e _ _ ' , ' _ _ n a m e _ _ ' , ' _ _ p a c k a g e _ _ ' ]
  18. The module is executed at import time Like all Python

    modules, the modules created with Boost.Python are imported and executed dynamically. As such, they can execute initialization and setup code as needed, a technique that can be very useful. I n [ 1 2 ] : % % b p _ m o d u l e m o d u l e _ i n i t # i n c l u d e < f s t r e a m > # i n c l u d e < b o o s t / p y t h o n . h p p > v o i d i n i t i a l i z e ( ) { s t d : : o f s t r e a m o f s ( " m o d u l e _ i n i t . t x t " ) ; o f s < < " M o d u l e h a s b e e n i n i t i a l i z e d ! " ; } B O O S T _ P Y T H O N _ M O D U L E ( m o d u l e _ i n i t ) { i n i t i a l i z e ( ) ; } I n [ 1 3 ] : i m p o r t o s I N I T _ F I L E = ' m o d u l e _ i n i t . t x t ' # E n s u r e a n d v e r i f y t h a t t h e m o d u l e i n i t f i l e d o e s n ' t e x i s t i f o s . p a t h . e x i s t s ( I N I T _ F I L E ) : o s . r e m o v e ( I N I T _ F I L E ) a s s e r t n o t o s . p a t h . e x i s t s ( I N I T _ F I L E ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - I m p o r t E r r o r T r a c e b a c k ( m o s t r e c e n t c a l l l a s t ) < i p y t h o n - i n p u t - 1 1 - b c 6 7 6 b 9 9 e b 2 0 > i n < m o d u l e > ( ) - - - - > 1 i m p o r t d i f f e r e n t _ n a m e s I m p o r t E r r o r : d y n a m i c m o d u l e d o e s n o t d e f i n e i n i t f u n c t i o n ( i n i t d i f f e r e n t _ n a m e s )
  19. # I m p o r t t h e

    m o d u l e i m p o r t m o d u l e _ i n i t a s s e r t o s . p a t h . e x i s t s ( I N I T _ F I L E ) w i t h o p e n ( I N I T _ F I L E , ' r ' ) a s f : p r i n t ( f . r e a d ( ) ) Try it: Make a module named h e l l o _ w o r l d In the file h e l l o _ w o r l d . c p p you'll find a function g r e e t ( ) that we want to expose to Python. The first step, before we even expose a function, is to make an importable module. The module should be named h e l l o _ w o r l d . Remember: I n [ 1 7 ] : % % s n i p p e t # i n c l u d e < b o o s t / p y t h o n . h p p > B O O S T _ P Y T H O N _ M O D U L E ( m o d u l e - n a m e ) { } Exposing functions You expose functions using b o o s t : : p y t h o n : : d e f ( ) . Most commonly you only need to pass the Python name of the function followed by the C++ function name. d e f ( ) must be called inside the module macro block to be included in the module. Note how Boost.Python automatically determines the types involved in the function signature. You normally never need to tell it about return or argument types. I n [ 1 8 ] : % % s n i p p e t i n t c p p _ f u n c t i o n ( c o n s t s t d : : s t r i n g & x ) { . . . } B O O S T _ P Y T H O N _ M O D U L E ( m o d u l e _ n a m e ) { b o o s t : : p y t h o n : : d e f ( " p y t h o n _ n a m e " , c p p _ f u n c t i o n ) ; } M o d u l e h a s b e e n i n i t i a l i z e d !
  20. Try it: Expose the g r e e t (

    ) function to Python Update your h e l l o _ w o r l d . c p p to expose the greet function using d e f . Remember: I n [ 2 7 ] : % % s n i p p e t b o o s t : : p y t h o n : : d e f ( " p y t h o n _ n a m e " , c p p _ f u n c t i o n ) ; Try it: Add a n a m e parameter to g r e e t ( ) Update your g r e e t ( ) function to take a name argument and print that instead of "world". Remember: I n [ 2 8 ] : % % s n i p p e t v o i d g r e e t ( c o n s t s t d : : s t r i n g & n a m e ) { s t d : : c o u t < < " H e l l o " + n a m e + " ! " < < s t d : : e n d l ; } Try it: Return a string instead of printing it Now have g r e e t ( ) return its results rather than print them. I n [ 2 9 ] : % % s n i p p e t s t d : : s t r i n g g r e e t ( c o n s t s t d : : s t r i n g & n a m e ) { r e t u r n " H e l l o " + n a m e + " ! " ; } You can now do the printing in Python:
  21. I n [ 3 0 ] : % % s

    n i p p e t i m p o r t h e l l o _ w o r l d g = h e l l o _ w o r l d . g r e e t ( " W o r l d " ) p r i n t ( g ) Example project: Rock-Paper-Scissors We're going to use this as the basis for the rest of the course, exposing it to Python in stages to demonstrate the various techniques you can use for creating extension modules with Boost.Python. You'll find a C++ project in e x e r c i s e s / r p s / r p s . c p p . This project implements a simple rock-paper-scissors playing program where two "player" classes are pitted against one another for a certain number of rounds. Try it: Add the module definition to the RPS code Remember: I n [ 3 1 ] : % % s n i p p e t # i n c l u d e < b o o s t / p y t h o n . h p p > B O O S T _ P Y T H O N _ M O D U L E ( m o d u l e - n a m e ) { } Try it: Expose the t e s t ( ) function Remember: I n [ 3 2 ] : % % s n i p p e t b o o s t : : p y t h o n : : d e f ( " p y t h o n - n a m e " , c p p - f u n c t i o n ) ;
  22. Try it: Add a n u m _ r o

    u n d s argument to t e s t ( ) I n [ 3 3 ] : % % s n i p p e t s t d : : s t r i n g t e s t ( s t d : : v e c t o r < i n t > : : s i z e _ t y p e n u m _ r o u n d s ) { . . . s t d : : v e c t o r < i n t > r e s u l t s = p l a y ( p 1 , p 2 , n u m _ r o u n d s ) ; . . . } Try it: Add a default value for the n u m _ r o u n d s parameter to t e s t ( ) I n [ 3 4 ] : % % s n i p p e t s t d : : s t r i n g t e s t ( s t d : : v e c t o r < i n t > : : s i z e _ t y p e n u m _ r o u n d s = 1 0 0 ) . . . Test this in Python to see which "version" of the function you get. Default argument values Ultimately, there's no way for d e f to know anything about default values, so one strategy for exposing functions with default argument values is to create wrapper functions: I n [ 3 5 ] : % % s n i p p e t i n t f ( i n t x = 3 ) { r e t u r n x * 2 ; } i n t f 0 ( ) { r e t u r n f ( ) ; } B O O S T _ P Y T H O N _ M O D U L E ( f ) { b o o s t : : p y t h o n : : d e f ( " f " , f 0 ) ; b o o s t : : p y t h o n : : d e f ( " f " , f ) ;
  23. } Note how we use the same Python name f

    for both versions of the function. Boost.Python makes this work in Python. Also note that this same technique can be used for overloaded functions. Try it: Expose both versions of t e s t using manual wrapping Remember: I n [ 3 6 ] : % % s n i p p e t / / O r i g i n a l f u n c t i o n i n t x ( i n t y = 3 ) { . . . } / / w r a p p e r f u n c t i o n i n t x 0 ( ) { r e t u r n x ( ) ; } / / E x p o s e b o t h w i t h t h e s a m e p y t h o n n a m e b o o s t : : p y t h o n : : d e f ( " x " , x 0 ) ; b o o s t : : p y t h o n : : d e f ( " x " , x ) ; Default argument values: BOOST_PYTHON_FUNCTION_OVERLOADS This manual wrapping can be a bit tedious and mechanical, so recent versions of Boost.Python now include the B O O S T _ P Y T H O N _ F U N C T I O N _ O V E R L O A D S macro for automating this work. B O O S T _ P Y T H O N _ F U N C T I O N _ O V E R L O A D S takes the following arguments: The name of the overloads class to create The name of the function with default argument values The minimum number of arguments the function accepts The maximum number of arguments the function accepts So a simple volume calculator module looks something like this: I n [ 3 7 ] : % % b p _ m o d u l e v o l u m e _ d e f a u l t s
  24. # i n c l u d e < b

    o o s t / p y t h o n . h p p > d o u b l e v o l u m e ( d o u b l e a , d o u b l e b , d o u b l e c , d o u b l e d = 1 , d o u b l e e = 1 ) { r e t u r n a * b * c * d * e ; } B O O S T _ P Y T H O N _ F U N C T I O N _ O V E R L O A D S ( v o l u m e _ o v e r l o a d s , v o l u m e , 3 , 5 ) ; B O O S T _ P Y T H O N _ M O D U L E ( v o l u m e _ d e f a u l t s ) { / / N o t i c e t h a t d e f ( ) t a k e s t h r e e a r g u m e n t s h e r e . b o o s t : : p y t h o n : : d e f ( " v o l u m e " , v o l u m e , v o l u m e _ o v e r l o a d s ( ) ) ; } I n [ 3 8 ] : f r o m v o l u m e _ d e f a u l t s i m p o r t v o l u m e p r i n t ( v o l u m e ( 1 , 1 , 2 ) ) p r i n t ( v o l u m e ( 3 , 5 , 8 , 1 3 ) ) p r i n t ( v o l u m e ( 2 1 , 3 4 , 5 5 , 8 9 , 1 4 4 ) ) And of course the Python function requires a minimum number of arguments: I n [ 3 9 ] : p r i n t ( v o l u m e ( 3 , 5 ) ) 2 . 0 1 5 6 0 . 0 5 0 3 2 8 4 3 2 0 . 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A r g u m e n t E r r o r T r a c e b a c k ( m o s t r e c e n t c a l l l a s t ) < i p y t h o n - i n p u t - 3 9 - 3 0 5 4 8 8 c 4 9 0 6 f > i n < m o d u l e > ( ) - - - - > 1 p r i n t ( v o l u m e ( 3 , 5 ) ) A r g u m e n t E r r o r : P y t h o n a r g u m e n t t y p e s i n v o l u m e _ d e f a u l t s . v o l u m e ( i n t , i n t ) d i d n o t m a t c h C + + s i g n a t u r e : v o l u m e ( d o u b l e , d o u b l e , d o u b l e ) v o l u m e ( d o u b l e , d o u b l e , d o u b l e , d o u b l e ) v o l u m e ( d o u b l e , d o u b l e , d o u b l e , d o u b l e , d o u b l e )
  25. and a maximum: I n [ 4 0 ] :

    p r i n t ( v o l u m e ( 1 , 2 , 3 , 4 , 5 , 6 ) ) Note that this technique does not work for overloaded functions. Try it: Expose t e s t ( ) using B O O S T _ P Y T H O N _ F U N C T I O N _ O V E R L O A D S Remember: I n [ 4 1 ] : % % s n i p p e t B O O S T _ P Y T H O N _ F U N C T I O N _ O V E R L O A D S ( o v e r l o a d s - n a m e , f u n c t i o n - n a m e , m i n - a r g s , m a x - a r g s ) ; b o o s t : : p y t h o n : : d e f ( " p y t h o n - n a m e " , f u n c t i o n - n a m e , o v e r l o a d s - n a m e ( ) ) ; Exposing classes Just like we use the d e f function to expose functions to Python, Boost.Python provides the c l a s s _ < > template for exposing classes. The basic form for wrapping a class is c l a s s _ < C l a s s T o B e W r a p p e d > ( " P y t h o n C l a s s N a m e " ) . For example: I n [ 4 2 ] : % % b p _ m o d u l e m a t r i x 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A r g u m e n t E r r o r T r a c e b a c k ( m o s t r e c e n t c a l l l a s t ) < i p y t h o n - i n p u t - 4 0 - b 8 b 8 f 1 2 e c 7 7 c > i n < m o d u l e > ( ) - - - - > 1 p r i n t ( v o l u m e ( 1 , 2 , 3 , 4 , 5 , 6 ) ) A r g u m e n t E r r o r : P y t h o n a r g u m e n t t y p e s i n v o l u m e _ d e f a u l t s . v o l u m e ( i n t , i n t , i n t , i n t , i n t , i n t ) d i d n o t m a t c h C + + s i g n a t u r e : v o l u m e ( d o u b l e , d o u b l e , d o u b l e ) v o l u m e ( d o u b l e , d o u b l e , d o u b l e , d o u b l e ) v o l u m e ( d o u b l e , d o u b l e , d o u b l e , d o u b l e , d o u b l e )
  26. # i n c l u d e < b

    o o s t / p y t h o n . h p p > n a m e s p a c e m a t r i x 1 { c l a s s M a t r i x { } ; } B O O S T _ P Y T H O N _ M O D U L E ( m a t r i x 1 ) { u s i n g n a m e s p a c e m a t r i x 1 ; b o o s t : : p y t h o n : : c l a s s _ < M a t r i x > ( " M a t r i x " ) ; } Note on implementation details: The reason we need to use different names and namespaces - e.g. "matrix1" - throughout these example is because of how IPython, Python, and Boost.Python all interact. Don't get confused by these details. What's important to focus on is the evolution of the M a t r i x class and the module definition. With this, you can import your module and create new M a t r i x objects in Python: I n [ 4 3 ] : f r o m m a t r i x 1 i m p o r t M a t r i x p r i n t ( M a t r i x ) m = M a t r i x ( ) p r i n t ( t y p e ( m ) ) p r i n t ( d i r ( m ) ) < c l a s s ' m a t r i x 1 . M a t r i x ' > < c l a s s ' m a t r i x 1 . M a t r i x ' > [ ' _ _ c l a s s _ _ ' , ' _ _ d e l a t t r _ _ ' , ' _ _ d i c t _ _ ' , ' _ _ d o c _ _ ' , ' _ _ f o r m a t _ _ ' , ' _ _ g e t a t t r i b u t e _ _ ' , ' _ _ h a s h _ _ ' , ' _ _ i n i t _ _ ' , ' _ _ i n s t a n c e _ s i z e _ _ ' , ' _ _ m o d u l e _ _ ' , ' _ _ n e w _ _ ' , ' _ _ r e d u c e _ _ ' , ' _ _ r e d u c e _ e x _ _ ' , ' _ _ r e p r _ _ ' , ' _ _ s e t a t t r _ _ ' , ' _ _ s i z e o f _ _ ' , ' _ _ s t r _ _ ' , ' _ _ s u b c l a s s h o o k _ _ ' , ' _ _ w e a k r e f _ _ ' ]
  27. Member functions Exposing empty classes is not that interesting, and

    of course Boost.Python provides a way to expose member functions. Like normal functions, you use a function called d e f ( ) to expose member functions. This version of d e f ( ) is a member of the value returned by c l a s s _ < > . First let's update our class with some member functions for accessing the size of the M a t r i x : I n [ 4 4 ] : % % b p _ m o d u l e m a t r i x 2 # i n c l u d e < b o o s t / p y t h o n . h p p > n a m e s p a c e m a t r i x 2 { c l a s s M a t r i x { p u b l i c : M a t r i x ( ) : s i z e _ x _ ( 0 ) , s i z e _ y _ ( 0 ) { } u n s i g n e d i n t s i z e _ x ( ) c o n s t { r e t u r n s i z e _ x _ ; } u n s i g n e d i n t s i z e _ y ( ) c o n s t { r e t u r n s i z e _ y _ ; } p r i v a t e : u n s i g n e d i n t s i z e _ x _ ; u n s i g n e d i n t s i z e _ y _ ; } ; } B O O S T _ P Y T H O N _ M O D U L E ( m a t r i x 2 ) { u s i n g n a m e s p a c e m a t r i x 2 ; b o o s t : : p y t h o n : : c l a s s _ < M a t r i x > ( " M a t r i x " ) . d e f ( " s i z e _ x " , & M a t r i x : : s i z e _ x ) . d e f ( " s i z e _ y " , & M a t r i x : : s i z e _ y ) ; } It's still not much of a matrix implementation, but it's getting there. We can see that it works in Python:
  28. I n [ 4 5 ] : f r o

    m m a t r i x 2 i m p o r t M a t r i x m = M a t r i x ( ) p r i n t ( m . s i z e _ x ( ) ) p r i n t ( m . s i z e _ y ( ) ) Constructors An obvious shortcoming of our Matrix implementation so far is that you can only have zero-sized matrices. To allow users to create matrices with positive size, we need to add a constructor to the Matrix class. With Boost.Python you use the i n i t < > class to define constructors. i n i t < > takes any number (really, up to a practical limit) of template arguments specifying the types of arguments to the constructor. For your class's first constructor, you pass the i n i t < > as the second argument of the c l a s s _ < > constructor. For any subsequent constructors, you can call d e f ( ) with i n i t < > instances. Let's add some proper constructors to our Matrix to see how this works: I n [ 4 6 ] : % % b p _ m o d u l e m a t r i x 3 # i n c l u d e < b o o s t / p y t h o n . h p p > n a m e s p a c e m a t r i x 3 { c l a s s M a t r i x { p u b l i c : M a t r i x ( c o n s t M a t r i x & m ) : s i z e _ x _ ( m . s i z e _ x ( ) ) , s i z e _ y _ ( m . s i z e _ y ( ) ) { } M a t r i x ( u n s i g n e d i n t x , u n s i g n e d i n t y ) : s i z e _ x _ ( x ) , s i z e _ y _ ( y ) { } u n s i g n e d i n t s i z e _ x ( ) c o n s t { r e t u r n s i z e _ x _ ; } u n s i g n e d i n t s i z e _ y ( ) c o n s t { r e t u r n s i z e _ y _ ; } p r i v a t e : 0 0
  29. u n s i g n e d i n

    t s i z e _ x _ ; u n s i g n e d i n t s i z e _ y _ ; } ; } B O O S T _ P Y T H O N _ M O D U L E ( m a t r i x 3 ) { u s i n g n a m e s p a c e m a t r i x 3 ; n a m e s p a c e b p = b o o s t : : p y t h o n ; b o o s t : : p y t h o n : : c l a s s _ < M a t r i x > ( " M a t r i x " , b p : : i n i t < u n s i g n e d i n t , u n s i g n e d i n t > ( ) ) . d e f ( b p : : i n i t < c o n s t M a t r i x & > ( ) ) . d e f ( " s i z e _ x " , & M a t r i x : : s i z e _ x ) . d e f ( " s i z e _ y " , & M a t r i x : : s i z e _ y ) ; } We can see that the two-argument constructor works as expected: I n [ 4 7 ] : f r o m m a t r i x 3 i m p o r t M a t r i x m = M a t r i x ( 1 2 3 , 4 5 6 ) p r i n t ( m . s i z e _ x ( ) ) p r i n t ( m . s i z e _ y ( ) ) and that the copy constructor version works as well: I n [ 4 8 ] : m 2 = M a t r i x ( m ) p r i n t ( m 2 . s i z e _ x ( ) , m 2 . s i z e _ y ( ) ) Try it: Expose the R a n d o m class with its n a m e ( ) and s e t N a m e ( ) functions 1 2 3 4 5 6 ( 1 2 3 , 4 5 6 )
  30. Use the b o o s t : : p

    y t h o n : : c l a s s _ < > template to expose the R a n d o m class. Use the d e f ( ) member of c l a s s _ < > to expose R a n d o m . n a m e ( ) and R a n d o m . s e t N a m e ( ) . Remember: I n [ 4 9 ] : % % s n i p p e t b o o s t : : p y t h o n : : c l a s s _ < C + + - c l a s s > ( " P y t h o n c l a s s n a m e " , b o o s t : : p y t h o n : : i n i t < c o n s t r u c t o r - a r g s , . . . > ( ) ) . d e f ( " p y t h o n - n a m e " , m e m b e r - f u n c t i o n - p o i n t e r ) ; Properties Let's look at how we can make our Python APIs a bit more Pythonic with properties. Properties don't have a real analogue in C++, but Boost.Python provides the a d d _ p r o p e r t y ( ) function for adding properties which are backed by C++ functions. a d d _ p r o p e r t y ( ) takes the property's names as its first argument. The second argument is the function which gets the property value, and the optional third argument is the setter function. Consider a simple simple E n t i t y class with a mutable name and immutable id: I n [ 5 0 ] : % % b p _ m o d u l e p r o p e r t i e s # i n c l u d e < b o o s t / p y t h o n . h p p > # i n c l u d e < s t r i n g > n a m e s p a c e p r o p e r t i e s { c l a s s E n t i t y { p u b l i c : E n t i t y ( i n t i d , c o n s t s t d : : s t r i n g & n a m e ) : i d _ ( i d ) , n a m e _ ( n a m e ) { } i n t i d ( ) c o n s t { r e t u r n i d _ ; } s t d : : s t r i n g n a m e ( ) c o n s t { r e t u r n n a m e _ ; } v o i d s e t _ n a m e ( c o n s t s t d : : s t r i n g & n a m e ) { n a m e _ = n a m e ; }
  31. p r i v a t e : c o

    n s t i n t i d _ ; s t d : : s t r i n g n a m e _ ; } ; } B O O S T _ P Y T H O N _ M O D U L E ( p r o p e r t i e s ) { u s i n g n a m e s p a c e p r o p e r t i e s ; n a m e s p a c e b p = b o o s t : : p y t h o n ; b p : : c l a s s _ < E n t i t y > ( " E n t i t y " , b p : : i n i t < i n t , c o n s t s t d : : s t r i n g & > ( ) ) . a d d _ p r o p e r t y ( " i d " , & E n t i t y : : i d ) . a d d _ p r o p e r t y ( " n a m e " , & E n t i t y : : n a m e , & E n t i t y : : s e t _ n a m e ) ; } Here's how you can access n a m e and i d as properties in Python: I n [ 5 1 ] : f r o m p r o p e r t i e s i m p o r t E n t i t y i = E n t i t y ( 9 6 , " J o h n n y A l l e n " ) p r i n t ( i . i d , i . n a m e ) I n [ 5 2 ] : i . n a m e = " J a m e s M a r s h a l l " p r i n t ( i . i d , i . n a m e ) I n [ 5 3 ] : i . i d = 1 3 ( 9 6 , ' J o h n n y A l l e n ' ) ( 9 6 , ' J a m e s M a r s h a l l ' ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A t t r i b u t e E r r o r T r a c e b a c k ( m o s t r e c e n t c a l l l a s t ) < i p y t h o n - i n p u t - 5 3 - 4 0 0 e 2 8 6 b 8 3 a 0 > i n < m o d u l e > ( )
  32. Try it: Replace n a m e ( ) with

    a read-only property Use a read-only property to replace the n a m e ( ) function. Leave s e t N a m e ( ) alone for now. Remember: I n [ 5 4 ] : % % s n i p p e t c l a s s < . . . > ( . . . ) . a d d _ p r o p e r t y ( " p r o p e r t y - n a m e " , g e t t e r - f u n c t i o n ) ; Try it: Replace s e t N a m e ( ) with a read-write property Now fold s e t N a m e ( ) into the n a m e property as well. Remember: I n [ 5 5 ] : % % s n i p p e t c l a s s < . . . > ( . . . ) . a d d _ p r o p e r t y ( " p r o p e r t y - n a m e " , g e t t e r - f u n c t i o n , s e t t e r - f u n c t i o n ) ; Inheritance Boost.Python allows you specify inheritance relationships between your C++ classes when exposing them to Python. When you do this, the Python classes for derived C++ classes get all of the base Python class's attributes. You express inheritance in Boost.Python by passing a b a s e s < > template instantiation as the second template parameter to the c l a s s _ < > template, like this: - - - - > 1 i . i d = 1 3 A t t r i b u t e E r r o r : c a n ' t s e t a t t r i b u t e
  33. c l a s s _ < S o m

    e C l a s s , b a s e s < B a s e C l a s s > > ( . . . ) ; To demonstrate this, let's derive our M a t r i x class from E n t i t y (with some modification for readability): I n [ 5 6 ] : % % b p _ m o d u l e m a t r i x 4 # i n c l u d e < b o o s t / p y t h o n . h p p > # i n c l u d e < s t r i n g > n a m e s p a c e m a t r i x 4 { s t r u c t E n t i t y { E n t i t y ( c o n s t i n t i d , c o n s t s t d : : s t r i n g & n a m e ) : i d _ ( i d ) , n a m e _ ( n a m e ) { } c o n s t i n t i d _ ; s t d : : s t r i n g n a m e _ ; } ; c l a s s M a t r i x : p u b l i c E n t i t y { p u b l i c : M a t r i x ( u n s i g n e d i n t x , u n s i g n e d i n t y , i n t i d , c o n s t s t d : : s t r i n g & n a m e ) : E n t i t y ( i d , n a m e ) , s i z e _ x _ ( x ) , s i z e _ y _ ( y ) { } u n s i g n e d i n t s i z e _ x ( ) c o n s t { r e t u r n s i z e _ x _ ; } u n s i g n e d i n t s i z e _ y ( ) c o n s t { r e t u r n s i z e _ y _ ; } p r i v a t e : u n s i g n e d i n t s i z e _ x _ ; u n s i g n e d i n t s i z e _ y _ ; } ; } B O O S T _ P Y T H O N _ M O D U L E ( m a t r i x 4 ) { u s i n g n a m e s p a c e m a t r i x 4 ;
  34. n a m e s p a c e b

    p = b o o s t : : p y t h o n ; b p : : c l a s s _ < E n t i t y > ( " E n t i t y " , b p : : i n i t < i n t , c o n s t s t d : : s t r i n g & > ( ) ) . d e f _ r e a d o n l y ( " i d " , & E n t i t y : : i d _ ) . d e f _ r e a d w r i t e ( " n a m e " , & E n t i t y : : n a m e _ ) ; b p : : c l a s s _ < M a t r i x , b p : : b a s e s < E n t i t y > > ( " M a t r i x " , b p : : i n i t < u n s i g n e d i n t , u n s i g n e d i n t , i n t , c o n s t s t d : : s t r i n g & > ( ) ) . d e f ( " s i z e _ x " , & M a t r i x : : s i z e _ x ) . d e f ( " s i z e _ y " , & M a t r i x : : s i z e _ y ) ; } Now if we create a Matrix in Python, we can see that it has all of the attribute of an Entity: I n [ 5 7 ] : f r o m m a t r i x 4 i m p o r t M a t r i x m = M a t r i x ( 1 0 0 , 2 0 0 , 5 , " i m p o r t a n t d a t a " ) p r i n t ( m . s i z e _ x ( ) , m . s i z e _ y ( ) ) p r i n t ( m . i d , m . n a m e ) Try it: Expose P l a y e r and use it as a base class for R a n d o m and T i t F o r T a t Expose the P l a y e r base class using c l a s s _ < > . Note that since P l a y e r is abstract, you must pass n o n c o p y a b l e to the c l a s s _ < > template and n o _ i n i t as the constructor. Remember: I n [ 5 8 ] : % % s n i p p e t ( 1 0 0 , 2 0 0 ) ( 5 , ' i m p o r t a n t d a t a ' )
  35. b p : : c l a s s _

    < a b s t r a c t - c l a s s , b o o s t : : n o n c o p y a b l e > ( " P y t h o n N a m e " , b p : : n o _ i n i t ) . . . ; b p : : c l a s s _ < c l a s s - n a m e , b p : : b a s e s < b a s e - c l a s s , . . . > > ( " P y t h o n N a m e " , . . . ) . . . ; Enums Boost.Python provides a way to expose C++ e n u m s in Python even though Python doesn't have a native notion of e n u m s. As you might have guessed, you do this by using the e n u m _ < > template. This takes the C++ enum as its argument, and it allows you to add Python attributes which map to constant values in C++. Here's a simple example of this looks: I n [ 5 9 ] : % % b p _ m o d u l e e n u m s # i n c l u d e < b o o s t / p y t h o n . h p p > / / T h e o b v i o u s c h o i c e s f o r p r o j e c t i o n s . . . e n u m P r o j e c t i o n { M e r c a t o r , D y m a x i o n , S i n u s o i d a l } ; B O O S T _ P Y T H O N _ M O D U L E ( e n u m s ) { b o o s t : : p y t h o n : : e n u m _ < P r o j e c t i o n > ( " P r o j e c t i o n " ) . v a l u e ( " M e r c a t o r " , M e r c a t o r ) . v a l u e ( " D y m a x i o n " , D y m a x i o n ) . v a l u e ( " S i n u s o i d a l " , S i n u s o i d a l ) ; }
  36. I n [ 6 0 ] : i m p

    o r t e n u m s p r i n t ( ' C l a s s : ' , e n u m s . P r o j e c t i o n ) p r i n t ( ' n a m e s : ' , e n u m s . P r o j e c t i o n . n a m e s ) p r i n t ( ' M e r c a t o r : ' , e n u m s . P r o j e c t i o n . M e r c a t o r ) p r i n t ( ' M e r c a t o r = = 0 ? ' , e n u m s . P r o j e c t i o n . M e r c a t o r = = 0 ) p r i n t ( ' D y m a x i o n v a l u e : ' , i n t ( e n u m s . P r o j e c t i o n . D y m a x i o n ) ) Try it: Expose the M o v e enum Expose the M o v e enum along with the s c o r e ( M o v e , M o v e ) function. Remember: I n [ 6 1 ] : % % s n i p p e t b o o s t : : p y t h o n : : e n u m _ < c p p - e n u m > ( " E n u m N a m e " ) . v a l u e ( " p y t h o n - n a m e " , c + + - v a l u e ) . . . ; b o o s t : : p y t h o n : : d e f ( " p y t h o n - n a m e " , f u n c t i o n ) ; Derived object types Boost.Python provides a number of b o o s t : : p y t h o n : : o b j e c t subclasses which we can use in C++ for interacting with standard Python types like l i s t , d i c t , and t u p l e . ( ' C l a s s : ' , < c l a s s ' e n u m s . P r o j e c t i o n ' > ) ( ' n a m e s : ' , { ' D y m a x i o n ' : e n u m s . P r o j e c t i o n . D y m a x i o n , ' S i n u s o i d a l ' : e n u m s . P r o j e c t i o n . S i n u s o i d a l , ' M e r c a t o r ' : e n u m s . P r o j e c t i o n . M e r c a t o r } ) ( ' M e r c a t o r : ' , e n u m s . P r o j e c t i o n . M e r c a t o r ) ( ' M e r c a t o r = = 0 ? ' , T r u e ) ( ' D y m a x i o n v a l u e : ' , 1 )
  37. These can be useful if you want to accept standard

    Python types in your C++ code, or if you want to pass them back from C++ functions. I n [ 6 2 ] : % % b p _ m o d u l e l i s t _ e x a m p l e # i n c l u d e < b o o s t / p y t h o n . h p p > n a m e s p a c e b p = b o o s t : : p y t h o n ; i n t e x p e n s i v e _ c o m p u t a t i o n ( i n t x , i n t f a c t o r ) { / / T h i s i s a f a k e e x p e n s i v e c o m p u t a t i o n r e t u r n x * f a c t o r ; } b p : : l i s t r u n _ c a l c u l a t i o n ( b p : : l i s t v a l u e s , i n t f a c t o r ) { b p : : l i s t r s l t ; f o r ( i n t i d x = 0 ; i d x < b p : : l e n ( v a l u e s ) ; + + i d x ) { i n t v a l = b p : : e x t r a c t < i n t > ( v a l u e s [ i d x ] ) ; r s l t . a p p e n d ( b p : : m a k e _ t u p l e ( v a l , e x p e n s i v e _ c o m p u t a t i o n ( v a l , f a c t o r ) ) ) ; } r e t u r n r s l t ; } B O O S T _ P Y T H O N _ M O D U L E ( l i s t _ e x a m p l e ) { b p : : d e f ( " r u n _ c a l c u l a t i o n " , & r u n _ c a l c u l a t i o n ) ; } I n [ 6 3 ] : f r o m l i s t _ e x a m p l e i m p o r t r u n _ c a l c u l a t i o n d a t a = l i s t ( r a n g e ( 1 0 ) ) r u n _ c a l c u l a t i o n ( d a t a , 3 ) O u t [ 6 3 ] : [ ( 0 , 0 ) , ( 1 , 3 ) , ( 2 , 6 ) , ( 3 , 9 ) ,
  38. Full details on Boost.Python's derived types can be found in

    the documentation (http://www.boost.org/doc/libs/1_55_0/libs/python/doc/tutorial/doc/html/python/object.html#python.derived_object_types). Try it: Expose p l a y ( ) using a b o o s t : : p y t h o n : : l i s t The p l a y ( ) function returns a s t d : : v e c t o r which we can't use directly in Python. You can wrap p l a y ( ) in a function called p y _ p l a y ( ) and have it return a b o o s t : : p y t h o n : : l i s t rather than a vector. Remember: I n [ 6 4 ] : % % s n i p p e t b o o s t : : p y t h o n : : d e f ( " p y t h o n - n a m e " , f u n c t i o n ) ; b o o s t : : p y t h o n : : l i s t l ; l . a p p e n d ( s o m e - v a l u e ) ; Data members Boost.Python also allows you to expose C++ data members. Use the d e f _ r e a d o n l y and d e f _ r e a d w r i t e functions of c l a s s _ < > to add read-only and read-write data members, respectively. To demonstrate this, let's modify our E n t i t y class to directly expose its data: I n [ 6 5 ] : % % b p _ m o d u l e d a t a _ m e m b e r s # i n c l u d e < b o o s t / p y t h o n . h p p > # i n c l u d e < s t r i n g > ( 4 , 1 2 ) , ( 5 , 1 5 ) , ( 6 , 1 8 ) , ( 7 , 2 1 ) , ( 8 , 2 4 ) , ( 9 , 2 7 ) ]
  39. n a m e s p a c e d

    a t a _ m e m b e r s { s t r u c t E n t i t y { E n t i t y ( c o n s t i n t i d , c o n s t s t d : : s t r i n g & n a m e ) : i d _ ( i d ) , n a m e _ ( n a m e ) { } c o n s t i n t i d _ ; s t d : : s t r i n g n a m e _ ; } ; } B O O S T _ P Y T H O N _ M O D U L E ( d a t a _ m e m b e r s ) { u s i n g n a m e s p a c e d a t a _ m e m b e r s ; n a m e s p a c e b p = b o o s t : : p y t h o n ; b p : : c l a s s _ < E n t i t y > ( " E n t i t y " , b p : : i n i t < i n t , c o n s t s t d : : s t r i n g & > ( ) ) . d e f _ r e a d o n l y ( " i d " , & E n t i t y : : i d _ ) . d e f _ r e a d w r i t e ( " n a m e " , & E n t i t y : : n a m e _ ) ; } In Python we can see that these data members are exposed as we expect: I n [ 6 6 ] : f r o m d a t a _ m e m b e r s i m p o r t E n t i t y i = E n t i t y ( 4 , " F r e d " ) p r i n t ( i . i d , i . n a m e ) We can edit the n a m e field: I n [ 6 7 ] : i . n a m e = " F r e d r i c k " p r i n t ( i . i d , i . n a m e ) ( 4 , ' F r e d ' ) ( 4 , ' F r e d r i c k ' )
  40. but not the id: I n [ 6 8 ]

    : i . i d = 1 2 3 4 Try it: Expose R o u n d with immutable data members Expose the R o u n d struct using c l a s s _ < > , exposing its members as read-only data members. Remember: I n [ 6 9 ] : % % s n i p p e t b o o s t : : p y t h o n : : c l a s s _ < c l a s s - n a m e > ( " P y t h o n N a m e " , b o o s t : : p y t h o n : : i n i t < a r g , . . . > ) . d e f _ r e a d o n l y ( " a t t r - n a m e " , d a t a - m e m b e r ) ; Try it: Create a P l a y e r subclass that delegates to a b o o s t : : p y t h o n : : o b j e c t Create a P l a y e r subclass in C++ which delegates its n e x t M o v e functionality to a Python object. Remember: b o o s t : : p y t h o n : : o b j e c t is a C++ type that can refer to any Python object s t d : : v e c t o r can't be passed across the C++/Python boundary, but boost::python::list can I n [ 7 0 ] : % % s n i p p e t / / o b j e c t s c a n b e c o n s t r u c t e d f r o m a n y t y p e b o o s t : : p y t h o n : : o b j e c t o b j ( < a n y t y p e > ) ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A t t r i b u t e E r r o r T r a c e b a c k ( m o s t r e c e n t c a l l l a s t ) < i p y t h o n - i n p u t - 6 8 - f a b c 4 c 2 f a 4 0 1 > i n < m o d u l e > ( ) - - - - > 1 i . i d = 1 2 3 4 A t t r i b u t e E r r o r : c a n ' t s e t a t t r i b u t e
  41. / / a t t r i b u t

    e a c c e s s i s d o n e v i a t h e ` a t t r ( ) ` m e t h o d b o o s t : : p y t h o n : o b j e c t r e s u l t = o b j . a t t r ( " a t t r _ n a m e " ) ; / / o b j e c t s a r e c a l l a b l e , t h o u g h t h e u n d e r l y i n g P y t h o n o b j e c t m a y n o t b e b o o s t : : p y t h o n : : o b j e c t r 2 = r e s u l t ( ) ; / / A p p e n d t o b o o s t : : p y t h o n : : l i s t w i t h a p p e n d ( ) b o o s t : : p y t h o n : : l i s t l ; l . a p p e n d ( " s o m e v a l u e " ) ; Type Conversion Often you can rely on the type-conversion support created automatically by Boost.Python. For example, when you use the c l a s s _ < > template, Boost.Python ensures that your wrapped C++ type can cross the Python/C++ boundary without any extra work. There are times, however, when you might need to create explicit converters for your types. These converters are invoked when: you construct an b p : : o b j e c t from a C++ object you use b p : : e x t r a c t < > to convert a Python object into C++ To create a converter, you need to implement three functions: A function to convert from your C++ type into a P y O b j e c t A function to check if a Python object can be converted into your C++ type A function to convert a Python object into your C++ type Likewise, you need to register these converters with the Boost.Python runtime. Let's see how all of that looks. A simple C o o r d i n a t e class We will use this simple C o o r d i n a t e class to demonstrate conversion. We want to be able to convert this class to and from Python tuples. Specifically, we want to convert to and from Python tuples of size 2 containing numeric values. Here's the class itself:
  42. I n [ 7 1 ] : % % s

    n i p p e t s t r u c t C o o r d i n a t e { C o o r d i n a t e ( l o n g x i n , l o n g y i n ) : x ( x i n ) , y ( y i n ) { } l o n g x ; l o n g y ; } ; A to-python converter A to-python converter is a function that takes your C++ type as an argument and returns a new P y O b j e c t . This function must be a static function named c o n v e r t on a struct or class. A to-python converter for our C o o r d i n a t e looks like this: I n [ 7 2 ] : % % s n i p p e t s t r u c t C o o r d i n a t e _ t o _ t u p l e { s t a t i c P y O b j e c t * c o n v e r t ( c o n s t C o o r d i n a t e & c ) { r e t u r n b p : : i n c r e f < > ( b p : : m a k e _ t u p l e ( c . x , c . y ) . p t r ( ) ) ; } } ; Notice how we create a Boost.Python managed tuple, extract it's P y O b j e c t pointer, and i n c r e f that pointer before returning it. We do this i n c r e f because otherwise Python will garbage collect the tuple object after the Boost.Python objects in this function destruct. To register this converter with Boost.Python, use b p : : t o _ p y t h o n _ c o n v e r t e r like this: I n [ 7 3 ] : % % s n i p p e t b p : : t o _ p y t h o n _ c o n v e r t e r < C o o r d i n a t e , C o o r d i n a t e _ t o _ t u p l e > ( ) ; Testing for from-python convertibility
  43. The next piece of functionality we need is a function

    to test if a P y O b j e c t can be converted into our C++ type. This will be invoked when b p : : e x t r a c t < > used to create a C++ object from a Python object. A convertibility test function must accept a P y O b j e c t * as its only argument. It should return 0 if the P y O b j e c t can not be converted to the requested C++ type. Otherwise it should return the P y O b j e c t * argument. A convertbility tester for our C o o r d i n a t e looks like this: I n [ 7 4 ] : % % s n i p p e t s t a t i c v o i d * c o n v e r t i b l e ( P y O b j e c t * o b j _ p t r ) { i f ( ! P y T u p l e _ C h e c k ( o b j _ p t r ) ) r e t u r n 0 ; i f ( P y T u p l e _ S i z e ( o b j _ p t r ) ! = 2 ) r e t u r n 0 ; i f ( ! P y N u m b e r _ C h e c k ( P y T u p l e _ G e t I t e m ( o b j _ p t r , 0 ) ) ) r e t u r n 0 ; i f ( ! P y N u m b e r _ C h e c k ( P y T u p l e _ G e t I t e m ( o b j _ p t r , 1 ) ) ) r e t u r n 0 ; r e t u r n o b j _ p t r ; } Converting from Python to C++ The final piece of conversion machinery is the from-python converter function which takes the P y O b j e c t and converts it into a C++ object. This ultimately involves performing an in-place construction using memory provided to you by the Boost.Python runtime. The details are a bit complex, but fortunately it's not necessary to understand them deeply. Instead, you can simply follow this example and replace the types as needed in your own code: I n [ 7 5 ] : % % s n i p p e t s t a t i c v o i d c o n s t r u c t ( P y O b j e c t * o b j _ p t r , b p : : c o n v e r t e r : : r v a l u e _ f r o m _ p y t h o n _ s t a g e 1 _ d a t a * d a t a ) { / / E x t r a c t i n t e g r a l v a l u e s f r o m t u p l e l o n g x _ c o o r d = P y L o n g _ A s L o n g ( P y N u m b e r _ L o n g ( P y T u p l e _ G e t I t e m ( o b j _ p t r , 0 ) ) ) ; l o n g y _ c o o r d = P y L o n g _ A s L o n g ( P y N u m b e r _ L o n g ( P y T u p l e _ G e t I t e m ( o b j _ p t r , 1 ) ) ) ; / / G r a b p o i n t e r t o m e m o r y i n t o w h i c h t o c o n s t r u c t t h e n e w C o o r d i n a t e
  44. v o i d * s t o r a

    g e = ( ( b p : : c o n v e r t e r : : r v a l u e _ f r o m _ p y t h o n _ s t o r a g e < C o o r d i n a t e > * ) d a t a ) - > s t o r a g e . b y t e s ; / / i n - p l a c e c o n s t r u c t t h e n e w C o o r d i n a t e u s i n g t h e n u m b e r s / / e x t r a c t e d f r o m t h e p y t h o n o b j e c t n e w ( s t o r a g e ) C o o r d i n a t e ( x _ c o o r d , y _ c o o r d ) ; / / S t a s h t h e m e m o r y c h u n k p o i n t e r f o r l a t e r u s e b y b o o s t . p y t h o n d a t a - > c o n v e r t i b l e = s t o r a g e ; } Registering from-python converters To register your from-python converters with Boost.Python, use b p : : c o n v e r t e r : : r e g i s t r y : : p u s h _ b a c k with your functions and the type-id of your C++ class. That looks like this: I n [ 7 6 ] : % % s n i p p e t b p : : c o n v e r t e r : : r e g i s t r y : : p u s h _ b a c k ( & c o n v e r t i b l e , & c o n s t r u c t , b p : : t y p e _ i d < C o o r d i n a t e > ( ) ) ; The full code for this example can be found in this gist (https://gist.github.com/anonymous/9687369). Try it: Write a to-python converter for R o u n d We'd like R o u n d to be represented in Python as a simple tuple. The first thing we need to do is implement and register a to-python converter. Remember: I n [ 7 7 ] : % % s n i p p e t s t r u c t A n y _ n a m e
  45. { s t a t i c P y O

    b j e c t * c o n v e r t ( c o n s t c l a s s - t o - c o n v e r t & c ) { / / c o d e t o c o n v e r t c t o P y O b j e c t * r e t u r n b p : : i n c r e f < > ( b p : : m a k e _ t u p l e ( c . x , c . y ) . p t r ( ) ) ; } } ; / / C r e a t e s a n e w P y t h o n t u p l e b o o s t : : p y t h o n : : m a k e _ t u p l e ( a r g 1 , . . . ) / / r e t u r n s t h e P y O b j e c t * h e l d b y a b o o s t : : p y t h o n : : o b j e c t b o o s t : : p y t h o n : : o b j e c t : : p t r ( ) / / I n c r e m e n t s t h e r e f - c o u n t o n a P y O b j e c t * b o o s t : : p y t h o n : : i n c r e f < > ( P y O b j e c t * . . . ) ; b p : : t o _ p y t h o n _ c o n v e r t e r < c l a s s - n a m e , c o n v e r t e r - s t r u c t > ( ) ; Try it: Write a c o n v e r t i b l e function This is actually fairly complex, but at least give it some thought. You can find a complete answer in the course materials. Remember: I n [ 7 8 ] : % % s n i p p e t v o i d * c o n v e r t i b l e ( P y O b j e c t * o b j ) { . . . } P y T u p l e _ C h e c k ( t u p l e - o b j e c t - p o i n t e r ) ; P y T u p l e _ S i z e ( t u p l e - o b j e c t - p o i n t e r ) ; P y T u p l e _ G e t I t e m ( t u p l e - o b j e c t - p o i n t , i n d e x ) ;
  46. P y O b j e c t _ I

    s I n s t a n c e ( o b j e c t - p t r , c l a s s - o b j e c t - p t r ) ; b o o s t : : p y t h o n : : i m p o r t ( " m o d u l e - n a m e " ) ; Try it: Write a from-python converter function This is also substantially more complex than we probably want to tackle in this workshop. A complete answer is in the course materials. Remember: I n [ 7 9 ] : % % s n i p p e t s t a t i c v o i d c o n s t r u c t ( P y O b j e c t * o b j _ p t r , b p : : c o n v e r t e r : : r v a l u e _ f r o m _ p y t h o n _ s t a g e 1 _ d a t a * d a t a ) { . . . / / G r a b p o i n t e r t o m e m o r y i n t o w h i c h t o c o n s t r u c t t h e n e w o b j e c t v o i d * s t o r a g e = ( ( b p : : c o n v e r t e r : : r v a l u e _ f r o m _ p y t h o n _ s t o r a g e < o b j e c t - t y p e > * ) d a t a ) - > s t o r a g e . b y t e s ; / / i n - p l a c e c o n s t r u c t t h e n e w o b j e c t n e w ( s t o r a g e ) c l a s s - n a m e ( a r g 1 , . . . ) ; / / S t a s h t h e m e m o r y c h u n k p o i n t e r f o r l a t e r u s e b y b o o s t . p y t h o n d a t a - > c o n v e r t i b l e = s t o r a g e ; } / / H o w t o t a k e a r e f e r e n c e t o a b o r r o w e d o b j e c t b o o s t : : p y t h o n : : o b j e c t ( b o o s t : : p y t h o n : : h a n d l e < > ( b o o s t : : p y t h o n : : b o r r o w e d ( P y O b j e c t * ) ) )
  47. b p : : c o n v e r

    t e r : : r e g i s t r y : : p u s h _ b a c k ( & c o n v e r t i b l e , & c o n s t r u c t , b p : : t y p e _ i d < c l a s s - n a m e > ( ) ) ; Subclassing C++ classes in Python with b o o s t : : p y t h o n : : w r a p p e r In some cases it's convenient to be able to create classes in Python which inherit from C++ classes and which implement virtual functions exposed from C++, event pure abstract functions. This requires a certain amount of scaffolding because you can't instantiate an abstract class, but that would be required for Python inheritance. Boost.Python provides the b o o s t : : p y t h o n : : w r a p p e r < > class for this purpose. It allows you to implement the virtual functions in C++ in a special wrapper class, and then to have those calls routed to the function implemented in Python. It looks like this: I n [ 8 0 ] : % % b p _ m o d u l e b a s e _ w r a p p e r # i n c l u d e < b o o s t / p y t h o n . h p p > # i n c l u d e < i o s t r e a m > n a m e s p a c e b p = b o o s t : : p y t h o n ; c l a s s B a s e { p u b l i c : v i r t u a l i n t f ( i n t x ) c o n s t = 0 ; } ; i n t r u n B a s e ( c o n s t B a s e & b , i n t x ) { r e t u r n b . f ( x ) ; } c l a s s B a s e W r a p p e r : p u b l i c B a s e , p u b l i c b p : : w r a p p e r < B a s e >
  48. { p u b l i c : i n

    t f ( i n t x ) c o n s t { r e t u r n t h i s - > g e t _ o v e r r i d e ( " f " ) ( x ) ; } } ; B O O S T _ P Y T H O N _ M O D U L E ( b a s e _ w r a p p e r ) { b p : : d e f ( " r u n _ b a s e " , & r u n B a s e ) ; b p : : c l a s s _ < B a s e W r a p p e r , b o o s t : : n o n c o p y a b l e > ( " B a s e " , b p : : i n i t < > ( ) ) . d e f ( " f " , & B a s e W r a p p e r : : f ) ; } I n [ 8 2 ] : i m p o r t b a s e _ w r a p p e r c l a s s A ( b a s e _ w r a p p e r . B a s e ) : d e f f ( s e l f , x ) : r e t u r n 4 2 * x p r i n t ( b a s e _ w r a p p e r . r u n _ b a s e ( A ( ) , 5 ) ) Try it: Expose P l a y e r using b o o s t : : p y t h o n : : w r a p p e r Replace the existing P l a y e r bindings with a new one based on b o o s t : : p y t h o n : : w r a p p e r . Create a P l a y e r subclass in Python and run it through a few games. Remember: 2 1 0
  49. I n [ 8 3 ] : % % s

    n i p p e t c l a s s B a s e W r a p p e r : p u b l i c B a s e , p u b l i c b p : : w r a p p e r < B a s e > { i n t f ( a r g 1 , . . . ) { r e t u r n t h i s - > g e t _ o v e r r i d e ( " f " ) ( a r g 1 , . . . ) ; } } ; C++ exceptions The Python and C++ exception models are conceptually similar, but their implementations are entirely different. When exceptions come from C++ to the Python/C++ interface, they need to be translated from the C++ model to Python. By default, Boost.Python will translate any C++ exceptions into R u n t i m e E r r o r s in Python. I n [ 8 4 ] : % % b p _ m o d u l e d e f a u l t _ e x c e p t i o n # i n c l u d e < b o o s t / p y t h o n . h p p > c l a s s M y E x c e p t i o n : p u b l i c s t d : : e x c e p t i o n { } ; v o i d t h r o w _ e x c e p t i o n ( ) { t h r o w M y E x c e p t i o n ( ) ; } B O O S T _ P Y T H O N _ M O D U L E ( d e f a u l t _ e x c e p t i o n ) { b o o s t : : p y t h o n : : d e f ( " t h r o w _ e x c e p t i o n " , t h r o w _ e x c e p t i o n ) ; } I n [ 8 5 ] : f r o m d e f a u l t _ e x c e p t i o n i m p o r t t h r o w _ e x c e p t i o n t h r o w _ e x c e p t i o n ( ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - R u n t i m e E r r o r T r a c e b a c k ( m o s t r e c e n t c a l l l a s t ) < i p y t h o n - i n p u t - 8 5 - f f 0 c 5 1 f 0 c 2 a 3 > i n < m o d u l e > ( ) 1 f r o m d e f a u l t _ e x c e p t i o n i m p o r t t h r o w _ e x c e p t i o n 2 - - - - > 3 t h r o w _ e x c e p t i o n ( )
  50. Explicitly translating C++ exceptions If you want to convert C++

    exceptions into a more appropriate Python exception, you can catch them and use the Python C API to produce what you want. In this case, we'll translate M y E x c e p t i o n into a S y s t e m E r r o r (just to keep things simple.) I n [ 8 6 ] : % % b p _ m o d u l e e x p l i c i t _ r a i s e # i n c l u d e < b o o s t / p y t h o n . h p p > c l a s s M y E x c e p t i o n : p u b l i c s t d : : e x c e p t i o n { } ; v o i d t h r o w _ e x c e p t i o n ( ) { t h r o w M y E x c e p t i o n ( ) ; } v o i d t h r o w _ e x c e p t i o n _ w i t h _ t r a n s l a t e ( ) { t r y { t h r o w _ e x c e p t i o n ( ) ; } c a t c h ( c o n s t M y E x c e p t i o n & ) { P y E r r _ S e t S t r i n g ( P y E x c _ S y s t e m E r r o r , " C a u g h t a M y E x c e p t i o n e r r o r . . . n o t s u r e w h a t t o d o ! " ) ; b o o s t : : p y t h o n : : t h r o w _ e r r o r _ a l r e a d y _ s e t ( ) ; } } B O O S T _ P Y T H O N _ M O D U L E ( e x p l i c i t _ r a i s e ) { b o o s t : : p y t h o n : : d e f ( " t h r o w _ e x c e p t i o n " , t h r o w _ e x c e p t i o n _ w i t h _ t r a n s l a t e ) ; } I n [ 8 7 ] : f r o m e x p l i c i t _ r a i s e i m p o r t t h r o w _ e x c e p t i o n t h r o w _ e x c e p t i o n ( ) R u n t i m e E r r o r : s t d : : e x c e p t i o n - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - S y s t e m E r r o r T r a c e b a c k ( m o s t r e c e n t c a l l l a s t )
  51. Try it: Explicitly handle an exception For this section we'll

    be working with the file e x e r c i s e s / e x c e p t i o n s / e x c e p t i o n s . c p p . Add a wrapper function around d i v i d e which catches M a t h E r r o r and converts it into a Python V a l u e E r r o r . Expose this wrapper function in a new module and test it out. Remember: https://docs.python.org/3/c-api/exceptions.html#c.PyErr_SetString P y E x c _ * in the Python C-API I n [ 8 8 ] : % % s n i p p e t P y E r r _ S e t S t r i n g ( e x c e p t i o n - o b j e c t , m e s s a g e - s t r i n g ) ; Registering C++ exception translators A more scalable and maintainable approach to translating C++ exceptions is to use Boost.Python's r e g i s t e r _ e x c e p t i o n _ t r a n s l a t o r < > function. This will set up a global translator for specific C++ exception types, meaning that you don't have to explicitly catch and translate them yourself. Here's how that looks: I n [ 8 9 ] : % % b p _ m o d u l e r e g i s t e r e d _ e x c e p t i o n _ t r a n s l a t o r # i n c l u d e < b o o s t / p y t h o n . h p p > < i p y t h o n - i n p u t - 8 7 - a 5 8 2 5 9 4 b 7 d f 1 > i n < m o d u l e > ( ) 1 f r o m e x p l i c i t _ r a i s e i m p o r t t h r o w _ e x c e p t i o n 2 - - - - > 3 t h r o w _ e x c e p t i o n ( ) S y s t e m E r r o r : C a u g h t a M y E x c e p t i o n e r r o r . . . n o t s u r e w h a t t o d o !
  52. c l a s s M y E x c

    e p t i o n : p u b l i c s t d : : e x c e p t i o n { } ; v o i d t h r o w _ e x c e p t i o n ( ) { t h r o w M y E x c e p t i o n ( ) ; } v o i d t r a n s l a t e _ M y E x c e p t i o n ( c o n s t M y E x c e p t i o n & ) { P y E r r _ S e t S t r i n g ( P y E x c _ S y s t e m E r r o r , " T r a n s l a t o r h a n d l i n g a M y E x c e p t i o n e r r o r . . . s t i l l n o t s u r e w h a t t o d o ! " ) ; } B O O S T _ P Y T H O N _ M O D U L E ( r e g i s t e r e d _ e x c e p t i o n _ t r a n s l a t o r ) { b o o s t : : p y t h o n : : r e g i s t e r _ e x c e p t i o n _ t r a n s l a t o r < M y E x c e p t i o n > ( t r a n s l a t e _ M y E x c e p t i o n ) ; b o o s t : : p y t h o n : : d e f ( " t h r o w _ e x c e p t i o n " , t h r o w _ e x c e p t i o n ) ; } I n [ 9 0 ] : f r o m r e g i s t e r e d _ e x c e p t i o n _ t r a n s l a t o r i m p o r t t h r o w _ e x c e p t i o n t h r o w _ e x c e p t i o n ( ) Try it: Register an exception translator Replace your wrapper function with a translator function. Remember: I n [ 9 1 ] : % % s n i p p e t b o o s t : : p y t h o n : : r e g i s t e r _ e x c e p t i o n _ t r a n s l a t o r < e x c e p t i o n - t y p e > ( t r a n s l a t o r - f u n c t i o n ) ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - S y s t e m E r r o r T r a c e b a c k ( m o s t r e c e n t c a l l l a s t ) < i p y t h o n - i n p u t - 9 0 - 3 1 4 3 5 5 4 8 d e 4 b > i n < m o d u l e > ( ) 1 f r o m r e g i s t e r e d _ e x c e p t i o n _ t r a n s l a t o r i m p o r t t h r o w _ e x c e p t i o n 2 - - - - > 3 t h r o w _ e x c e p t i o n ( ) S y s t e m E r r o r : T r a n s l a t o r h a n d l i n g a M y E x c e p t i o n e r r o r . . . s t i l l n o t s u r e w h a t t o d o !
  53. v o i d t r a n s l

    a t e - f u n c t i o n ( c o n s t e x c e p t i o n - t y p e & ) { . . . } Try it: Why do we need call policies? Before explaining call policies, we'll first set up a situation that demonstrates why we need them. Edit e x e r c i s e s / c a l l _ p o l i c i e s / c a l l _ p o l i c i e s . c p p such that e x e r c i s e s / c a l l _ p o l i c i e s / t e s t . p y works. Call policies Because C++ can work at very low level with direct references and pointers, it is possible to create situations where Boost.Python can't determine lifetime relationships between objects. To address this problem, Boost.Python has a notion of call policies that allow you to explicitly declare lifetime relationships that would otherwise be obscure. In the exercise, R o w objects refer to data owned by M a t r i x objects, and therefore the lifetimes of R o w objects need to be bound by the lifetimes of their M a t r i x instances. In the terms used by Boost.Python, we want R o w to be a custodian of M a t r i x , the ward. Whenever a custodian is still alive, Boost.Python will guarantee that the ward is not deleted. We establish the custodian-ward relationship with call policies. Specifying call policies You specify a call policy by passing it as the third argument to d e f ( ) . I n [ 9 2 ] : % % s n i p p e t
  54. d e f ( " p y t h o

    n - n a m e " , f u n c t i o n - n a m e , c a l l - p o l i c y ) ; The custodian-ward relationship can be established using the w i t h _ c u s t o d i a n _ a n d _ w a r d < > or w i t h _ c u s t o d i a n _ a n d _ w a r d _ p o s t c a l l < > call policies. These both take at least two template arguments: 1. The position of the custodian 2. The position of the ward where 0 indicates the return value of the function and other numbers indicate 1-based argument values (and where 1 for a member function means the instance.) They look like this: I n [ 9 3 ] : % % s n i p p e t v o i d f o o ( E x t e r n a l C l a s s & s , I n t e r n a l R e f & i ) { r e t u r n } . . . b o o s t : : p y t h o n : : d e f ( " f o o " , f o o , b o o s t : : p y t h o n : : w i t h _ c u s t o d i a n _ a n d _ w a r d < 2 , 1 > ( ) ) ; Using more than one call policy If you need to use more than one call policy, you can nest them by passing them as the last template argument to other call policies like this: I n [ 9 4 ] : % % s n i p p e t p o l i c y 1 < a r g s . . . , p o l i c y 2 < a r g s . . . , p o l i c y 3 < a r g s . . . > > > Call policies: the available options The available call policies are: w i t h _ c u s t o d i a n _ a n d _ w a r d w i t h _ c u s t o d i a n _ a n d _ w a r d _ p o s t c a l l r e t u r n _ i n t e r n a l _ r e f e r e n c e
  55. r e t u r n _ v a l

    u e _ p o l i c y < T > where T is one of: r e f e r e n c e _ e x i s t i n g _ o b j e c t c o p y _ c o n s t _ r e f e r e n c e c o p y _ n o n _ c o n s t _ r e f e r e n c e m a n a g e _ n e w _ o b j e c t Full detail can be found at the Boost.Python function documentation (http://www.boost.org/doc/libs/1_55_0/libs/python/doc/tutorial/doc/html/python/functions.html) and the Boost.Python reference documentation (http://www.boost.org/doc/libs/1_55_0/libs/python/doc/v2/reference.html#models_of_call_policies). Try it: Use a call policy In the M a t r i x /R o w example the problem was that the M a t r i x was be deleted before the R o w . Use a call policy to tell Boost.Python about the relationship between the classes. Remember: a ward won't be deleted as long as the custodian is alive a position of 0 means the return value of the function I n [ 9 5 ] : % % s n i p p e t b p : : d e f ( " n a m e " , f u n c t i o n , b p : : c a l l - p o l i c y - n a m e < a r g s , . . . > ( ) ) ; Thanks! Questions? You can download the IPython notebook for this presentation at https://bitbucket.org/sixty-north/boost_python_workshop (https://bitbucket.org/sixty- north/boost_python_workshop) @austin_bingham