📄 tutorial.qbk
字号:
>>> print x.name, 'is around', x.value
pi is around 3.14
Note that [^name] is exposed as [*read-only] while [^value] is exposed
as [*read-write].
>>> x.name = 'e' # can't change name
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AttributeError: can't set attribute
[endsect]
[section Class Properties]
In C++, classes with public data members are usually frowned
upon. Well designed classes that take advantage of encapsulation hide
the class' data members. The only way to access the class' data is
through access (getter/setter) functions. Access functions expose class
properties. Here's an example:
[c++]
struct Num
{
Num();
float get() const;
void set(float value);
...
};
However, in Python attribute access is fine; it doesn't neccessarily break
encapsulation to let users handle attributes directly, because the
attributes can just be a different syntax for a method call. Wrapping our
[^Num] class using Boost.Python:
class_<Num>("Num")
.add_property("rovalue", &Num::get)
.add_property("value", &Num::get, &Num::set);
And at last, in Python:
[python]
>>> x = Num()
>>> x.value = 3.14
>>> x.value, x.rovalue
(3.14, 3.14)
>>> x.rovalue = 2.17 # error!
Take note that the class property [^rovalue] is exposed as [*read-only]
since the [^rovalue] setter member function is not passed in:
[c++]
.add_property("rovalue", &Num::get)
[endsect]
[section Inheritance]
In the previous examples, we dealt with classes that are not polymorphic.
This is not often the case. Much of the time, we will be wrapping
polymorphic classes and class hierarchies related by inheritance. We will
often have to write Boost.Python wrappers for classes that are derived from
abstract base classes.
Consider this trivial inheritance structure:
struct Base { virtual ~Base(); };
struct Derived : Base {};
And a set of C++ functions operating on [^Base] and [^Derived] object
instances:
void b(Base*);
void d(Derived*);
Base* factory() { return new Derived; }
We've seen how we can wrap the base class [^Base]:
class_<Base>("Base")
/*...*/
;
Now we can inform Boost.Python of the inheritance relationship between
[^Derived] and its base class [^Base]. Thus:
class_<Derived, bases<Base> >("Derived")
/*...*/
;
Doing so, we get some things for free:
# Derived automatically inherits all of Base's Python methods
(wrapped C++ member functions)
# [*If] Base is polymorphic, [^Derived] objects which have been passed to
Python via a pointer or reference to [^Base] can be passed where a pointer
or reference to [^Derived] is expected.
Now, we shall expose the C++ free functions [^b] and [^d] and [^factory]:
def("b", b);
def("d", d);
def("factory", factory);
Note that free function [^factory] is being used to generate new
instances of class [^Derived]. In such cases, we use
[^return_value_policy<manage_new_object>] to instruct Python to adopt
the pointer to [^Base] and hold the instance in a new Python [^Base]
object until the the Python object is destroyed. We shall see more of
Boost.Python [link python.call_policies call policies] later.
// Tell Python to take ownership of factory's result
def("factory", factory,
return_value_policy<manage_new_object>());
[endsect]
[section Class Virtual Functions]
In this section, we shall learn how to make functions behave polymorphically
through virtual functions. Continuing our example, let us add a virtual function
to our [^Base] class:
struct Base
{
virtual ~Base() {}
virtual int f() = 0;
};
One of the goals of Boost.Python is to be minimally intrusive on an existing C++
design. In principle, it should be possible to expose the interface for a 3rd
party library without changing it. It is not ideal to add anything to our class
`Base`. Yet, when you have a virtual function that's going to be overridden in
Python and called polymorphically *from C++*, we'll need to add some
scaffoldings to make things work properly. What we'll do is write a class
wrapper that derives from `Base` that will unintrusively hook into the virtual
functions so that a Python override may be called:
struct BaseWrap : Base, wrapper<Base>
{
int f()
{
return this->get_override("f")();
}
};
Notice too that in addition to inheriting from `Base`, we also multiply-
inherited `wrapper<Base>` (See [@../../../v2/wrapper.html Wrapper]). The
`wrapper` template makes the job of wrapping classes that are meant to
overridden in Python, easier.
[blurb __alert__ [*MSVC6/7 Workaround]\n\n
If you are using Microsoft Visual C++ 6 or 7, you have to write `f` as:\n\n
`return call<int>(this->get_override("f").ptr());`.]
BaseWrap's overridden virtual member function `f` in effect calls the
corresponding method of the Python object through `get_override`.
Finally, exposing `Base`:
class_<BaseWrap, boost::noncopyable>("Base")
.def("f", pure_virtual(&Base::f))
;
`pure_virtual` signals Boost.Python that the function `f` is a pure virtual
function.
[blurb __note__ [*member function and methods]\n\n Python, like
many object oriented languages uses the term [*methods]. Methods
correspond roughly to C++'s [*member functions]]
[endsect]
[section Virtual Functions with Default Implementations]
We've seen in the previous section how classes with pure virtual functions are
wrapped using Boost.Python's [@../../../v2/wrapper.html class wrapper]
facilities. If we wish to wrap [*non]-pure-virtual functions instead, the
mechanism is a bit different.
Recall that in the [link python.class_virtual_functions previous section], we
wrapped a class with a pure virtual function that we then implemented in C++, or
Python classes derived from it. Our base class:
struct Base
{
virtual int f() = 0;
};
had a pure virtual function [^f]. If, however, its member function [^f] was
not declared as pure virtual:
struct Base
{
virtual ~Base() {}
virtual int f() { return 0; }
};
We wrap it this way:
struct BaseWrap : Base, wrapper<Base>
{
int f()
{
if (override f = this->get_override("f"))
return f(); // *note*
return Base::f();
}
int default_f() { return this->Base::f(); }
};
Notice how we implemented `BaseWrap::f`. Now, we have to check if there is an
override for `f`. If none, then we call `Base::f()`.
[blurb __alert__ [*MSVC6/7 Workaround]\n\n
If you are using Microsoft Visual C++ 6 or 7, you have to rewrite the line
with the `*note*` as:\n\n
`return call<char const*>(f.ptr());`.]
Finally, exposing:
class_<BaseWrap, boost::noncopyable>("Base")
.def("f", &Base::f, &BaseWrap::default_f)
;
Take note that we expose both `&Base::f` and `&BaseWrap::default_f`.
Boost.Python needs to keep track of 1) the dispatch function [^f] and 2) the
forwarding function to its default implementation [^default_f]. There's a
special [^def] function for this purpose.
In Python, the results would be as expected:
[python]
>>> base = Base()
>>> class Derived(Base):
... def f(self):
... return 42
...
>>> derived = Derived()
Calling [^base.f()]:
>>> base.f()
0
Calling [^derived.f()]:
>>> derived.f()
42
[endsect]
[section Class Operators/Special Functions]
[h2 Python Operators]
C is well known for the abundance of operators. C++ extends this to the
extremes by allowing operator overloading. Boost.Python takes advantage of
this and makes it easy to wrap C++ operator-powered classes.
Consider a file position class [^FilePos] and a set of operators that take
on FilePos instances:
[c++]
class FilePos { /*...*/ };
FilePos operator+(FilePos, int);
FilePos operator+(int, FilePos);
int operator-(FilePos, FilePos);
FilePos operator-(FilePos, int);
FilePos& operator+=(FilePos&, int);
FilePos& operator-=(FilePos&, int);
bool operator<(FilePos, FilePos);
The class and the various operators can be mapped to Python rather easily
and intuitively:
class_<FilePos>("FilePos")
.def(self + int()) // __add__
.def(int() + self) // __radd__
.def(self - self) // __sub__
.def(self - int()) // __sub__
.def(self += int()) // __iadd__
.def(self -= other<int>())
.def(self < self); // __lt__
The code snippet above is very clear and needs almost no explanation at
all. It is virtually the same as the operators' signatures. Just take
note that [^self] refers to FilePos object. Also, not every class [^T] that
you might need to interact with in an operator expression is (cheaply)
default-constructible. You can use [^other<T>()] in place of an actual
[^T] instance when writing "self expressions".
[h2 Special Methods]
Python has a few more ['Special Methods]. Boost.Python supports all of the
standard special method names supported by real Python class instances. A
similar set of intuitive interfaces can also be used to wrap C++ functions
that correspond to these Python ['special functions]. Example:
class Rational
{ public: operator double() const; };
Rational pow(Rational, Rational);
Rational abs(Rational);
ostream& operator<<(ostream&,Rational);
class_<Rational>("Rational")
.def(float_(self)) // __float__
.def(pow(self, other<Rational>)) // __pow__
.def(abs(self)) // __abs__
.def(str(self)) // __str__
;
Need we say more?
[blurb __note__ What is the business of `operator<<`?
Well, the method `str` requires the `operator<<` to do its work (i.e.
`operator<<` is used by the method defined by `def(str(self))`.]
[endsect]
[endsect] [/ Exposing Classes ]
[section Functions]
In this chapter, we'll look at Boost.Python powered functions in closer
detail. We shall see some facilities to make exposing C++ functions to
Python safe from potential pifalls such as dangling pointers and
references. We shall also see facilities that will make it even easier for
us to expose C++ functions that take advantage of C++ features such as
overloading and default arguments.
[:['Read on...]]
But before you do, you might want to fire up Python 2.2 or later and type
[^>>> import this].
[pre
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
]
[section Call Policies]
In C++, we often deal with arguments and return types such as pointers
and references. Such primitive types are rather, ummmm, low level and
they really don't tell us much. At the very least, we don't know the
owner of the pointer or the referenced object. No wonder languages
such as Java and Python never deal with such low level entities. In
C++, it's usually considered a good practice to use smart pointers
which exactly describe ownership semantics. Still, even good C++
interfaces use raw references and pointers sometimes, so Boost.Python
must deal with them. To do this, it may need your help. Consider the
following C++ function:
X& f(Y& y, Z* z);
How should the library wrap this function? A naive approach builds a
Python X object around result reference. This strategy might or might
not work out. Here's an example where it didn't
>>> x = f(y, z) # x refers to some C++ X
>>> del y
>>> x.some_method() # CRASH!
What's the problem?
Well, what if f() was implemented as shown below:
X& f(Y& y, Z* z)
{
y.z = z;
return y.x;
}
The problem is that the lifetime of result X& is tied to the lifetime
of y, because the f() returns a reference to a member of the y
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -