mirror of
https://github.com/boostorg/python.git
synced 2026-01-23 05:42:30 +00:00
Added dangling_reference FAQ
Various idiomatic MPL cleanups in indirect_traits.hpp raw_function support Patches for CWPro7.2 Patches to pass tests under Python 2.3 with the new bool type. Tests for member operators returning const objects Fixes for testing Boost.Python under Cygwin [SVN r17777]
This commit is contained in:
119
doc/v2/def.html
119
doc/v2/def.html
@@ -3,7 +3,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta name="generator" content=
|
||||
"HTML Tidy for Windows (vers 1st August 2002), see www.w3.org">
|
||||
"HTML Tidy for Cygwin (vers 1st April 2002), see www.w3.org">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
<link rel="stylesheet" type="text/css" href="../boost.css">
|
||||
|
||||
@@ -75,6 +75,12 @@ void def(char const* name, Fn fn, A1 const&, A2 const&, A3 const&);
|
||||
|
||||
<dd>
|
||||
<ul>
|
||||
<li>If <code>Fn</code> is [derived from] <code><a href=
|
||||
"object.html#object-spec">object</a></code>, it will be added to
|
||||
the current scope as a single overload. To be useful,
|
||||
<code>fn</code> should be <a href=
|
||||
"http://www.python.org/doc/current/lib/built-in-funcs.html#l2h-6">callable</a>.</li>
|
||||
|
||||
<li>
|
||||
If <code>a1</code> is the result of an <a href=
|
||||
"overloads.html#overload-dispatch-expression"><em>overload-dispatch-expression</em></a>,
|
||||
@@ -104,67 +110,52 @@ void def(char const* name, Fn fn, A1 const&, A2 const&, A3 const&);
|
||||
</dl>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Otherwise, a single function overload built around fn (which must
|
||||
not be null) is added to the <a href="scope.html">current
|
||||
scope</a>:
|
||||
|
||||
<ul>
|
||||
<li>If fn is a function or member function pointer,
|
||||
<code>a1</code>-<code>a3</code> (if supplied) may be selected
|
||||
in any order from the table below.</li>
|
||||
|
||||
<li>Otherwise, <code>Fn</code> must be [derived from] <code><a
|
||||
href="object.html#object-spec">object</a></code>, and
|
||||
<code>a1-a2</code> (if supplied) may be selcted in any order
|
||||
from the first two rows of the table below. To be useful,
|
||||
<code>fn</code> should be <a href=
|
||||
"http://www.python.org/doc/current/lib/built-in-funcs.html#l2h-6">
|
||||
callable</a>.</li>
|
||||
</ul>
|
||||
|
||||
<table border="1" summary="def() optional arguments">
|
||||
<tr>
|
||||
<th>Memnonic Name</th>
|
||||
|
||||
<th>Requirements/Type properties</th>
|
||||
|
||||
<th>Effects</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>docstring</td>
|
||||
|
||||
<td>Any <a href="definitions.html#ntbs">ntbs</a>.</td>
|
||||
|
||||
<td>Value will be bound to the <code>__doc__</code> attribute
|
||||
of the resulting method overload.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>policies</td>
|
||||
|
||||
<td>A model of <a href=
|
||||
"CallPolicies.html">CallPolicies</a></td>
|
||||
|
||||
<td>A copy will be used as the call policies of the resulting
|
||||
method overload.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>keywords</td>
|
||||
|
||||
<td>The result of a <a href=
|
||||
"args.html#keyword-expression"><em>keyword-expression</em></a>
|
||||
specifying no more arguments than the <a href=
|
||||
"definitions.html#arity">arity</a> of <code>fn</code>.</td>
|
||||
|
||||
<td>A copy will be used as the call policies of the resulting
|
||||
method overload.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</li>
|
||||
<li>Otherwise, fn must be a non-null function or member function
|
||||
pointer, and a single function overload built around fn is added to
|
||||
the <a href="scope.html">current scope</a>. If any of
|
||||
<code>a1</code>-<code>a3</code> are supplied, they may be selected
|
||||
in any order from the table below.</li>
|
||||
</ul>
|
||||
|
||||
<table border="1" summary="def() optional arguments">
|
||||
<tr>
|
||||
<th>Memnonic Name</th>
|
||||
|
||||
<th>Requirements/Type properties</th>
|
||||
|
||||
<th>Effects</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>docstring</td>
|
||||
|
||||
<td>Any <a href="definitions.html#ntbs">ntbs</a>.</td>
|
||||
|
||||
<td>Value will be bound to the <code>__doc__</code> attribute of
|
||||
the resulting method overload.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>policies</td>
|
||||
|
||||
<td>A model of <a href="CallPolicies.html">CallPolicies</a></td>
|
||||
|
||||
<td>A copy will be used as the call policies of the resulting
|
||||
method overload.</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>keywords</td>
|
||||
|
||||
<td>The result of a <a href=
|
||||
"args.html#keyword-expression"><em>keyword-expression</em></a>
|
||||
specifying no more arguments than the <a href=
|
||||
"definitions.html#arity">arity</a> of <code>fn</code>.</td>
|
||||
|
||||
<td>A copy will be used as the call policies of the resulting
|
||||
method overload.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
@@ -174,6 +165,8 @@ void def(char const* name, Fn fn, A1 const&, A2 const&, A3 const&);
|
||||
#include <boost/python/module.hpp>
|
||||
#include <boost/python/args.hpp>
|
||||
|
||||
using namespace boost::python;
|
||||
|
||||
char const* foo(int x, int y) { return "foo"; }
|
||||
|
||||
BOOST_PYTHON_MODULE(def_test)
|
||||
@@ -184,8 +177,8 @@ BOOST_PYTHON_MODULE(def_test)
|
||||
|
||||
<p>
|
||||
<!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%d %B, %Y" startspan -->
|
||||
13 November, 2002
|
||||
<!--webbot bot="Timestamp" endspan i-checksum="39359" -->
|
||||
7 March, 2003
|
||||
<!--webbot bot="Timestamp" endspan i-checksum="39359" -->
|
||||
</p>
|
||||
|
||||
<p><i>© Copyright <a href=
|
||||
|
||||
577
doc/v2/faq.html
577
doc/v2/faq.html
@@ -1,70 +1,95 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="generator" content=
|
||||
"HTML Tidy for Cygwin (vers 1st February 2003), see www.w3.org">
|
||||
"HTML Tidy for Cygwin (vers 1st April 2002), see www.w3.org">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">
|
||||
<link rel="stylesheet" type="text/css" href="../boost.css">
|
||||
<title>
|
||||
Boost.Python - FAQ
|
||||
</title>
|
||||
|
||||
<title>Boost.Python - FAQ</title>
|
||||
</head>
|
||||
|
||||
<body link="#0000ff" vlink="#800080">
|
||||
<table border="0" cellpadding="7" cellspacing="0" width="100%"
|
||||
summary="header">
|
||||
<table border="0" cellpadding="7" cellspacing="0" width="100%" summary=
|
||||
"header">
|
||||
<tr>
|
||||
<td valign="top" width="300">
|
||||
<h3>
|
||||
<a href="../../../../index.htm"><img height="86" width="277"
|
||||
alt="C++ Boost" src="../../../../c++boost.gif" border=
|
||||
"0"></a>
|
||||
</h3>
|
||||
<h3><a href="../../../../index.htm"><img height="86" width="277"
|
||||
alt="C++ Boost" src="../../../../c++boost.gif" border="0"></a></h3>
|
||||
</td>
|
||||
|
||||
<td valign="top">
|
||||
<h1 align="center">
|
||||
<a href="../index.html">Boost.Python</a>
|
||||
</h1>
|
||||
<h2 align="center">
|
||||
Frequently Asked Questions (FAQs)
|
||||
</h2>
|
||||
<h1 align="center"><a href="../index.html">Boost.Python</a></h1>
|
||||
|
||||
<h2 align="center">Frequently Asked Questions (FAQs)</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
|
||||
<dl class="page-index">
|
||||
<dt>
|
||||
<a href="#question1">Is return_internal_reference efficient?</a>
|
||||
</dt>
|
||||
<dt>
|
||||
<a href="#question2">How can I wrap functions which take C++
|
||||
containers as arguments?</a>
|
||||
</dt>
|
||||
<dt>
|
||||
<a href="#c1204">fatal error C1204:Compiler limit:internal structure
|
||||
overflow</a>
|
||||
</dt>
|
||||
<dt>
|
||||
<a href="#debugging">How do I debug my Python extensions?</a>
|
||||
</dt>
|
||||
<dt>
|
||||
<a href="#imul">Why doesn't my <code>*=</code> operator work?</a>
|
||||
</dt>
|
||||
<dt>
|
||||
<a href="#macosx">Does Boost.Python work with Mac OS X?</a>
|
||||
</dt>
|
||||
<dt>
|
||||
<a href="#xref">How can I find the existing PyObject that holds a C++
|
||||
object?</a>
|
||||
</dt>
|
||||
<dt>
|
||||
<a href="#ownership">How can I wrap a function which needs to take
|
||||
ownership of a raw pointer?</a>
|
||||
</dt>
|
||||
<dt><a href="#dangling">I'm getting the "attempt to return dangling
|
||||
reference" error. What am I doing wrong?</a></dt>
|
||||
|
||||
<dt><a href="#question1">Is return_internal_reference
|
||||
efficient?</a></dt>
|
||||
|
||||
<dt><a href="#question2">How can I wrap functions which take C++
|
||||
containers as arguments?</a></dt>
|
||||
|
||||
<dt><a href="#c1204">fatal error C1204:Compiler limit:internal
|
||||
structure overflow</a></dt>
|
||||
|
||||
<dt><a href="#debugging">How do I debug my Python extensions?</a></dt>
|
||||
|
||||
<dt><a href="#imul">Why doesn't my <code>*=</code> operator
|
||||
work?</a></dt>
|
||||
|
||||
<dt><a href="#macosx">Does Boost.Python work with Mac OS X?</a></dt>
|
||||
|
||||
<dt><a href="#xref">How can I find the existing PyObject that holds a
|
||||
C++ object?</a></dt>
|
||||
|
||||
<dt><a href="#ownership">How can I wrap a function which needs to take
|
||||
ownership of a raw pointer?</a></dt>
|
||||
</dl>
|
||||
<hr>
|
||||
<h2>
|
||||
<a name="question1"></a>Is return_internal_reference efficient?
|
||||
</h2>
|
||||
|
||||
<h2><a name="dangling">I'm getting the "attempt to return dangling
|
||||
reference" error. What am I doing wrong?</a></h2>
|
||||
That exception is protecting you from causing a nasty crash. It usually
|
||||
happens in response to some code like this:
|
||||
<pre>
|
||||
period const& get_floating_frequency() const
|
||||
{
|
||||
return boost::python::call_method<period const&>(
|
||||
m_self,"get_floating_frequency");
|
||||
}
|
||||
</pre>
|
||||
And you get:
|
||||
<pre>
|
||||
ReferenceError: Attempt to return dangling reference to object of type:
|
||||
class period
|
||||
</pre>
|
||||
|
||||
<p>In this case, the Python method invoked by <code>call_method</code>
|
||||
constructs a new Python object. You're trying to return a reference to a
|
||||
C++ object (an instance of <code>class period</code>) contained within
|
||||
and owned by that Python object. Because the called method handed back a
|
||||
brand new object, the only reference to it is held for the duration of
|
||||
<code>get_floating_frequency()</code> above. When the function returns,
|
||||
the Python object will be destroyed, destroying the instance of
|
||||
<code>class period</code>, and leaving the returned reference dangling.
|
||||
That's already undefined behavior, and if you try to do anything with
|
||||
that reference you're likely to cause a crash. Boost.Python detects this
|
||||
situation at runtime and helpfully throws an exception instead of letting
|
||||
you do that.<br>
|
||||
</p>
|
||||
<hr>
|
||||
|
||||
<h2><a name="question1"></a>Is return_internal_reference efficient?</h2>
|
||||
|
||||
<blockquote>
|
||||
<b>Q:</b> <i>I have an object composed of 12 doubles. A const& to
|
||||
this object is returned by a member function of another class. From the
|
||||
@@ -74,126 +99,129 @@
|
||||
return_internal_reference. Are there considerations that would lead me
|
||||
to prefer one over the other, such as size of generated code or memory
|
||||
overhead?</i>
|
||||
<p>
|
||||
<b>A:</b> copy_const_reference will make an instance with storage for
|
||||
one of your objects, size = base_size + 12 * sizeof(double).
|
||||
return_internal_reference will make an instance with storage for a
|
||||
pointer to one of your objects, size = base_size + sizeof(void*).
|
||||
However, it will also create a weak reference object which goes in
|
||||
the source object's weakreflist and a special callback object to
|
||||
manage the lifetime of the internally-referenced object. My guess?
|
||||
copy_const_reference is your friend here, resulting in less overall
|
||||
memory use and less fragmentation, also probably fewer total cycles.
|
||||
</p>
|
||||
|
||||
<p><b>A:</b> copy_const_reference will make an instance with storage
|
||||
for one of your objects, size = base_size + 12 * sizeof(double).
|
||||
return_internal_reference will make an instance with storage for a
|
||||
pointer to one of your objects, size = base_size + sizeof(void*).
|
||||
However, it will also create a weak reference object which goes in the
|
||||
source object's weakreflist and a special callback object to manage the
|
||||
lifetime of the internally-referenced object. My guess?
|
||||
copy_const_reference is your friend here, resulting in less overall
|
||||
memory use and less fragmentation, also probably fewer total
|
||||
cycles.</p>
|
||||
</blockquote>
|
||||
<hr>
|
||||
<h2>
|
||||
<a name="question2"></a>How can I wrap functions which take C++
|
||||
containers as arguments?
|
||||
</h2>
|
||||
<p>
|
||||
Ralf W. Grosse-Kunstleve provides these notes:
|
||||
</p>
|
||||
|
||||
<h2><a name="question2"></a>How can I wrap functions which take C++
|
||||
containers as arguments?</h2>
|
||||
|
||||
<p>Ralf W. Grosse-Kunstleve provides these notes:</p>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
Using the regular <code>class_<></code> wrapper:
|
||||
<pre>
|
||||
<pre>
|
||||
class_<std::vector<double> >("std_vector_double")
|
||||
.def(...)
|
||||
...
|
||||
;
|
||||
</pre>This can be moved to a template so that several types (double, int,
|
||||
long, etc.) can be wrapped with the same code. This technique is used in the
|
||||
file
|
||||
</pre>
|
||||
This can be moved to a template so that several types (double, int,
|
||||
long, etc.) can be wrapped with the same code. This technique is used
|
||||
in the file
|
||||
|
||||
<blockquote>
|
||||
scitbx/include/scitbx/array_family/boost_python/flex_wrapper.h
|
||||
</blockquote>in the "scitbx" package. The file could easily be
|
||||
modified for wrapping std::vector<> instantiations.
|
||||
<p>
|
||||
This type of C++/Python binding is most suitable for containers
|
||||
that may contain a large number of elements (>10000).
|
||||
</p>
|
||||
</blockquote>
|
||||
in the "scitbx" package. The file could easily be modified for
|
||||
wrapping std::vector<> instantiations.
|
||||
|
||||
<p>This type of C++/Python binding is most suitable for containers
|
||||
that may contain a large number of elements (>10000).</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Using custom rvalue converters. Boost.Python "rvalue converters"
|
||||
match function signatures such as:
|
||||
<pre>
|
||||
<pre>
|
||||
void foo(std::vector<double> const& array); // pass by const-reference
|
||||
void foo(std::vector<double> array); // pass by value
|
||||
</pre>Some custom rvalue converters are implemented in the file
|
||||
</pre>
|
||||
Some custom rvalue converters are implemented in the file
|
||||
|
||||
<blockquote>
|
||||
scitbx/include/scitbx/boost_python/container_conversions.h
|
||||
</blockquote>This code can be used to convert from C++ container
|
||||
types such as std::vector<> or std::list<> to Python
|
||||
tuples and vice versa. A few simple examples can be found in the file
|
||||
|
||||
</blockquote>
|
||||
This code can be used to convert from C++ container types such as
|
||||
std::vector<> or std::list<> to Python tuples and vice
|
||||
versa. A few simple examples can be found in the file
|
||||
|
||||
<blockquote>
|
||||
scitbx/array_family/boost_python/regression_test_module.cpp
|
||||
</blockquote>Automatic C++ container <-> Python tuple
|
||||
conversions are most suitable for containers of moderate size. These
|
||||
converters generate significantly less object code compared to
|
||||
alternative 1 above.
|
||||
</blockquote>
|
||||
Automatic C++ container <-> Python tuple conversions are most
|
||||
suitable for containers of moderate size. These converters generate
|
||||
significantly less object code compared to alternative 1 above.
|
||||
</li>
|
||||
</ol>A disadvantage of using alternative 2 is that operators such as
|
||||
</ol>
|
||||
A disadvantage of using alternative 2 is that operators such as
|
||||
arithmetic +,-,*,/,% are not available. It would be useful to have custom
|
||||
rvalue converters that convert to a "math_array" type instead of tuples.
|
||||
This is currently not implemented but is possible within the framework of
|
||||
Boost.Python V2 as it will be released in the next couple of weeks. [ed.:
|
||||
this was posted on 2002/03/10]
|
||||
<p>
|
||||
It would also be useful to also have "custom lvalue converters" such as
|
||||
std::vector<> <-> Python list. These converters would
|
||||
support the modification of the Python list from C++. For example:
|
||||
</p>
|
||||
<p>
|
||||
C++:
|
||||
</p>
|
||||
<pre>
|
||||
|
||||
<p>It would also be useful to also have "custom lvalue converters" such
|
||||
as std::vector<> <-> Python list. These converters would
|
||||
support the modification of the Python list from C++. For example:</p>
|
||||
|
||||
<p>C++:</p>
|
||||
<pre>
|
||||
void foo(std::vector<double>& array)
|
||||
{
|
||||
for(std::size_t i=0;i<array.size();i++) {
|
||||
array[i] *= 2;
|
||||
}
|
||||
}
|
||||
</pre>Python:
|
||||
<pre>
|
||||
</pre>
|
||||
Python:
|
||||
<pre>
|
||||
>>> l = [1, 2, 3]
|
||||
>>> foo(l)
|
||||
>>> print l
|
||||
[2, 4, 6]
|
||||
</pre>Custom lvalue converters require changes to the Boost.Python core
|
||||
library and are currently not available.
|
||||
<p>
|
||||
P.S.:
|
||||
</p>
|
||||
<p>
|
||||
The "scitbx" files referenced above are available via anonymous CVS:
|
||||
</p>
|
||||
<pre>
|
||||
</pre>
|
||||
Custom lvalue converters require changes to the Boost.Python core library
|
||||
and are currently not available.
|
||||
|
||||
<p>P.S.:</p>
|
||||
|
||||
<p>The "scitbx" files referenced above are available via anonymous
|
||||
CVS:</p>
|
||||
<pre>
|
||||
cvs -d:pserver:anonymous@cvs.cctbx.sourceforge.net:/cvsroot/cctbx login
|
||||
cvs -d:pserver:anonymous@cvs.cctbx.sourceforge.net:/cvsroot/cctbx co scitbx
|
||||
</pre>
|
||||
<hr>
|
||||
<h2>
|
||||
<a name="c1204"></a>fatal error C1204:Compiler limit:internal structure
|
||||
overflow
|
||||
</h2>
|
||||
|
||||
<h2><a name="c1204"></a>fatal error C1204:Compiler limit:internal
|
||||
structure overflow</h2>
|
||||
|
||||
<blockquote>
|
||||
<b>Q:</b> <i>I get this error message when compiling a large source
|
||||
file. What can I do?</i>
|
||||
<p>
|
||||
<b>A:</b> You have two choices:
|
||||
</p>
|
||||
|
||||
<p><b>A:</b> You have two choices:</p>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
Upgrade your compiler (preferred)
|
||||
</li>
|
||||
<li>Upgrade your compiler (preferred)</li>
|
||||
|
||||
<li>
|
||||
Break your source file up into multiple translation units.
|
||||
<p>
|
||||
<code><b>my_module.cpp</b></code>:
|
||||
</p>
|
||||
<pre>
|
||||
|
||||
<p><code><b>my_module.cpp</b></code>:</p>
|
||||
<pre>
|
||||
...
|
||||
void more_of_my_module();
|
||||
BOOST_PYTHON_MODULE(my_module)
|
||||
@@ -203,23 +231,25 @@ BOOST_PYTHON_MODULE(my_module)
|
||||
...
|
||||
more_of_my_module();
|
||||
}
|
||||
</pre><code><b>more_of_my_module.cpp</b></code>:
|
||||
<pre>
|
||||
</pre>
|
||||
<code><b>more_of_my_module.cpp</b></code>:
|
||||
<pre>
|
||||
void more_of_my_module()
|
||||
{
|
||||
def("baz", baz);
|
||||
...
|
||||
}
|
||||
</pre>If you find that a <code><a href=
|
||||
"class.html#class_-spec">class_</a><...></code> declaration can't fit
|
||||
in a single source file without triggering the error, you can always pass a
|
||||
reference to the <code>class_</code> object to a function in another source
|
||||
file, and call some of its member functions (e.g. <code>.def(...)</code>) in
|
||||
the auxilliary source file:
|
||||
<p>
|
||||
<code><b>more_of_my_class.cpp</b></code>:
|
||||
</p>
|
||||
<pre>
|
||||
</pre>
|
||||
If you find that a <code><a href=
|
||||
"class.html#class_-spec">class_</a><...></code> declaration
|
||||
can't fit in a single source file without triggering the error, you
|
||||
can always pass a reference to the <code>class_</code> object to a
|
||||
function in another source file, and call some of its member
|
||||
functions (e.g. <code>.def(...)</code>) in the auxilliary source
|
||||
file:
|
||||
|
||||
<p><code><b>more_of_my_class.cpp</b></code>:</p>
|
||||
<pre>
|
||||
void more_of_my_class(class<my_class>& x)
|
||||
{
|
||||
x
|
||||
@@ -234,12 +264,11 @@ void more_of_my_class(class<my_class>& x)
|
||||
</ol>
|
||||
</blockquote>
|
||||
<hr>
|
||||
<h2>
|
||||
<a name="debugging"></a>How do I debug my Python extensions?
|
||||
</h2>
|
||||
<p>
|
||||
Greg Burley gives the following answer for Unix GCC users:
|
||||
</p>
|
||||
|
||||
<h2><a name="debugging"></a>How do I debug my Python extensions?</h2>
|
||||
|
||||
<p>Greg Burley gives the following answer for Unix GCC users:</p>
|
||||
|
||||
<blockquote>
|
||||
Once you have created a boost python extension for your c++ library or
|
||||
class, you may need to debug the code. Afterall this is one of the
|
||||
@@ -249,13 +278,12 @@ void more_of_my_class(class<my_class>& x)
|
||||
boost::python either works or it doesn't. (ie. While errors can occur
|
||||
when the wrapping method is invalid, most errors are caught by the
|
||||
compiler ;-).
|
||||
<p>
|
||||
The basic steps required to initiate a gdb session to debug a c++
|
||||
library via python are shown here. Note, however that you should
|
||||
start the gdb session in the directory that contains your BPL
|
||||
my_ext.so module.
|
||||
</p>
|
||||
<pre>
|
||||
|
||||
<p>The basic steps required to initiate a gdb session to debug a c++
|
||||
library via python are shown here. Note, however that you should start
|
||||
the gdb session in the directory that contains your BPL my_ext.so
|
||||
module.</p>
|
||||
<pre>
|
||||
(gdb) target exec python
|
||||
(gdb) run
|
||||
>>> from my_ext import *
|
||||
@@ -269,44 +297,43 @@ Current language: auto; currently c++
|
||||
(gdb) do debugging stuff
|
||||
</pre>
|
||||
</blockquote>
|
||||
<p>
|
||||
Greg's approach works even better using Emacs' "<code>gdb</code>"
|
||||
command, since it will show you each line of source as you step through
|
||||
it.
|
||||
</p>
|
||||
<p>
|
||||
On <b>Windows</b>, my favorite debugging solution is the debugger that
|
||||
comes with Microsoft Visual C++ 7. This debugger seems to work with
|
||||
code generated by all versions of Microsoft and Metrowerks toolsets;
|
||||
it's rock solid and "just works" without requiring any special tricks
|
||||
from the user.
|
||||
</p>
|
||||
<p>
|
||||
Unfortunately for Cygwin and MinGW users, as of this writing gdb on
|
||||
Windows has a very hard time dealing with shared libraries, which could
|
||||
make Greg's approach next to useless for you. My best advice for you is
|
||||
to use Metrowerks C++ for compiler conformance and Microsoft Visual
|
||||
Studio as a debugger when you need one.
|
||||
</p>
|
||||
<h3>
|
||||
Debugging extensions through Boost.Build
|
||||
</h3>If you are launching your extension module tests with <a href=
|
||||
|
||||
<p>Greg's approach works even better using Emacs' "<code>gdb</code>"
|
||||
command, since it will show you each line of source as you step through
|
||||
it.</p>
|
||||
|
||||
<p>On <b>Windows</b>, my favorite debugging solution is the debugger that
|
||||
comes with Microsoft Visual C++ 7. This debugger seems to work with code
|
||||
generated by all versions of Microsoft and Metrowerks toolsets; it's rock
|
||||
solid and "just works" without requiring any special tricks from the
|
||||
user.</p>
|
||||
|
||||
<p>Unfortunately for Cygwin and MinGW users, as of this writing gdb on
|
||||
Windows has a very hard time dealing with shared libraries, which could
|
||||
make Greg's approach next to useless for you. My best advice for you is
|
||||
to use Metrowerks C++ for compiler conformance and Microsoft Visual
|
||||
Studio as a debugger when you need one.</p>
|
||||
|
||||
<h3>Debugging extensions through Boost.Build</h3>
|
||||
If you are launching your extension module tests with <a href=
|
||||
"../../../tools/build">Boost.Build</a> using the
|
||||
<code>boost-python-runtest</code> rule, you can ask it to launch your
|
||||
debugger for you by adding "-sPYTHON_LAUNCH=<i>debugger</i>" to your bjam
|
||||
command-line:
|
||||
<pre>
|
||||
<pre>
|
||||
bjam -sTOOLS=metrowerks "-sPYTHON_LAUNCH=devenv /debugexe" test
|
||||
bjam -sTOOLS=gcc -sPYTHON_LAUNCH=gdb test
|
||||
</pre>It can also be extremely useful to add the <code>-d+2</code> option
|
||||
when you run your test, because Boost.Build will then show you the exact
|
||||
commands it uses to invoke it. This will invariably involve setting up
|
||||
PYTHONPATH and other important environment variables such as LD_LIBRARY_PATH
|
||||
which may be needed by your debugger in order to get things to work right.
|
||||
</pre>
|
||||
It can also be extremely useful to add the <code>-d+2</code> option when
|
||||
you run your test, because Boost.Build will then show you the exact
|
||||
commands it uses to invoke it. This will invariably involve setting up
|
||||
PYTHONPATH and other important environment variables such as
|
||||
LD_LIBRARY_PATH which may be needed by your debugger in order to get
|
||||
things to work right.
|
||||
<hr>
|
||||
<h2>
|
||||
<a name="imul"></a>Why doesn't my <code>*=</code> operator work?
|
||||
</h2>
|
||||
|
||||
<h2><a name="imul"></a>Why doesn't my <code>*=</code> operator work?</h2>
|
||||
|
||||
<blockquote>
|
||||
<b>Q:</b> <i>I have exported my class to python, with many overloaded
|
||||
operators. it works fine for me except the</i> <code>*=</code>
|
||||
@@ -314,60 +341,57 @@ which may be needed by your debugger in order to get things to work right.
|
||||
type". If I use</i> <code>p1.__imul__(p2)</code> <i>instead of</i>
|
||||
<code>p1 *= p2</code><i>, it successfully executes my code. What's
|
||||
wrong with me?</i>
|
||||
<p>
|
||||
<b>A:</b> There's nothing wrong with you. This is a bug in Python
|
||||
2.2. You can see the same effect in Pure Python (you can learn a lot
|
||||
about what's happening in Boost.Python by playing with new-style
|
||||
classes in Pure Python).
|
||||
</p>
|
||||
<pre>
|
||||
|
||||
<p><b>A:</b> There's nothing wrong with you. This is a bug in Python
|
||||
2.2. You can see the same effect in Pure Python (you can learn a lot
|
||||
about what's happening in Boost.Python by playing with new-style
|
||||
classes in Pure Python).</p>
|
||||
<pre>
|
||||
>>> class X(object):
|
||||
... def __imul__(self, x):
|
||||
... print 'imul'
|
||||
...
|
||||
>>> x = X()
|
||||
>>> x *= 1
|
||||
</pre>To cure this problem, all you need to do is upgrade your Python to
|
||||
version 2.2.1 or later.
|
||||
</pre>
|
||||
To cure this problem, all you need to do is upgrade your Python to
|
||||
version 2.2.1 or later.
|
||||
</blockquote>
|
||||
<hr>
|
||||
<h2>
|
||||
<a name="macosx"></a>Does Boost.Python work with Mac OS X?
|
||||
</h2>
|
||||
|
||||
<h2><a name="macosx"></a>Does Boost.Python work with Mac OS X?</h2>
|
||||
|
||||
<blockquote>
|
||||
<p>
|
||||
The short answer: as of January 2003, unfortunately not.
|
||||
</p>
|
||||
<p>
|
||||
The longer answer: using Mac OS 10.2.3 with the December Developer's
|
||||
Kit, Python 2.3a1, and bjam's darwin-tools.jam, Boost.Python compiles
|
||||
fine, including the examples. However, there are problems at runtime
|
||||
(see <a href=
|
||||
"http://mail.python.org/pipermail/c++-sig/2003-January/003267.html">http://mail.python.org/pipermail/c++-sig/2003-January/003267.html</a>).
|
||||
Solutions are currently unknown.
|
||||
</p>
|
||||
<p>
|
||||
It is known that under certain circumstances objects are
|
||||
double-destructed. See <a href=
|
||||
"http://mail.python.org/pipermail/c++-sig/2003-January/003278.html">http://mail.python.org/pipermail/c++-sig/2003-January/003278.html</a>
|
||||
for details. It is not clear however if this problem is related to
|
||||
the Boost.Python runtime issues.
|
||||
</p>
|
||||
<p>The short answer: as of January 2003, unfortunately not.</p>
|
||||
|
||||
<p>The longer answer: using Mac OS 10.2.3 with the December Developer's
|
||||
Kit, Python 2.3a1, and bjam's darwin-tools.jam, Boost.Python compiles
|
||||
fine, including the examples. However, there are problems at runtime
|
||||
(see <a href=
|
||||
"http://mail.python.org/pipermail/c++-sig/2003-January/003267.html">http://mail.python.org/pipermail/c++-sig/2003-January/003267.html</a>).
|
||||
Solutions are currently unknown.</p>
|
||||
|
||||
<p>It is known that under certain circumstances objects are
|
||||
double-destructed. See <a href=
|
||||
"http://mail.python.org/pipermail/c++-sig/2003-January/003278.html">http://mail.python.org/pipermail/c++-sig/2003-January/003278.html</a>
|
||||
for details. It is not clear however if this problem is related to the
|
||||
Boost.Python runtime issues.</p>
|
||||
</blockquote>
|
||||
<hr>
|
||||
<h2>
|
||||
<a name="xref">How can I find the existing PyObject that holds a C++
|
||||
object?</a>
|
||||
</h2>
|
||||
|
||||
<h2><a name="xref">How can I find the existing PyObject that holds a C++
|
||||
object?</a></h2>
|
||||
|
||||
<blockquote>
|
||||
"I am wrapping a function that always returns a pointer to an
|
||||
already-held C++ object."
|
||||
</blockquote>One way to do that is to hijack the mechanisms used for
|
||||
wrapping a class with virtual functions. If you make a wrapper class with
|
||||
an initial PyObject* constructor argument and store that PyObject* as
|
||||
"self", you can get back to it by casting down to that wrapper type in a
|
||||
thin wrapper function. For example:
|
||||
<pre>
|
||||
</blockquote>
|
||||
One way to do that is to hijack the mechanisms used for wrapping a class
|
||||
with virtual functions. If you make a wrapper class with an initial
|
||||
PyObject* constructor argument and store that PyObject* as "self", you
|
||||
can get back to it by casting down to that wrapper type in a thin wrapper
|
||||
function. For example:
|
||||
<pre>
|
||||
class X { X(int); virtual ~X(); ... };
|
||||
X* f(); // known to return Xs that are managed by Python objects
|
||||
|
||||
@@ -393,44 +417,43 @@ def("f", f_wrap());
|
||||
class_<X,X_wrap>("X", init<int>())
|
||||
...
|
||||
;
|
||||
</pre>Of course, if X has no virtual functions you'll have to use
|
||||
<code>static_cast</code> instead of <code>dynamic_cast</code> with no runtime
|
||||
check that it's valid. This approach also only works if the <code>X</code>
|
||||
object was constructed from Python, because <code>X</code>s constructed from
|
||||
C++ are of course never <code>X_wrap</code> objects.
|
||||
<p>
|
||||
Another approach to this requires some work on Boost.Python, but it's
|
||||
work we've been meaning to get to anyway. Currently, when a
|
||||
<code>shared_ptr<X></code> is converted from Python, the
|
||||
shared_ptr actually manages a reference to the containing Python
|
||||
object. I plan to make it so that when a shared_ptr<X> is
|
||||
converted back to Python, the library checks to see if it's one of
|
||||
those "Python object managers" and if so just returns the original
|
||||
Python object. To exploit this you'd have to be able to change the C++
|
||||
code you're wrapping so that it deals with shared_ptr instead of raw
|
||||
pointers.
|
||||
</p>
|
||||
<p>
|
||||
There are other approaches too. The functions that receive the Python
|
||||
object that you eventually want to return could be wrapped with a thin
|
||||
wrapper that records the correspondence between the object address and
|
||||
its containing Python object, and you could have your f_wrap function
|
||||
look in that mapping to get the Python object out.
|
||||
</p>
|
||||
<h2>
|
||||
<a name="ownership">How can I wrap a function which needs to take
|
||||
ownership of a raw pointer?</a>
|
||||
</h2>
|
||||
</pre>
|
||||
Of course, if X has no virtual functions you'll have to use
|
||||
<code>static_cast</code> instead of <code>dynamic_cast</code> with no
|
||||
runtime check that it's valid. This approach also only works if the
|
||||
<code>X</code> object was constructed from Python, because
|
||||
<code>X</code>s constructed from C++ are of course never
|
||||
<code>X_wrap</code> objects.
|
||||
|
||||
<p>Another approach to this requires you to change your C++ code a bit;
|
||||
if that's an option for you it might be a better way to go. work we've
|
||||
been meaning to get to anyway. When a <code>shared_ptr<X></code> is
|
||||
converted from Python, the shared_ptr actually manages a reference to the
|
||||
containing Python object. When a shared_ptr<X> is converted back to
|
||||
Python, the library checks to see if it's one of those "Python object
|
||||
managers" and if so just returns the original Python object. So you could
|
||||
just write <code>object(p)</code> to get the Python object back. To
|
||||
exploit this you'd have to be able to change the C++ code you're wrapping
|
||||
so that it deals with shared_ptr instead of raw pointers.</p>
|
||||
|
||||
<p>There are other approaches too. The functions that receive the Python
|
||||
object that you eventually want to return could be wrapped with a thin
|
||||
wrapper that records the correspondence between the object address and
|
||||
its containing Python object, and you could have your f_wrap function
|
||||
look in that mapping to get the Python object out.</p>
|
||||
|
||||
<h2><a name="ownership">How can I wrap a function which needs to take
|
||||
ownership of a raw pointer?</a></h2>
|
||||
|
||||
<blockquote>
|
||||
<i>Part of an API that I'm wrapping goes something like this:</i>
|
||||
<pre>
|
||||
<pre>
|
||||
struct A {}; struct B { void add( A* ); }
|
||||
where B::add() takes ownership of the pointer passed to it.
|
||||
</pre>
|
||||
<p>
|
||||
<i>However:</i>
|
||||
</p>
|
||||
<pre>
|
||||
|
||||
<p><i>However:</i></p>
|
||||
<pre>
|
||||
a = mod.A()
|
||||
b = mod.B()
|
||||
b.add( a )
|
||||
@@ -439,41 +462,43 @@ del b
|
||||
# python interpreter crashes
|
||||
# later due to memory corruption.
|
||||
</pre>
|
||||
<p>
|
||||
<i>Even binding the lifetime of a</i> to b via
|
||||
with_custodian_and_ward doesn't prevent the python object a from
|
||||
ultimately trying to delete the object it's pointing to. Is there a
|
||||
way to accomplish a 'transfer-of-ownership' of a wrapped C++ object?
|
||||
</p>
|
||||
<p>
|
||||
<i>--Bruce Lowery</i>
|
||||
</p>
|
||||
</blockquote>Yes: Make sure the C++ object is held by auto_ptr:
|
||||
<pre>
|
||||
|
||||
<p><i>Even binding the lifetime of a</i> to b via
|
||||
with_custodian_and_ward doesn't prevent the python object a from
|
||||
ultimately trying to delete the object it's pointing to. Is there a way
|
||||
to accomplish a 'transfer-of-ownership' of a wrapped C++ object?</p>
|
||||
|
||||
<p><i>--Bruce Lowery</i></p>
|
||||
</blockquote>
|
||||
Yes: Make sure the C++ object is held by auto_ptr:
|
||||
<pre>
|
||||
class_<A, std::auto_ptr<A> >("A")
|
||||
...
|
||||
;
|
||||
</pre>Then make a thin wrapper function which takes an auto_ptr parameter:
|
||||
<pre>
|
||||
</pre>
|
||||
Then make a thin wrapper function which takes an auto_ptr parameter:
|
||||
<pre>
|
||||
void b_insert(B& b, std::auto_ptr<A> a)
|
||||
{
|
||||
b.insert(a.get());
|
||||
a.release();
|
||||
}
|
||||
</pre>Wrap that as B.add. Note that pointers returned via
|
||||
<code><a href="manage_new_object.html#manage_new_object-spec">manage_new_object</a></code>
|
||||
will also be held by <code>auto_ptr</code>, so this
|
||||
transfer-of-ownership will also work correctly.
|
||||
</pre>
|
||||
Wrap that as B.add. Note that pointers returned via <code><a href=
|
||||
"manage_new_object.html#manage_new_object-spec">manage_new_object</a></code>
|
||||
will also be held by <code>auto_ptr</code>, so this transfer-of-ownership
|
||||
will also work correctly.
|
||||
<hr>
|
||||
<p>
|
||||
Revised
|
||||
<!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%d %B, %Y" startspan -->
|
||||
23 January, 2003
|
||||
<!--webbot bot="Timestamp" endspan i-checksum="39359" -->
|
||||
</p>
|
||||
<p>
|
||||
<i>© Copyright <a href="../../../../people/dave_abrahams.htm">Dave
|
||||
Abrahams</a> 2002-2003. All Rights Reserved.</i>
|
||||
|
||||
<p>Revised
|
||||
<!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%d %B, %Y" startspan -->
|
||||
23 January, 2003
|
||||
<!--webbot bot="Timestamp" endspan i-checksum="39359" -->
|
||||
</p>
|
||||
|
||||
<p><i>© Copyright <a href=
|
||||
"../../../../people/dave_abrahams.htm">Dave Abrahams</a> 2002-2003. All
|
||||
Rights Reserved.</i></p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta name="generator" content=
|
||||
"HTML Tidy for Windows (vers 1st August 2002), see www.w3.org">
|
||||
"HTML Tidy for Cygwin (vers 1st April 2002), see www.w3.org">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
<link rel="stylesheet" type="text/css" href="../boost.css">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
p.c3 {font-style: italic}
|
||||
h2.c2 {text-align: center}
|
||||
h1.c1 {text-align: center}
|
||||
</style>
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -527,6 +527,24 @@
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
|
||||
<dt><a href="raw_function.html">raw_function.hpp</a></dt>
|
||||
|
||||
<dd>
|
||||
<dl class="index">
|
||||
<dt><a href="raw_function.html#functions">Functions</a></dt>
|
||||
|
||||
<dd>
|
||||
<dl class="page-index">
|
||||
<dt><a href=
|
||||
"raw_function.html#raw_function-spec">raw_function</a></dt>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
|
||||
<dd>
|
||||
<a name="models_of_call_policies"></a>
|
||||
|
||||
<h3>Models of CallPolicies</h3>
|
||||
@@ -913,8 +931,8 @@
|
||||
|
||||
<p>Revised
|
||||
<!--webbot bot="Timestamp" S-Type="EDITED" S-Format="%d %B, %Y" startspan -->
|
||||
13 November, 2002
|
||||
<!--webbot bot="Timestamp" endspan i-checksum="39359" -->
|
||||
7 March, 2003
|
||||
<!--webbot bot="Timestamp" endspan i-checksum="39359" -->
|
||||
</p>
|
||||
|
||||
<p class="c3">© Copyright <a href=
|
||||
|
||||
@@ -132,7 +132,9 @@ namespace detail
|
||||
must_be_derived_class_member(Default const&)
|
||||
{
|
||||
typedef typename assertion<mpl::not_<is_same<Default,Fn> > >::failed test0;
|
||||
# if !BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
typedef typename assertion<is_polymorphic<T> >::failed test1;
|
||||
# endif
|
||||
typedef typename assertion<is_member_function_pointer<Fn> >::failed test2;
|
||||
not_a_derived_class_member<Default>(Fn());
|
||||
}
|
||||
|
||||
@@ -76,12 +76,17 @@ namespace detail
|
||||
detail::define_with_defaults(
|
||||
name, stubs, current, detail::get_signature(sig));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
object make_function1(T fn, ...) { return make_function(fn); }
|
||||
|
||||
object make_function1(object const& x, object const*) { return x; }
|
||||
}
|
||||
|
||||
template <class Fn>
|
||||
void def(char const* name, Fn fn)
|
||||
{
|
||||
detail::scope_setattr_doc(name, boost::python::make_function(fn), 0);
|
||||
detail::scope_setattr_doc(name, detail::make_function1(fn, &fn), 0);
|
||||
}
|
||||
|
||||
template <class Arg1T, class Arg2T>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#ifndef INDIRECT_TRAITS_DWA2002131_HPP
|
||||
# define INDIRECT_TRAITS_DWA2002131_HPP
|
||||
# include <boost/type_traits/is_function.hpp>
|
||||
# include <boost/type_traits/detail/ice_and.hpp>
|
||||
# include <boost/type_traits/is_reference.hpp>
|
||||
# include <boost/type_traits/is_pointer.hpp>
|
||||
# include <boost/type_traits/is_class.hpp>
|
||||
@@ -16,8 +15,18 @@
|
||||
# include <boost/type_traits/remove_cv.hpp>
|
||||
# include <boost/type_traits/remove_reference.hpp>
|
||||
# include <boost/type_traits/remove_pointer.hpp>
|
||||
|
||||
# include <boost/type_traits/detail/ice_and.hpp>
|
||||
|
||||
# include <boost/detail/workaround.hpp>
|
||||
# if 0 && BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
# include <boost/type_traits/is_enum.hpp>
|
||||
# endif
|
||||
|
||||
# include <boost/mpl/if.hpp>
|
||||
# include <boost/mpl/bool.hpp>
|
||||
# include <boost/mpl/and.hpp>
|
||||
# include <boost/mpl/not.hpp>
|
||||
# include <boost/mpl/aux_/lambda_support.hpp>
|
||||
|
||||
# ifdef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
|
||||
@@ -28,27 +37,24 @@ namespace boost { namespace python { namespace detail {
|
||||
|
||||
# ifndef BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION
|
||||
template <class T>
|
||||
struct is_reference_to_const
|
||||
struct is_reference_to_const : mpl::false_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = false);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_const<T const&>
|
||||
struct is_reference_to_const<T const&> : mpl::true_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = true);
|
||||
};
|
||||
|
||||
# if defined(BOOST_MSVC) && _MSC_FULL_VER <= 13102140 // vc7.01 alpha workaround
|
||||
template<class T>
|
||||
struct is_reference_to_const<T const volatile&>
|
||||
struct is_reference_to_const<T const volatile&> : mpl::true_
|
||||
{
|
||||
static const bool value = true;
|
||||
};
|
||||
# endif
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_function : mpl::bool_<false>
|
||||
struct is_reference_to_function : mpl::false_
|
||||
{
|
||||
};
|
||||
|
||||
@@ -58,9 +64,8 @@ struct is_reference_to_function<T&> : is_function<T>
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_pointer_to_function : mpl::bool_<false>
|
||||
struct is_pointer_to_function : mpl::false_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = false);
|
||||
};
|
||||
|
||||
// There's no such thing as a pointer-to-cv-function, so we don't need
|
||||
@@ -71,7 +76,7 @@ struct is_pointer_to_function<T*> : is_function<T>
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_member_function_pointer_impl : mpl::bool_<false>
|
||||
struct is_reference_to_member_function_pointer_impl : mpl::false_
|
||||
{
|
||||
};
|
||||
|
||||
@@ -91,23 +96,23 @@ struct is_reference_to_member_function_pointer
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_function_pointer_aux
|
||||
: mpl::and_<
|
||||
is_reference<T>
|
||||
, is_pointer_to_function<
|
||||
typename remove_cv<
|
||||
typename remove_reference<T>::type
|
||||
>::type
|
||||
>
|
||||
>
|
||||
{
|
||||
// There's no such thing as a pointer-to-cv-function, so we don't need specializations for those
|
||||
BOOST_STATIC_CONSTANT(bool, value = (
|
||||
is_reference<T>::value
|
||||
& is_pointer_to_function<
|
||||
typename remove_cv<
|
||||
typename remove_reference<T>::type
|
||||
>::type
|
||||
>::value));
|
||||
typedef mpl::bool_<value> type;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_function_pointer
|
||||
: mpl::if_c<
|
||||
is_reference_to_function<T>::value
|
||||
, mpl::bool_<false>
|
||||
: mpl::if_<
|
||||
is_reference_to_function<T>
|
||||
, mpl::false_
|
||||
, is_reference_to_function_pointer_aux<T>
|
||||
>::type
|
||||
{
|
||||
@@ -115,70 +120,60 @@ struct is_reference_to_function_pointer
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_non_const
|
||||
: mpl::and_<
|
||||
is_reference<T>
|
||||
, mpl::not_<
|
||||
is_reference_to_const<T>
|
||||
>
|
||||
>
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(
|
||||
bool, value = (
|
||||
::boost::type_traits::ice_and<
|
||||
::boost::is_reference<T>::value
|
||||
, ::boost::type_traits::ice_not<
|
||||
::boost::python::detail::is_reference_to_const<T>::value>::value
|
||||
>::value)
|
||||
);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_volatile
|
||||
struct is_reference_to_volatile : mpl::false_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = false);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_volatile<T volatile&>
|
||||
struct is_reference_to_volatile<T volatile&> : mpl::true_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = true);
|
||||
};
|
||||
|
||||
# if defined(BOOST_MSVC) && _MSC_FULL_VER <= 13102140 // vc7.01 alpha workaround
|
||||
template <class T>
|
||||
struct is_reference_to_volatile<T const volatile&>
|
||||
struct is_reference_to_volatile<T const volatile&> : mpl::true_
|
||||
{
|
||||
static const bool value = true;
|
||||
};
|
||||
# endif
|
||||
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_pointer
|
||||
struct is_reference_to_pointer : mpl::false_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = false);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_pointer<T*&>
|
||||
struct is_reference_to_pointer<T*&> : mpl::true_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = true);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_pointer<T* const&>
|
||||
struct is_reference_to_pointer<T* const&> : mpl::true_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = true);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_pointer<T* volatile&>
|
||||
struct is_reference_to_pointer<T* volatile&> : mpl::true_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = true);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_pointer<T* const volatile&>
|
||||
struct is_reference_to_pointer<T* const volatile&> : mpl::true_
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(bool, value = true);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_reference_to_class
|
||||
struct is_reference_to_classx
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(
|
||||
bool, value
|
||||
@@ -196,19 +191,47 @@ struct is_reference_to_class
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_pointer_to_class
|
||||
struct is_reference_to_class
|
||||
: mpl::and_<
|
||||
is_reference<T>
|
||||
# if 0 && BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
, mpl::not_<
|
||||
is_enum<
|
||||
typename remove_cv<
|
||||
typename remove_reference<T>::type
|
||||
>::type
|
||||
>
|
||||
>
|
||||
# endif
|
||||
, is_class<
|
||||
typename remove_cv<
|
||||
typename remove_reference<T>::type
|
||||
>::type
|
||||
>
|
||||
>
|
||||
{
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct is_pointer_to_class
|
||||
: mpl::and_<
|
||||
is_pointer<T>
|
||||
# if 0 && BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
, mpl::not_<
|
||||
is_enum<
|
||||
typename remove_cv<
|
||||
typename remove_pointer<T>::type
|
||||
>::type
|
||||
>
|
||||
>
|
||||
# endif
|
||||
, is_class<
|
||||
typename remove_cv<
|
||||
typename remove_pointer<T>::type
|
||||
>::type
|
||||
>
|
||||
>
|
||||
{
|
||||
BOOST_STATIC_CONSTANT(
|
||||
bool, value
|
||||
= (boost::type_traits::ice_and<
|
||||
is_pointer<T>::value
|
||||
, is_class<
|
||||
typename remove_cv<
|
||||
typename remove_pointer<T>::type
|
||||
>::type
|
||||
>::value
|
||||
>::value)
|
||||
);
|
||||
};
|
||||
|
||||
# else
|
||||
@@ -220,8 +243,8 @@ typedef char (&outer_no_type)[1];
|
||||
template <typename V>
|
||||
struct is_const_help
|
||||
{
|
||||
typedef typename mpl::if_c<
|
||||
is_const<V>::value
|
||||
typedef typename mpl::if_<
|
||||
is_const<V>
|
||||
, inner_yes_type
|
||||
, inner_no_type
|
||||
>::type type;
|
||||
@@ -230,8 +253,8 @@ struct is_const_help
|
||||
template <typename V>
|
||||
struct is_volatile_help
|
||||
{
|
||||
typedef typename mpl::if_c<
|
||||
is_volatile<V>::value
|
||||
typedef typename mpl::if_<
|
||||
is_volatile<V>
|
||||
, inner_yes_type
|
||||
, inner_no_type
|
||||
>::type type;
|
||||
@@ -240,8 +263,8 @@ struct is_volatile_help
|
||||
template <typename V>
|
||||
struct is_pointer_help
|
||||
{
|
||||
typedef typename mpl::if_c<
|
||||
is_pointer<V>::value
|
||||
typedef typename mpl::if_<
|
||||
is_pointer<V>
|
||||
, inner_yes_type
|
||||
, inner_no_type
|
||||
>::type type;
|
||||
@@ -250,8 +273,8 @@ struct is_pointer_help
|
||||
template <typename V>
|
||||
struct is_class_help
|
||||
{
|
||||
typedef typename mpl::if_c<
|
||||
is_class<V>::value
|
||||
typedef typename mpl::if_<
|
||||
is_class<V>
|
||||
, inner_yes_type
|
||||
, inner_no_type
|
||||
>::type type;
|
||||
|
||||
@@ -315,7 +315,10 @@ namespace detail
|
||||
, mpl::push_front<>
|
||||
>::type args;
|
||||
|
||||
typedef typename ClassT::holder_selector::type selector_t;
|
||||
typedef typename ClassT::holder_selector holder_selector_t;
|
||||
# if !BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
typedef typename holder_selector_t::type selector_t;
|
||||
# endif
|
||||
typedef typename ClassT::held_type held_type_t;
|
||||
|
||||
cl.def(
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
# include <boost/mpl/if.hpp>
|
||||
# include <boost/type_traits/object_traits.hpp>
|
||||
# include <boost/type_traits/is_polymorphic.hpp>
|
||||
# include <boost/detail/workaround.hpp>
|
||||
|
||||
namespace boost { namespace python { namespace objects {
|
||||
|
||||
@@ -108,16 +109,20 @@ struct implicit_cast_generator
|
||||
template <class Source, class Target>
|
||||
struct cast_generator
|
||||
{
|
||||
// CWPro7 will return false sometimes, but that's OK since we can
|
||||
// always cast up with dynamic_cast<>
|
||||
// It's OK to return false, since we can always cast up with
|
||||
// dynamic_cast<> if neccessary.
|
||||
# if BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
BOOST_STATIC_CONSTANT(bool, is_upcast = false);
|
||||
# else
|
||||
BOOST_STATIC_CONSTANT(
|
||||
bool, is_upcast = (
|
||||
is_base_and_derived<Target,Source>::value
|
||||
));
|
||||
# endif
|
||||
|
||||
typedef typename mpl::if_c<
|
||||
is_upcast
|
||||
# if defined(__MWERKS__) && __MWERKS__ <= 0x2406
|
||||
# if BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
// grab a few more implicit_cast cases for CodeWarrior
|
||||
|| !is_polymorphic<Source>::value
|
||||
|| !is_polymorphic<Target>::value
|
||||
|
||||
@@ -48,8 +48,14 @@ struct make_ptr_instance
|
||||
}
|
||||
|
||||
template <class U>
|
||||
static inline PyTypeObject* get_derived_class_object(mpl::false_, U*)
|
||||
static inline PyTypeObject* get_derived_class_object(mpl::false_, U* x)
|
||||
{
|
||||
# if BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
if (typeid(*x) != typeid(U))
|
||||
return get_derived_class_object(mpl::true_(), x);
|
||||
# else
|
||||
(void)x;
|
||||
# endif
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,10 +39,15 @@ function::function(
|
||||
unsigned keyword_offset
|
||||
= m_max_arity > num_keywords ? m_max_arity - num_keywords : 0;
|
||||
|
||||
|
||||
m_arg_names = object(handle<>(PyTuple_New(m_max_arity)));
|
||||
for (unsigned j = 0; j < keyword_offset; ++j)
|
||||
PyTuple_SET_ITEM(m_arg_names.ptr(), j, incref(Py_None));
|
||||
|
||||
unsigned tuple_size = num_keywords ? m_max_arity : 0;
|
||||
m_arg_names = object(handle<>(PyTuple_New(tuple_size)));
|
||||
|
||||
if (num_keywords != 0)
|
||||
{
|
||||
for (unsigned j = 0; j < keyword_offset; ++j)
|
||||
PyTuple_SET_ITEM(m_arg_names.ptr(), j, incref(Py_None));
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < num_keywords; ++i)
|
||||
{
|
||||
@@ -68,7 +73,7 @@ function::function(
|
||||
function::~function()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
PyObject* function::call(PyObject* args, PyObject* keywords) const
|
||||
{
|
||||
std::size_t nargs = PyTuple_GET_SIZE(args);
|
||||
@@ -76,47 +81,68 @@ PyObject* function::call(PyObject* args, PyObject* keywords) const
|
||||
std::size_t total_args = nargs + nkeywords;
|
||||
|
||||
function const* f = this;
|
||||
|
||||
// Try overloads looking for a match
|
||||
do
|
||||
{
|
||||
// Check for a plausible number of arguments
|
||||
if (total_args >= f->m_min_arity && total_args <= f->m_max_arity)
|
||||
{
|
||||
// This will be the args that actually get passed
|
||||
handle<> args2(allow_null(borrowed(args)));
|
||||
if (nkeywords > 0)
|
||||
|
||||
if (nkeywords > 0) // Keyword arguments were supplied
|
||||
{
|
||||
if (!f->m_arg_names
|
||||
|| static_cast<std::size_t>(PyTuple_Size(f->m_arg_names.ptr())) < total_args)
|
||||
if (f->m_arg_names.ptr() == Py_None) // this overload doesn't accept keywords
|
||||
{
|
||||
args2 = handle<>(); // signal failure
|
||||
}
|
||||
else
|
||||
{
|
||||
// build a new arg tuple
|
||||
args2 = handle<>(PyTuple_New(total_args));
|
||||
std::size_t max_args
|
||||
= static_cast<std::size_t>(PyTuple_Size(f->m_arg_names.ptr()));
|
||||
|
||||
// Fill in the positional arguments
|
||||
for (std::size_t i = 0; i < nargs; ++i)
|
||||
PyTuple_SET_ITEM(args2.get(), i, incref(PyTuple_GET_ITEM(args, i)));
|
||||
|
||||
// Grab remaining arguments by name from the keyword dictionary
|
||||
for (std::size_t j = nargs; j < total_args; ++j)
|
||||
// "all keywords are none" is a special case
|
||||
// indicating we will accept any number of keyword
|
||||
// arguments
|
||||
if (max_args == 0)
|
||||
{
|
||||
PyObject* value = PyDict_GetItem(
|
||||
keywords, PyTuple_GET_ITEM(f->m_arg_names.ptr(), j));
|
||||
|
||||
if (!value)
|
||||
// no argument preprocessing
|
||||
}
|
||||
else if (max_args < total_args)
|
||||
{
|
||||
args2 = handle<>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// build a new arg tuple
|
||||
args2 = handle<>(PyTuple_New(total_args));
|
||||
|
||||
// Fill in the positional arguments
|
||||
for (std::size_t i = 0; i < nargs; ++i)
|
||||
PyTuple_SET_ITEM(args2.get(), i, incref(PyTuple_GET_ITEM(args, i)));
|
||||
|
||||
// Grab remaining arguments by name from the keyword dictionary
|
||||
for (std::size_t j = nargs; j < total_args; ++j)
|
||||
{
|
||||
PyErr_Clear();
|
||||
args2 = handle<>();
|
||||
break;
|
||||
PyObject* value = PyDict_GetItem(
|
||||
keywords, PyTuple_GET_ITEM(f->m_arg_names.ptr(), j));
|
||||
|
||||
if (!value)
|
||||
{
|
||||
PyErr_Clear();
|
||||
args2 = handle<>();
|
||||
break;
|
||||
}
|
||||
PyTuple_SET_ITEM(args2.get(), j, incref(value));
|
||||
}
|
||||
PyTuple_SET_ITEM(args2.get(), j, incref(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function
|
||||
PyObject* result = args2 ? f->m_fn(args2.get(), 0) : 0;
|
||||
// Call the function. Pass keywords in case it's a
|
||||
// function accepting any number of keywords
|
||||
PyObject* result = args2 ? f->m_fn(args2.get(), keywords) : 0;
|
||||
|
||||
// If the result is NULL but no error was set, m_fn failed
|
||||
// the argument-matching test.
|
||||
@@ -482,4 +508,20 @@ handle<> function_handle_impl(py_function const& f, unsigned min_arity, unsigned
|
||||
new function(f, min_arity, max_arity, 0, 0)));
|
||||
}
|
||||
|
||||
}}} // namespace boost::python::objects
|
||||
}
|
||||
|
||||
namespace detail
|
||||
{
|
||||
object BOOST_PYTHON_DECL make_raw_function(objects::py_function f, std::size_t min_args)
|
||||
{
|
||||
static keyword k;
|
||||
|
||||
return objects::function_object(
|
||||
f
|
||||
, min_args
|
||||
, std::numeric_limits<std::size_t>::max()
|
||||
, keyword_range(&k,&k));
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace boost::python::objects
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <boost/python/tuple.hpp>
|
||||
#include <boost/python/class.hpp>
|
||||
#include <boost/python/overloads.hpp>
|
||||
#include <boost/python/raw_function.hpp>
|
||||
#include <boost/python/return_internal_reference.hpp>
|
||||
#include "test_class.hpp"
|
||||
|
||||
@@ -39,12 +40,20 @@ struct X
|
||||
|
||||
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(X_f_overloads, X::f, 0, 3)
|
||||
|
||||
|
||||
tuple raw_func(tuple args, dict kw)
|
||||
{
|
||||
return make_tuple(args, kw);
|
||||
}
|
||||
|
||||
BOOST_PYTHON_MODULE(args_ext)
|
||||
{
|
||||
def("f", f, args("x", "y", "z")
|
||||
, "This is f's docstring"
|
||||
);
|
||||
|
||||
def("raw", raw_function(raw_func));
|
||||
|
||||
#if defined(BOOST_MSVC) && BOOST_MSVC <= 1200
|
||||
// MSVC6 gives a fatal error LNK1179: invalid or corrupt file:
|
||||
// duplicate comdat error if we try to re-use the exact type of f
|
||||
@@ -57,6 +66,7 @@ BOOST_PYTHON_MODULE(args_ext)
|
||||
|
||||
class_<Y>("Y", init<int>(args("value"), "Y's docstring"))
|
||||
.def("value", &Y::value)
|
||||
.def("raw", raw_function(raw_func))
|
||||
;
|
||||
|
||||
class_<X>("X", "This is X's docstring")
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"""
|
||||
>>> from args_ext import *
|
||||
|
||||
>>> raw(3, 4, foo = 'bar', baz = 42)
|
||||
((3, 4), {'foo': 'bar', 'baz': 42})
|
||||
|
||||
>>> f(x= 1, y = 3, z = 'hello')
|
||||
(1, 3.0, 'hello')
|
||||
|
||||
@@ -101,6 +104,11 @@
|
||||
|
||||
>>> inner(n = 1, self = q).value()
|
||||
1
|
||||
|
||||
>>> y = Y(value = 33)
|
||||
>>> y.raw(this = 1, that = 'the other')[1]
|
||||
{'this': 1, 'that': 'the other'}
|
||||
|
||||
"""
|
||||
def run(args = None):
|
||||
import sys
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
>>> z2 = copy_Z(z)
|
||||
>>> x_instances()
|
||||
4
|
||||
>>> y_identity(y) is y
|
||||
1
|
||||
>>> assert y_identity(y) is y
|
||||
>>> y_equality(y, y)
|
||||
1
|
||||
'''
|
||||
|
||||
@@ -7,11 +7,21 @@
|
||||
#include <boost/python/def.hpp>
|
||||
#include <boost/python/module.hpp>
|
||||
#include <boost/python/class.hpp>
|
||||
|
||||
#if BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
# include <boost/type_traits/is_enum.hpp>
|
||||
# include <boost/mpl/bool.hpp>
|
||||
#endif
|
||||
using namespace boost::python;
|
||||
|
||||
enum color { red = 1, green = 2, blue = 4 };
|
||||
|
||||
#if BOOST_WORKAROUND(__MWERKS__, <= 0x2407)
|
||||
namespace boost // Pro7 has a hard time detecting enums
|
||||
{
|
||||
template <> struct is_enum<color> : boost::mpl::true_ {};
|
||||
}
|
||||
#endif
|
||||
|
||||
color identity_(color x) { return x; }
|
||||
|
||||
struct colorized {
|
||||
|
||||
@@ -21,7 +21,13 @@
|
||||
|
||||
using namespace boost::python;
|
||||
|
||||
typedef test_class<> X;
|
||||
struct X : test_class<>
|
||||
{
|
||||
typedef test_class<> base_t;
|
||||
|
||||
X(int x) : base_t(x) {}
|
||||
X const operator+(X const& r) const { return X(value() + r.value()); }
|
||||
};
|
||||
|
||||
X operator-(X const& l, X const& r) { return X(l.value() - r.value()); }
|
||||
X operator-(int l, X const& r) { return X(l - r.value()); }
|
||||
@@ -39,17 +45,17 @@ X abs(X x) { return X(x.value() < 0 ? -x.value() : x.value()); }
|
||||
|
||||
X pow(X x, int y)
|
||||
{
|
||||
return X(int(pow(double(x.value()), y)));
|
||||
return X(int(pow(double(x.value()), double(y))));
|
||||
}
|
||||
|
||||
X pow(X x, X y)
|
||||
{
|
||||
return X(int(pow(double(x.value()), y.value())));
|
||||
return X(int(pow(double(x.value()), double(y.value()))));
|
||||
}
|
||||
|
||||
int pow(int x, X y)
|
||||
{
|
||||
return int(pow(double(x), y.value()));
|
||||
return int(pow(double(x), double(y.value())));
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, X const& x)
|
||||
@@ -61,6 +67,7 @@ BOOST_PYTHON_MODULE(operators_ext)
|
||||
{
|
||||
class_<X>("X", init<int>())
|
||||
.def("value", &X::value)
|
||||
.def(self + self)
|
||||
.def(self - self)
|
||||
.def(self - int())
|
||||
.def(other<int>() - self)
|
||||
|
||||
@@ -61,19 +61,15 @@ r"""
|
||||
... else: print 'expected an OverflowError!'
|
||||
|
||||
|
||||
>>> abs(rewrap_value_float(4.2) - 4.2) < .000001
|
||||
1
|
||||
>>> assert abs(rewrap_value_float(4.2) - 4.2) < .000001
|
||||
>>> rewrap_value_double(4.2) - 4.2
|
||||
0.0
|
||||
>>> rewrap_value_long_double(4.2) - 4.2
|
||||
0.0
|
||||
|
||||
>>> abs(rewrap_value_complex_float(4+.2j) - (4+.2j)) < .000001
|
||||
1
|
||||
>>> abs(rewrap_value_complex_double(4+.2j) - (4+.2j)) < .000001
|
||||
1
|
||||
>>> abs(rewrap_value_complex_long_double(4+.2j) - (4+.2j)) < .000001
|
||||
1
|
||||
>>> assert abs(rewrap_value_complex_float(4+.2j) - (4+.2j)) < .000001
|
||||
>>> assert abs(rewrap_value_complex_double(4+.2j) - (4+.2j)) < .000001
|
||||
>>> assert abs(rewrap_value_complex_long_double(4+.2j) - (4+.2j)) < .000001
|
||||
|
||||
>>> rewrap_value_cstring('hello, world')
|
||||
'hello, world'
|
||||
@@ -136,19 +132,15 @@ r"""
|
||||
42L
|
||||
|
||||
|
||||
>>> abs(rewrap_const_reference_float(4.2) - 4.2) < .000001
|
||||
1
|
||||
>>> assert abs(rewrap_const_reference_float(4.2) - 4.2) < .000001
|
||||
>>> rewrap_const_reference_double(4.2) - 4.2
|
||||
0.0
|
||||
>>> rewrap_const_reference_long_double(4.2) - 4.2
|
||||
0.0
|
||||
|
||||
>>> abs(rewrap_const_reference_complex_float(4+.2j) - (4+.2j)) < .000001
|
||||
1
|
||||
>>> abs(rewrap_const_reference_complex_double(4+.2j) - (4+.2j)) < .000001
|
||||
1
|
||||
>>> abs(rewrap_const_reference_complex_long_double(4+.2j) - (4+.2j)) < .000001
|
||||
1
|
||||
>>> assert abs(rewrap_const_reference_complex_float(4+.2j) - (4+.2j)) < .000001
|
||||
>>> assert abs(rewrap_const_reference_complex_double(4+.2j) - (4+.2j)) < .000001
|
||||
>>> assert abs(rewrap_const_reference_complex_long_double(4+.2j) - (4+.2j)) < .000001
|
||||
|
||||
>>> rewrap_const_reference_cstring('hello, world')
|
||||
'hello, world'
|
||||
@@ -221,11 +213,9 @@ Check that classic classes also work
|
||||
... else: print 'expected a TypeError exception'
|
||||
|
||||
# show that arbitrary handle<T> instantiations can be returned
|
||||
>>> get_type(1) is type(1)
|
||||
1
|
||||
>>> assert get_type(1) is type(1)
|
||||
|
||||
>>> return_null_handle() is None
|
||||
1
|
||||
>>> assert return_null_handle() is None
|
||||
"""
|
||||
|
||||
def run(args = None):
|
||||
|
||||
@@ -70,11 +70,9 @@ Test call policies for constructors here
|
||||
>>> num_a_instances()
|
||||
0
|
||||
|
||||
>>> as_A(create('dynalloc')) is None
|
||||
0
|
||||
>>> assert as_A(create('dynalloc')) is not None
|
||||
>>> base = Base()
|
||||
>>> as_A(base) is None
|
||||
1
|
||||
>>> assert as_A(base) is None
|
||||
"""
|
||||
def run(args = None):
|
||||
import sys
|
||||
|
||||
Reference in New Issue
Block a user