?? tutorial.qbk
字號:
, Py_eval_input
, main_namespace.ptr()
, main_namespace.ptr()))
));
int five_squared = extract<int>(result);
[blurb
__note__ [*Note] that [^object]'s member function to return the wrapped
[^PyObject*] is called [^ptr] instead of [^get]. This makes sense if you
take into account the different functions that [^object] and [^handle]
perform.
]
[h2 Exception handling]
If an exception occurs in the execution of some Python code, the PyRun_String
function returns a null pointer. Constructing a [^handle] out of this null
pointer throws [@../../../v2/errors.html#error_already_set-spec error_already_set],
so basically, the Python exception is automatically translated into a
C++ exception when using [^handle]:
try
{
object result((handle<>(PyRun_String(
"5/0"
, Py_eval_input
, main_namespace.ptr()
, main_namespace.ptr()))
));
// execution will never get here:
int five_divided_by_zero = extract<int>(result);
}
catch(error_already_set)
{
// handle the exception in some way
}
The [^error_already_set] exception class doesn't carry any information in itself.
To find out more about the Python exception that occurred, you need to use the
[@http://www.python.org/doc/api/exceptionHandling.html exception handling functions]
of the Python/C API in your catch-statement. This can be as simple as calling
[@http://www.python.org/doc/api/exceptionHandling.html#l2h-70 PyErr_Print()] to
print the exception's traceback to the console, or comparing the type of the
exception with those of the [@http://www.python.org/doc/api/standardExceptions.html
standard exceptions]:
catch(error_already_set)
{
if (PyErr_ExceptionMatches(PyExc_ZeroDivisionError))
{
// handle ZeroDivisionError specially
}
else
{
// print all other errors to stderr
PyErr_Print();
}
}
(To retrieve even more information from the exception you can use some of the other
exception handling functions listed [@http://www.python.org/doc/api/exceptionHandling.html here].)
If you'd rather not have [^handle] throw a C++ exception when it is constructed, you
can use the [@../../../v2/handle.html#allow_null-spec allow_null] function in the same
way you'd use borrowed:
handle<> result((allow_null(PyRun_String(
"5/0"
, Py_eval_input
, main_namespace.ptr()
, main_namespace.ptr()))));
if (!result)
// Python exception occurred
else
// everything went okay, it's safe to use the result
[endsect]
[endsect] [/ Embedding]
[section Iterators]
In C++, and STL in particular, we see iterators everywhere. Python also has
iterators, but these are two very different beasts.
[*C++ iterators:]
* C++ has 5 type categories (random-access, bidirectional, forward, input, output)
* There are 2 Operation categories: reposition, access
* A pair of iterators is needed to represent a (first/last) range.
[*Python Iterators:]
* 1 category (forward)
* 1 operation category (next())
* Raises StopIteration exception at end
The typical Python iteration protocol: [^[*for y in x...]] is as follows:
[python]
iter = x.__iter__() # get iterator
try:
while 1:
y = iter.next() # get each item
... # process y
except StopIteration: pass # iterator exhausted
Boost.Python provides some mechanisms to make C++ iterators play along
nicely as Python iterators. What we need to do is to produce
appropriate `__iter__` function from C++ iterators that is compatible
with the Python iteration protocol. For example:
[c++]
object get_iterator = iterator<vector<int> >();
object iter = get_iterator(v);
object first = iter.next();
Or for use in class_<>:
.def("__iter__", iterator<vector<int> >())
[*range]
We can create a Python savvy iterator using the range function:
* range(start, finish)
* range<Policies,Target>(start, finish)
Here, start/finish may be one of:
* member data pointers
* member function pointers
* adaptable function object (use Target parameter)
[*iterator]
* iterator<T, Policies>()
Given a container [^T], iterator is a shortcut that simply calls [^range]
with &T::begin, &T::end.
Let's put this into action... Here's an example from some hypothetical
bogon Particle accelerator code:
[python]
f = Field()
for x in f.pions:
smash(x)
for y in f.bogons:
count(y)
Now, our C++ Wrapper:
[c++]
class_<F>("Field")
.property("pions", range(&F::p_begin, &F::p_end))
.property("bogons", range(&F::b_begin, &F::b_end));
[endsect]
[section:exception Exception Translation]
All C++ exceptions must be caught at the boundary with Python code. This
boundary is the point where C++ meets Python. Boost.Python provides a
default exception handler that translates selected standard exceptions,
then gives up:
raise RuntimeError, 'unidentifiable C++ Exception'
Users may provide custom translation. Here's an example:
struct PodBayDoorException;
void translator(PodBayDoorException const& x) {
PyErr_SetString(PyExc_UserWarning, "I'm sorry Dave...");
}
BOOST_PYTHON_MODULE(kubrick) {
register_exception_translator<
PodBayDoorException>(translator);
...
[endsect]
[section:techniques General Techniques]
Here are presented some useful techniques that you can use while wrapping code with Boost.Python.
[section Creating Packages]
A Python package is a collection of modules that provide to the user a certain
functionality. If you're not familiar on how to create packages, a good
introduction to them is provided in the
[@http://www.python.org/doc/current/tut/node8.html Python Tutorial].
But we are wrapping C++ code, using Boost.Python. How can we provide a nice
package interface to our users? To better explain some concepts, let's work
with an example.
We have a C++ library that works with sounds: reading and writing various
formats, applying filters to the sound data, etc. It is named (conveniently)
[^sounds]. Our library already has a neat C++ namespace hierarchy, like so:
sounds::core
sounds::io
sounds::filters
We would like to present this same hierarchy to the Python user, allowing him
to write code like this:
import sounds.filters
sounds.filters.echo(...) # echo is a C++ function
The first step is to write the wrapping code. We have to export each module
separately with Boost.Python, like this:
/* file core.cpp */
BOOST_PYTHON_MODULE(core)
{
/* export everything in the sounds::core namespace */
...
}
/* file io.cpp */
BOOST_PYTHON_MODULE(io)
{
/* export everything in the sounds::io namespace */
...
}
/* file filters.cpp */
BOOST_PYTHON_MODULE(filters)
{
/* export everything in the sounds::filters namespace */
...
}
Compiling these files will generate the following Python extensions:
[^core.pyd], [^io.pyd] and [^filters.pyd].
[blurb __note__ The extension [^.pyd] is used for python extension modules, which
are just shared libraries. Using the default for your system, like [^.so] for
Unix and [^.dll] for Windows, works just as well.]
Now, we create this directory structure for our Python package:
[pre
sounds/
\_\_init\_\_.py
core.pyd
filters.pyd
io.pyd
]
The file [^\_\_init\_\_.py] is what tells Python that the directory [^sounds/] is
actually a Python package. It can be a empty file, but can also perform some
magic, that will be shown later.
Now our package is ready. All the user has to do is put [^sounds] into his
[@http://www.python.org/doc/current/tut/node8.html#SECTION008110000000000000000 PYTHONPATH]
and fire up the interpreter:
[python]
>>> import sounds.io
>>> import sounds.filters
>>> sound = sounds.io.open('file.mp3')
>>> new_sound = sounds.filters.echo(sound, 1.0)
Nice heh?
This is the simplest way to create hierarchies of packages, but it is not very
flexible. What if we want to add a ['pure] Python function to the filters
package, for instance, one that applies 3 filters in a sound object at once?
Sure, you can do this in C++ and export it, but why not do so in Python? You
don't have to recompile the extension modules, plus it will be easier to write
it.
If we want this flexibility, we will have to complicate our package hierarchy a
little. First, we will have to change the name of the extension modules:
[c++]
/* file core.cpp */
BOOST_PYTHON_MODULE(_core)
{
...
/* export everything in the sounds::core namespace */
}
Note that we added an underscore to the module name. The filename will have to
be changed to [^_core.pyd] as well, and we do the same to the other extension modules.
Now, we change our package hierarchy like so:
[pre
sounds/
\_\_init\_\_.py
core/
\_\_init\_\_.py
_core.pyd
filters/
\_\_init\_\_.py
_filters.pyd
io/
\_\_init\_\_.py
_io.pyd
]
Note that we created a directory for each extension module, and added a
\_\_init\_\_.py to each one. But if we leave it that way, the user will have to
access the functions in the core module with this syntax:
[python]
>>> import sounds.core._core
>>> sounds.core._core.foo(...)
which is not what we want. But here enters the [^\_\_init\_\_.py] magic: everything
that is brought to the [^\_\_init\_\_.py] namespace can be accessed directly by the
user. So, all we have to do is bring the entire namespace from [^_core.pyd]
to [^core/\_\_init\_\_.py]. So add this line of code to [^sounds/core/\_\_init\_\_.py]:
from _core import *
We do the same for the other packages. Now the user accesses the functions and
classes in the extension modules like before:
>>> import sounds.filters
>>> sounds.filters.echo(...)
with the additional benefit that we can easily add pure Python functions to
any module, in a way that the user can't tell the difference between a C++
function and a Python function. Let's add a ['pure] Python function,
[^echo_noise], to the [^filters] package. This function applies both the
[^echo] and [^noise] filters in sequence in the given [^sound] object. We
create a file named [^sounds/filters/echo_noise.py] and code our function:
import _filters
def echo_noise(sound):
s = _filters.echo(sound)
s = _filters.noise(sound)
return s
Next, we add this line to [^sounds/filters/\_\_init\_\_.py]:
from echo_noise import echo_noise
And that's it. The user now accesses this function like any other function
from the [^filters] package:
>>> import sounds.filters
>>> sounds.filters.echo_noise(...)
[endsect]
[section Extending Wrapped Objects in Python]
Thanks to Python's flexibility, you can easily add new methods to a class,
even after it was already created:
>>> class C(object): pass
>>>
>>> # a regular function
>>> def C_str(self): return 'A C instance!'
>>>
>>> # now we turn it in a member function
>>> C.__str__ = C_str
>>>
>>> c = C()
>>> print c
A C instance!
>>> C_str(c)
A C instance!
Yes, Python rox. :-)
We can do the same with classes that were wrapped with Boost.Python. Suppose
we have a class [^point] in C++:
[c++]
class point {...};
BOOST_PYTHON_MODULE(_geom)
{
class_<point>("point")...;
}
If we are using the technique from the previous session,
[link python.creating_packages Creating Packages], we can code directly
into [^geom/\_\_init\_\_.py]:
[python]
from _geom import *
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -