2
0
mirror of https://github.com/boostorg/python.git synced 2026-01-19 16:32:16 +00:00

This commit was manufactured by cvs2svn to create branch

'ralf_grosse_kunstleve'.

[SVN r9550]
This commit is contained in:
nobody
2001-03-13 00:01:07 +00:00
8 changed files with 870 additions and 0 deletions

View File

@@ -0,0 +1,253 @@
<html>
<head>
<title>Cross-extension-module dependencies</title>
</head>
<body>
<img src="../../../c++boost.gif"
alt="c++boost.gif (8819 bytes)"
align="center"
width="277" height="86">
</body>
<hr>
<h1>Cross-extension-module dependencies</h1>
It is good programming practice to organize large projects as modules
that interact with each other via well defined interfaces. With
Boost.Python it is possible to reflect this organization at the C++
level at the Python level. This is, each logical C++ module can be
organized as a separate Python extension module.
<p>
At first sight this might seem natural and straightforward. However, it
is a fairly complex problem to establish cross-extension-module
dependencies while maintaining the same ease of use Boost.Python
provides for classes that are wrapped in the same extension module. To
a large extent this complexity can be hidden from the author of a
Boost.Python extension module, but not entirely.
<h1>The recipe</h1>
Suppose there is an extension module that exposes certain instances of
the C++ std::vector template library such that it can be used from
Python in the following manner:
<pre>
import std_vector
v = std_vector.double([1, 2, 3, 4])
v.push_back(5)
v.size()
</pre>
Suppose the std_vector module is done well and reflects all C++
functions that are useful at the Python level, for all C++ built-in
data types (std_vector.int, std_vector.long, etc.).
<p>
Suppose further that there is statistic module with a C++ class that
has constructors or member functions that use or return a std::vector.
For example:
<pre>
class xy {
private:
std::vector&lt;double&gt; m_x;
std::vector&lt;double&gt; m_y;
public:
xy(const std::vector&lt;double&gt;&amp; x, const std::vector&lt;double&gt;&amp; y) : m_x(x), m_y(y) {}
const std::vector&lt;double&gt;&amp; x() const { return m_x; }
const std::vector&lt;double&gt;&amp; y() const { return m_y; }
double correlation();
}
</pre>
What is more natural then reusing the std_vector extension module to
expose these constructors or functions to Python?
<p>
Unfortunately, what seems natural needs a little work in both the
std_vector and the statistics module.
<p>
In the std_vector extension module, std::vector&lt;double&gt; needs to be
exposed to Python with the x_class_builder&lt;&gt; template instead of the
regular class_builder&lt;&gt;. For example:
<pre>
x_class_builder&lt;std::vector&lt;double&gt; &gt; v_double(std_vector_module, &quot;double&quot;);
</pre>
In the extension module that wraps class xy we need to use
the import_class_builder&lt;&gt; template:
<pre>
import_class_builder&lt;std::vector&lt;double&gt; &gt; v_double(&quot;std_vector&quot;, &quot;double&quot;);
</pre>
That is all. All the properties that are defined for std_vector.double
in the std_vector Boost.Python module will be available for the
returned objects of xy.x() and xy.y(). Similarly, the constructor for
xy will accept objects that were created by the std_vector module.
<h1>Non-copyable types</h1>
The x_class_builder&lt;T&gt; instantiates template functions that invoke the
copy constructor of T. For a T that is non-copyable this will result in
compile-time error messages. In such a case, another variety of the
class_builder&lt;&gt;, the xptr_class_builder&lt;&gt; must be used.
For example:
<pre>
xptr_class_builder&lt;store&gt; py_store(your_module, &quot;store&quot;);
</pre>
The corresponding import_class_builder&lt;&gt; does not need any special
attention:
<pre>
import_class_builder&lt;store&gt; py_store(&quot;noncopyable_export&quot;, &quot;store&quot;);
</pre>
<h1>Python module search path</h1>
The std_vector and statistics modules can now be used in the following
way:
<pre>
import std_vector
import statistics
x = std_vector.double([1, 2, 3, 4])
y = std_vector.double([2, 4, 6, 8])
xy = statistics.xy(x, y)
xy.correlation()
</pre>
In this example it is clear that Python has to be able to find both the
std_vector and the statistics extension module. In other words, both
extension modules need to be in the Python module search path
(sys.path).
<p>
The situation is not always that obvious. Suppose the statistics
module has a random function that returns a vector of random
numbers with a given length:
<pre>
import statistics
x = statistics.random(5)
y = statistics.random(5)
xy = statistics.xy(x, y)
xy.correlation()
</pre>
A naive user will not easily anticipate that the std_vector module is
used to pass the x and y vectors around. If the std_vector module is in
the Python module search path, this form of ignorance is of no harm.
On the contrary, we are glad that we do not have to bother the user
with details like this.
<p>
If the std_vector module is not in the Python module search path, a
Python exception will be raised:
<pre>
Traceback (innermost last):
File &quot;foo.py&quot;, line 2, in ?
x = statistics.random(5)
ImportError: No module named std_vector
</pre>
As is the case with any system of a non-trivial complexity, it is
important that the setup is consistent and complete.
<h1>Two-way module dependencies</h1>
Boost.Python supports two-way module dependencies. This is best
illustrated by a simple example.
<p>
Suppose there is a module ivect that implements vectors of integers,
and a similar module dvect that implements vectors of doubles. We want
to be able do convert an integer vector to a double vector and vice
versa. For example:
<pre>
import ivect
iv = ivect.ivect((1,2,3,4,5))
dv = iv.as_dvect()
</pre>
The last expression will implicitly import the dvect module in order to
enable the conversion of the C++ representation of dvect to a Python
object. The analogous is possible for a dvect:
<pre>
import dvect
dv = dvect.dvect((1,2,3,4,5))
iv = dv.as_ivect()
</pre>
Now the ivect module is imported implicitly.
<p>
Note that the two-way dependencies are possible because the
dependencies are resolved only when needed. This is, the initialization
of the ivect module does not rely on the dvect module, and vice versa.
Only if as_dvect() or as_ivect() is actually invoked will the
corresponding module be implicitly imported. This also means that, for
example, the dvect module does not have to be available at all if
as_dvect() is never used.
<h1>Clarification of compile-time and link-time dependencies</h1>
Boost.Python's support for resolving cross-module dependencies at
runtime does not imply that compile-time dependencies are eliminated.
For example, the statistics extension module in the example above will
need to #include &lt;vector&gt;. This is immediately obvious from the
definition of class xy.
<p>
If a library is wrapped that consists of both header files and compiled
components (e.g. libdvect.a, dvect.lib, etc.), both the Boost.Python
extension module with the x_class_wrapper&lt;&gt; and the module with the
import_class_wrapper&lt;&gt; need to be linked against the object library.
Ideally one would build a shared library (e.g. libdvect.so, dvect.dll,
etc.). However, this introduces the issue of getting the search path
for the dynamic loading configured correctly. For small libraries it is
therefore often more convenient to ignore the fact that the object
files are loaded into memory more than once.
<p>
The main purpose of Boost.Python's support for resolving cross-module
dependencies at runtime is to allow for a modular system layout. With
this support it is straightforward to reflect C++ code organization at
the Python level. Without the cross-module support, a multi-purpose
module like std_vector would be impractical because the entire wrapper
code would somehow have to be duplicated in all extension modules that
use it, making them harder to maintain and harder to build.
<p>
Finally, there is an important psychological component. If a group of
classes is lumped together with many others in a huge module, the
authors will have difficulties in being identified with their work.
The situation is much more transparent if the work is represented by
a module with a recognizable name. This is not just a question of
strong egos, but also of getting credit and funding.
<h1>Why not use the x_class_builder universally?</h1>
There is some overhead associated with the Boost.Python cross-module
support. Depending on the platform, the code generated by
x_class_builder&lt;&gt; is roughly 10%-20% larger than that generated by
class_builder&lt;&gt;. For a large extension module with many wrapped
classes, this could mean a significant difference. Therefore the
general recommendation is to use x_class_wrapper&lt;&gt; only for classes
that are likely to be used as function arguments or return values in
other modules.
<hr>
<address>
Author: Ralf W. Grosse-Kunstleve, March 2001
</address>
</html>

245
doc/pickle.html Normal file
View File

@@ -0,0 +1,245 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0//EN"
"http://www.w3.org/TR/REC-html40/strict.dtd">
<title>Boost.Python Pickle Support</title>
<div>
<img src="../../../c++boost.gif"
alt="c++boost.gif (8819 bytes)"
align="center"
width="277" height="86">
<hr>
<h1>Boost.Python Pickle Support</h1>
Pickle is a Python module for object serialization, also known
as persistence, marshalling, or flattening.
<p>
It is often necessary to save and restore the contents of an object to
a file. One approach to this problem is to write a pair of functions
that read and write data from a file in a special format. A powerful
alternative approach is to use Python's pickle module. Exploiting
Python's ability for introspection, the pickle module recursively
converts nearly arbitrary Python objects into a stream of bytes that
can be written to a file.
<p>
The Boost Python Library supports the pickle module by emulating the
interface implemented by Jim Fulton's ExtensionClass module that is
included in the
<a href="http://www.zope.org/"
>ZOPE</a>
distribution.
This interface is similar to that for regular Python classes as
described in detail in the
<a href="http://www.python.org/doc/current/lib/module-pickle.html"
>Python Library Reference for pickle.</a>
<hr>
<h2>The Boost.Python Pickle Interface</h2>
At the user level, the Boost.Python pickle interface involves three special
methods:
<dl>
<dt>
<strong><tt>__getinitargs__</tt></strong>
<dd>
When an instance of a Boost.Python extension class is pickled, the
pickler tests if the instance has a <tt>__getinitargs__</tt> method.
This method must return a Python tuple (it is most convenient to use
a boost::python::tuple). When the instance is restored by the
unpickler, the contents of this tuple are used as the arguments for
the class constructor.
<p>
If <tt>__getinitargs__</tt> is not defined, the class constructor
will be called without arguments.
<p>
<dt>
<strong><tt>__getstate__</tt></strong>
<dd>
When an instance of a Boost.Python extension class is pickled, the
pickler tests if the instance has a <tt>__getstate__</tt> method.
This method should return a Python object representing the state of
the instance.
<p>
If <tt>__getstate__</tt> is not defined, the instance's
<tt>__dict__</tt> is pickled (if it is not empty).
<p>
<dt>
<strong><tt>__setstate__</tt></strong>
<dd>
When an instance of a Boost.Python extension class is restored by the
unpickler, it is first constructed using the result of
<tt>__getinitargs__</tt> as arguments (see above). Subsequently the
unpickler tests if the new instance has a <tt>__setstate__</tt>
method. If so, this method is called with the result of
<tt>__getstate__</tt> (a Python object) as the argument.
<p>
If <tt>__setstate__</tt> is not defined, the result of
<tt>__getstate__</tt> must be a Python dictionary. The items of this
dictionary are added to the instance's <tt>__dict__</tt>.
</dl>
If both <tt>__getstate__</tt> and <tt>__setstate__</tt> are defined,
the Python object returned by <tt>__getstate__</tt> need not be a
dictionary. The <tt>__getstate__</tt> and <tt>__setstate__</tt> methods
can do what they want.
<hr>
<h2>Pitfalls and Safety Guards</h2>
In Boost.Python extension modules with many extension classes,
providing complete pickle support for all classes would be a
significant overhead. In general complete pickle support should only be
implemented for extension classes that will eventually be pickled.
However, the author of a Boost.Python extension module might not
anticipate correctly which classes need support for pickle.
Unfortunately, the pickle protocol described above has two important
pitfalls that the end user of a Boost.Python extension module might not
be aware of:
<dl>
<dt>
<strong>Pitfall 1:</strong>
Both <tt>__getinitargs__</tt> and <tt>__getstate__</tt> are not defined.
<dd>
In this situation the unpickler calls the class constructor without
arguments and then adds the <tt>__dict__</tt> that was pickled by
default to that of the new instance.
<p>
However, most C++ classes wrapped with Boost.Python will have member
data that are not restored correctly by this procedure. To alert the
user to this problem, a safety guard is provided. If both
<tt>__getinitargs__</tt> and <tt>__getstate__</tt> are not defined,
Boost.Python tests if the class has an attribute
<tt>__dict_defines_state__</tt>. An exception is raised if this
attribute is not defined:
<pre>
RuntimeError: Incomplete pickle support (__dict_defines_state__ not set)
</pre>
In the rare cases where this is not the desired behavior, the safety
guard can deliberately be disabled. The corresponding C++ code for
this is, e.g.:
<pre>
class_builder&lt;your_class&gt; py_your_class(your_module, "your_class");
py_your_class.dict_defines_state();
</pre>
It is also possible to override the safety guard at the Python level.
E.g.:
<pre>
import your_bpl_module
class your_class(your_bpl_module.your_class):
__dict_defines_state__ = 1
</pre>
<p>
<dt>
<strong>Pitfall 2:</strong>
<tt>__getstate__</tt> is defined and the instance's <tt>__dict__</tt> is not empty.
<dd>
The author of a Boost.Python extension class might provide a
<tt>__getstate__</tt> method without considering the possibilities
that:
<p>
<ul>
<li>
his class is used in Python as a base class. Most likely the
<tt>__dict__</tt> of instances of the derived class needs to be
pickled in order to restore the instances correctly.
<p>
<li>
the user adds items to the instance's <tt>__dict__</tt> directly.
Again, the <tt>__dict__</tt> of the instance then needs to be
pickled.
</ul>
<p>
To alert the user to this highly unobvious problem, a safety guard is
provided. If <tt>__getstate__</tt> is defined and the instance's
<tt>__dict__</tt> is not empty, Boost.Python tests if the class has
an attribute <tt>__getstate_manages_dict__</tt>. An exception is
raised if this attribute is not defined:
<pre>
RuntimeError: Incomplete pickle support (__getstate_manages_dict__ not set)
</pre>
To resolve this problem, it should first be established that the
<tt>__getstate__</tt> and <tt>__setstate__</tt> methods manage the
instances's <tt>__dict__</tt> correctly. Note that this can be done
both at the C++ and the Python level. Finally, the safety guard
should intentionally be overridden. E.g. in C++:
<pre>
class_builder&lt;your_class&gt; py_your_class(your_module, "your_class");
py_your_class.getstate_manages_dict();
</pre>
In Python:
<pre>
import your_bpl_module
class your_class(your_bpl_module.your_class):
__getstate_manages_dict__ = 1
def __getstate__(self):
# your code here
def __setstate__(self, state):
# your code here
</pre>
</dl>
<hr>
<h2>Practical Advice</h2>
<ul>
<li>
Avoid using <tt>__getstate__</tt> if the instance can also be
reconstructed by way of <tt>__getinitargs__</tt>. This automatically
avoids Pitfall 2.
<p>
<li>
If <tt>__getstate__</tt> is required, include the instance's
<tt>__dict__</tt> in the Python object that is returned.
</ul>
<hr>
<h2>Example</h2>
An example that shows how to configure pickle support is available in the
<tt>boost/lib/python/example</tt> directory
(<tt>getting_started3.cpp</tt>).
<hr>
&copy; Copyright Ralf W. Grosse-Kunstleve 2001. Permission to copy,
use, modify, sell and distribute this document is granted provided this
copyright notice appears in all copies. This document is provided "as
is" without express or implied warranty, and with no claim as to its
suitability for any purpose.
<p>
Updated: March 10, 2001
</div>

57
example/pickle1.cpp Normal file
View File

@@ -0,0 +1,57 @@
/*
This example shows how to make an Extension Class "pickleable".
For more information refer to boost/libs/python/doc/pickle.html.
*/
#include <string>
#include <boost/python/class_builder.hpp>
namespace python = boost::python;
namespace { // Avoid cluttering the global namespace.
// A friendly class.
class world
{
private:
std::string country;
int secret_number;
public:
world(const std::string& country) : secret_number(0) {
this->country = country;
}
std::string greet() const { return "Hello from " + country + "!"; }
std::string get_country() const { return country; }
};
// Support for pickle.
python::ref world_getinitargs(const world& w) {
python::tuple result(1);
result.set_item(0, w.get_country());
return result.reference();
}
}
BOOST_PYTHON_MODULE_INIT(pickle1)
{
try
{
// Create an object representing this extension module.
python::module_builder this_module("pickle1");
// Create the Python type object for our extension class.
python::class_builder<world> world_class(this_module, "world");
// Add the __init__ function.
world_class.def(python::constructor<std::string>());
// Add a regular member function.
world_class.def(&world::greet, "greet");
// Support for pickle.
world_class.def(world_getinitargs, "__getinitargs__");
}
catch(...)
{
python::handle_exception(); // Deal with the exception for Python
}
}

80
example/pickle2.cpp Normal file
View File

@@ -0,0 +1,80 @@
/*
This example shows how to make an Extension Class "pickleable".
For more information refer to boost/libs/python/doc/pickle.html.
*/
#include <string>
#include <boost/python/class_builder.hpp>
namespace python = boost::python;
namespace { // Avoid cluttering the global namespace.
// A friendly class.
class world
{
public:
world(const std::string& country) : secret_number(0) {
this->country = country;
}
std::string greet() const { return "Hello from " + country + "!"; }
std::string get_country() const { return country; }
void set_secret_number(int number) { secret_number = number; }
int get_secret_number() const { return secret_number; }
private:
std::string country;
int secret_number;
};
// Support for pickle.
python::ref world_getinitargs(const world& w) {
python::tuple result(1);
result.set_item(0, w.get_country());
return result.reference(); // returning the reference avoids the copying.
}
python::ref world_getstate(const world& w) {
python::tuple result(1);
result.set_item(0, w.get_secret_number());
return result.reference(); // returning the reference avoids the copying.
}
void world_setstate(world& w, python::tuple state) {
if (state.size() != 1) {
PyErr_SetString(PyExc_ValueError,
"Unexpected argument in call to __setstate__.");
throw python::error_already_set();
}
int number = state[0].get<int>();
if (number != 42)
w.set_secret_number(number);
}
}
BOOST_PYTHON_MODULE_INIT(pickle2)
{
try
{
// Create an object representing this extension module.
python::module_builder this_module("pickle2");
// Create the Python type object for our extension class.
python::class_builder<world> world_class(this_module, "world");
// Add the __init__ function.
world_class.def(python::constructor<std::string>());
// Add a regular member function.
world_class.def(&world::greet, "greet");
world_class.def(&world::get_secret_number, "get_secret_number");
world_class.def(&world::set_secret_number, "set_secret_number");
// Support for pickle.
world_class.def(world_getinitargs, "__getinitargs__");
world_class.def(world_getstate, "__getstate__");
world_class.def(world_setstate, "__setstate__");
}
catch(...)
{
python::handle_exception(); // Deal with the exception for Python
}
}

121
example/pickle3.cpp Normal file
View File

@@ -0,0 +1,121 @@
/*
This example shows how to make an Extension Class "pickleable".
For more information refer to boost/libs/python/doc/pickle.html.
*/
#include <string>
#include <boost/python/class_builder.hpp>
namespace python = boost::python;
namespace { // Avoid cluttering the global namespace.
// A friendly class.
class world
{
public:
world(const std::string& country) : secret_number(0) {
this->country = country;
}
std::string greet() const { return "Hello from " + country + "!"; }
std::string get_country() const { return country; }
void set_secret_number(int number) { secret_number = number; }
int get_secret_number() const { return secret_number; }
private:
std::string country;
int secret_number;
};
// Support for pickle.
python::ref world_getinitargs(const world& w) {
python::tuple result(1);
result.set_item(0, w.get_country());
return result.reference(); // returning the reference avoids the copying.
}
python::ref world_getstate(python::tuple const & args,
python::dictionary const & keywords);
PyObject* world_setstate(python::tuple const & args,
python::dictionary const & keywords);
}
BOOST_PYTHON_MODULE_INIT(pickle3)
{
try
{
// Create an object representing this extension module.
python::module_builder this_module("pickle3");
// Create the Python type object for our extension class.
python::class_builder<world> world_class(this_module, "world");
// Add the __init__ function.
world_class.def(python::constructor<std::string>());
// Add a regular member function.
world_class.def(&world::greet, "greet");
world_class.def(&world::get_secret_number, "get_secret_number");
world_class.def(&world::set_secret_number, "set_secret_number");
// Support for pickle.
world_class.def(world_getinitargs, "__getinitargs__");
world_class.def_raw(world_getstate, "__getstate__");
world_class.def_raw(world_setstate, "__setstate__");
world_class.getstate_manages_dict();
}
catch(...)
{
python::handle_exception(); // Deal with the exception for Python
}
}
namespace {
python::ref world_getstate(python::tuple const & args,
python::dictionary const & keywords)
{
if(args.size() != 1 || keywords.size() != 0) {
PyErr_SetString(PyExc_TypeError, "wrong number of arguments");
throw boost::python::argument_error();
}
const world& w = args[0].get<const world&>();
python::ref mydict(args[0].getattr("__dict__"));
python::tuple result(2);
// store the object's __dict__
result.set_item(0, mydict);
// store the internal state of the C++ object
result.set_item(1, w.get_secret_number());
return result.reference(); // returning the reference avoids the copying.
}
PyObject* world_setstate(python::tuple const & args,
python::dictionary const & keywords)
{
if(args.size() != 2 || keywords.size() != 0) {
PyErr_SetString(PyExc_TypeError, "wrong number of arguments");
throw boost::python::argument_error();
}
world& w = args[0].get<world&>();
python::ref mydict(args[0].getattr("__dict__"));
const python::tuple& state(args[1].get<python::tuple>());
if (state.size() != 2) {
PyErr_SetString(PyExc_ValueError,
"Unexpected argument in call to __setstate__.");
throw python::error_already_set();
}
// restore the object's __dict__
python::dictionary odict(mydict.get<python::dictionary>());
const python::dictionary& pdict(state[0].get<python::dictionary>());
python::list pkeys(pdict.keys());
for (int i = 0; i < pkeys.size(); i++) {
python::ref k(pkeys[i]);
//odict[k] = pdict[k]; // XXX memory leak!
odict[k] = pdict.get_item(k); // this does not leak.
}
// restore the internal state of the C++ object
int number = state[1].get<int>();
if (number != 42)
w.set_secret_number(number);
return python::detail::none();
}
}

31
example/test_pickle1.py Normal file
View File

@@ -0,0 +1,31 @@
r'''>>> import pickle1
>>> import re
>>> import pickle
>>> pickle1.world.__module__
'pickle1'
>>> pickle1.world.__safe_for_unpickling__
1
>>> pickle1.world.__reduce__()
'world'
>>> assert re.match(
... "\(<extension class pickle1.world at [0-9a-fA-FxX]+>, \('Hello',\)\)",
... repr(pickle1.world('Hello').__reduce__()))
>>>
>>> wd = pickle1.world('California')
>>> pstr = pickle.dumps(wd)
>>> wl = pickle.loads(pstr)
>>> print wd.greet()
Hello from California!
>>> print wl.greet()
Hello from California!
'''
def run(args = None):
if args is not None:
import sys
sys.argv = args
import doctest, test_pickle1
doctest.testmod(test_pickle1)
if __name__ == '__main__':
run()

45
example/test_pickle2.py Normal file
View File

@@ -0,0 +1,45 @@
r'''>>> import pickle2
>>> import re
>>> import pickle
>>> pickle2.world.__module__
'pickle2'
>>> pickle2.world.__safe_for_unpickling__
1
>>> pickle2.world.__reduce__()
'world'
>>> assert re.match(
... "\(<extension class pickle2.world at [0-9a-fA-FxX]+>, \('Hello',\), \(0,\)\)",
... repr(pickle2.world('Hello').__reduce__()))
>>>
>>> for number in (24, 42):
... wd = pickle2.world('California')
... wd.set_secret_number(number)
... pstr = pickle.dumps(wd)
... wl = pickle.loads(pstr)
... print wd.greet(), wd.get_secret_number()
... print wl.greet(), wl.get_secret_number()
Hello from California! 24
Hello from California! 24
Hello from California! 42
Hello from California! 0
# Now show that the __dict__ is not taken care of.
>>> wd = pickle2.world('California')
>>> wd.x = 1
>>> wd.__dict__
{'x': 1}
>>> try: pstr = pickle.dumps(wd)
... except RuntimeError, err: print err[0]
...
Incomplete pickle support (__getstate_manages_dict__ not set)
'''
def run(args = None):
if args is not None:
import sys
sys.argv = args
import doctest, test_pickle2
doctest.testmod(test_pickle2)
if __name__ == '__main__':
run()

38
example/test_pickle3.py Normal file
View File

@@ -0,0 +1,38 @@
r'''>>> import pickle3
>>> import re
>>> import pickle
>>> pickle3.world.__module__
'pickle3'
>>> pickle3.world.__safe_for_unpickling__
1
>>> pickle3.world.__reduce__()
'world'
>>> assert re.match(
... "\(<extension class pickle3.world at [0-9a-fA-FxX]+>, \('Hello',\), \(\{\}, 0\)\)",
... repr(pickle3.world('Hello').__reduce__()))
>>>
>>> for number in (24, 42):
... wd = pickle3.world('California')
... wd.set_secret_number(number)
... wd.x = 2 * number
... wd.y = 'y' * number
... wd.z = 3. * number
... pstr = pickle.dumps(wd)
... wl = pickle.loads(pstr)
... print wd.greet(), wd.get_secret_number(), wd.__dict__
... print wl.greet(), wl.get_secret_number(), wl.__dict__
Hello from California! 24 {'z': 72.0, 'x': 48, 'y': 'yyyyyyyyyyyyyyyyyyyyyyyy'}
Hello from California! 24 {'z': 72.0, 'x': 48, 'y': 'yyyyyyyyyyyyyyyyyyyyyyyy'}
Hello from California! 42 {'z': 126.0, 'x': 84, 'y': 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'}
Hello from California! 0 {'z': 126.0, 'x': 84, 'y': 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'}
'''
def run(args = None):
if args is not None:
import sys
sys.argv = args
import doctest, test_pickle3
doctest.testmod(test_pickle3)
if __name__ == '__main__':
run()