Slide 1

Slide 1 text

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)

Slide 2

Slide 2 text

What is Python / C++ Integration? A lot of language integration can happen between processes

Slide 3

Slide 3 text

This is useful and interesting, but not what we're talking about here. Python is a C library

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

...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?

Slide 6

Slide 6 text

Basics of the Python C API

Slide 7

Slide 7 text

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.

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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.

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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.

Slide 13

Slide 13 text

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 ) ) ) ;

Slide 14

Slide 14 text

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:

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

# 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 ) ] ' ] "

Slide 18

Slide 18 text

} 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

Slide 19

Slide 19 text

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 _ _ ' ]

Slide 20

Slide 20 text

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 )

Slide 21

Slide 21 text

# 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 !

Slide 22

Slide 22 text

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:

Slide 23

Slide 23 text

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 ) ;

Slide 24

Slide 24 text

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 ) ;

Slide 25

Slide 25 text

} 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

Slide 26

Slide 26 text

# 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 )

Slide 27

Slide 27 text

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 )

Slide 28

Slide 28 text

# 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 _ _ ' ]

Slide 29

Slide 29 text

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:

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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 )

Slide 32

Slide 32 text

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 ; }

Slide 33

Slide 33 text

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 > ( )

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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 ;

Slide 36

Slide 36 text

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 ' )

Slide 37

Slide 37 text

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 ) ; }

Slide 38

Slide 38 text

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 )

Slide 39

Slide 39 text

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 ) ,

Slide 40

Slide 40 text

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 ) ]

Slide 41

Slide 41 text

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 ' )

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

/ / 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:

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

{ 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 ) ;

Slide 48

Slide 48 text

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 * ) ) )

Slide 49

Slide 49 text

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 >

Slide 50

Slide 50 text

{ 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

Slide 51

Slide 51 text

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 ( )

Slide 52

Slide 52 text

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 )

Slide 53

Slide 53 text

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 !

Slide 54

Slide 54 text

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 !

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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