{"id":71,"date":"2014-12-31T23:29:38","date_gmt":"2014-12-31T23:29:38","guid":{"rendered":"http:\/\/weegreenblobbie.com\/?p=71"},"modified":"2015-03-08T18:29:50","modified_gmt":"2015-03-08T18:29:50","slug":"swig-c-python-c-python-object-lifetime","status":"publish","type":"post","link":"http:\/\/weegreenblobbie.com\/?p=71","title":{"rendered":"SWIG C++ &#038; Python: C++ &#038; Python Object Lifetime"},"content":{"rendered":"<p>I&#8217;m currently struggling with a bug in my open source project and it has to do with temporary object lifetimes.\u00a0 I believe the core issue is that I have a Python object that holds a pointer to an underlying C++ object that&#8217;s gotten deleted too early.\u00a0 I&#8217;ve created a toy SWIG project to try to isolate the issue.<\/p>\n<p>The code: <a href=\"http:\/\/weegreenblobbie.com\/wp-content\/uploads\/2014\/12\/swig_test.tar.gz\">swig_test.tar.gz<\/a><\/p>\n<p>To build the project on your machine, just unzip and run make:<\/p>\n<pre class=\"theme:shell-default lang:sh decode:true\">tar xfz swig_test.tar.gz\r\ncd swig_test\r\nmake<\/pre>\n<p>This will build a Python C++ extension called <code>swig_test<\/code> and install it into your home directory.<\/p>\n<p>The <code>Buffer<\/code> class:<\/p>\n<pre class=\"lang:c++ decode:true\">struct Buffer\r\n{\r\n    Buffer();\r\n    Buffer(const Buffer &amp; copy);\r\n    ~Buffer();\r\n\r\n    Buffer &amp; operator=(const Buffer &amp; rhs);\r\n\r\n    Buffer &amp; operator&lt;&lt;(const Buffer &amp; rhs);\r\n    Buffer &amp; operator&lt;&lt;(double rhs);\r\n\r\n    std::string __str__() const;\r\n    std::string __repr__() const;\r\n\r\n    private:\r\n\r\n    std::vector&lt;double&gt; _data;\r\n    int                 _id;\r\n};<\/pre>\n<p>The <code>Buffer<\/code> class is just a container, I&#8217;m using <code>operator&lt;&lt;<\/code> to concatenate data to the end of the <code>Buffer<\/code>.\u00a0 This is just a toy example illustrating the problem I&#8217;m having with SWIG, my actual class does a whole lot more.<\/p>\n<p>The <code>_data<\/code> member is just a list of numbers that the container holds, <code>_id<\/code> is a unique identifier so we can tell which <code>Buffer<\/code> gets deleted.<\/p>\n<p>The <code>swig_test.i<\/code> file:<\/p>\n<pre class=\"lang:c++ decode:true\">%module swig_test\r\n\r\n%include \"std_string.i\"\r\n\r\n%{\r\n#include \"Buffer.hpp\"\r\n#include &lt;iostream&gt;\r\n%}\r\n\r\n%ignore Buffer::operator=;\r\n\r\n%include \"Buffer.hpp\"\r\n<\/pre>\n<p>This is about as basic as it can be.<\/p>\n<p>The go_test.py Python script:<\/p>\n<pre class=\"lang:python decode:true\">from swig_test import Buffer\r\n\r\ndef zeros(n):\r\n    '''\r\n    Returns a Buffer filled with 'n' zeros.\r\n    '''\r\n\r\n    b = Buffer()\r\n\r\n    for i in xrange(n):\r\n        b &lt;&lt; 0.0\r\n\r\n    return b\r\n\r\ndef ones(n):\r\n    '''\r\n    Returns a Buffer filled with 'n' ones.\r\n    '''\r\n\r\n    b = Buffer()\r\n\r\n    for i in xrange(n):\r\n        b &lt;&lt; 1.0\r\n\r\n    return b\r\n\r\ndef main():\r\n\r\n    #--------------------------------------------------------------------------\r\n    # This sections works as expected\r\n\r\n    print \"-\" * 80\r\n    print \"Works as expected:\"\r\n\r\n    b0 = zeros(3)\r\n\r\n    print \"    b0 = \", b0\r\n\r\n    b1 = ones(3)\r\n\r\n    print \"    b1 = \", b1\r\n\r\n    y = b0 &lt;&lt; b1\r\n\r\n    print \"    b0 = \", b0\r\n    print \"    y  = \", y\r\n    print \"    b1 = \", b1\r\n\r\n    print \"    repr(b0) = \", repr(b0)\r\n    print \"    repr(y)  = \", repr(y)\r\n\r\n    #--------------------------------------------------------------------------\r\n    # Funny things are happening here!\r\n\r\n    print \"Funny business:\"\r\n\r\n    b2 = zeros(3) &lt;&lt; ones(3)\r\n\r\n    print \"    repr(b2) = \", repr(b2)\r\n\r\n    b3 = zeros(3) &lt;&lt; 4.0\r\n\r\n    print \"    repr(b3) = \", repr(b3)\r\n\r\nif __name__ == \"__main__\":\r\n    main()\r\n<\/pre>\n<p>Now when running make, the following test script is executed that illustrates the issue I&#8217;m running into:<\/p>\n<pre class=\"lang:default decode:true\">python go_test.py\r\n--------------------------------------------------------------------------------\r\nWorks as expected:\r\n    b0 =  Buffer(0, 0, 0, )\r\n    b1 =  Buffer(1, 1, 1, )\r\n    b0 =  Buffer(0, 0, 0, 1, 1, 1, )\r\n    y  =  Buffer(0, 0, 0, 1, 1, 1, )\r\n    b1 =  Buffer(1, 1, 1, )\r\n    repr(b0) =  Buffer(id = 0, vector at 0x020bf450, data at 0x020aeb30, size = 6)\r\n    repr(y)  =  Buffer(id = 0, vector at 0x020bf450, data at 0x020aeb30, size = 6)\r\nFunny business:\r\n    Deleting Buffer(id = 2)\r\n    Deleting Buffer(id = 3)\r\n    repr(b2) =  Buffer(id = 2, vector at 0x020bf790, data at 0x00, size = 4257068)\r\n    Deleting Buffer(id = 4)\r\n    repr(b3) =  Buffer(id = 4, vector at 0x02037040, data at 0x0204a4e0, size = 6)\r\n    Deleting Buffer(id = 0)\r\n    Deleting Buffer(id = 1)\r\n<\/pre>\n<p>The &#8216;Deleting Buffer(id = X)&#8217; is being generated from inside the C++ code, so we can see here that in the &#8216;Funny business&#8217; section, the C++ Buffer objects are getting deleted too early! The Python objects &#8216;b2&#8217; and &#8216;b3&#8217; should be holding references to the C++ Buffer objects with id=2 and id=4.<\/p>\n<p>This summarizes the issue I&#8217;m having.<\/p>\n<hr \/>\n<p><strong>Pure Python Implementation<\/strong><\/p>\n<p>I&#8217;ve been searching the web for information to learn what&#8217;s going one here. I found this stackoverflow <a href=\"http:\/\/stackoverflow.com\/questions\/4975509\/lifetime-of-temporary-objects-in-swigs-python-wrappers\" target=\"_blank\">question<\/a> and though it might be related, so I&#8217;ve implemented a pure Python version of Buffer in the script <code>go_test_pure.py<\/code>, here&#8217;s it&#8217;s output:<\/p>\n<pre class=\"lang:default decode:true\">python go_test_pure.py\r\n--------------------------------------------------------------------------------\r\nWorks as expected:\r\n    b0 =  Buffer(0.0, 0.0, 0.0, )\r\n    b1 =  Buffer(1.0, 1.0, 1.0, )\r\n    b0 =  Buffer(0.0, 0.0, 0.0, 1.0, 1.0, 1.0, )\r\n    y  =  Buffer(0.0, 0.0, 0.0, 1.0, 1.0, 1.0, )\r\n    b1 =  Buffer(1.0, 1.0, 1.0, )\r\n    repr(b0) =  Buffer(id = 0, data at 0x7FFEBD772CB0, size = 6)\r\n    repr(y)  =  Buffer(id = 0, data at 0x7FFEBD772CB0, size = 6)\r\nNo funny business:\r\n    Deleting Buffer(id = 3)\r\n    repr(b2) =  Buffer(id = 2, data at 0x7FFEBD7AB050, size = 6)\r\n    repr(b3) =  Buffer(id = 4, data at 0x7FFEBD772CF8, size = 4)\r\n    Deleting Buffer(id = 1)\r\n    Deleting Buffer(id = 0)\r\n    Deleting Buffer(id = 2)\r\n    Deleting Buffer(id = 4)\r\n<\/pre>\n<p>However, everything works as expected. Notice that Buffer ids 2, 4 are deleted at the end, so the pure Python version works as I expect.<\/p>\n<hr \/>\n<p><strong>Experiment 1: No Destructors<\/strong><\/p>\n<p>So the first experiment I tried was to explicitly turn off destructors in the SWIG file with <strong>%nodefaultdtor<\/strong> and ignoring the C++ destructor:<\/p>\n<pre class=\"lang:default decode:true\">%module swig_test\r\n\r\n%include \"std_string.i\"\r\n\r\n%{\r\n#include \"Buffer.hpp\"\r\n#include &lt;iostream&gt;\r\n%}\r\n\r\n%ignore Buffer::operator=;\r\n%ignore Buffer::~Buffer;       \/\/ ignoring C++ destructor\r\n%nodefaultdtor Buffer;         \/\/ don't generate a destructor\r\n\r\n%include \"Buffer.hpp\"\r\n<\/pre>\n<p>And here&#8217;s the output:<\/p>\n<pre class=\"lang:default decode:true\">python go_test.py\r\n--------------------------------------------------------------------------------\r\nWorks as expected:\r\n    b0 =  Buffer(0, 0, 0, )\r\n    b1 =  Buffer(1, 1, 1, )\r\n    b0 =  Buffer(0, 0, 0, 1, 1, 1, )\r\n    y  =  Buffer(0, 0, 0, 1, 1, 1, )\r\n    b1 =  Buffer(1, 1, 1, )\r\n    repr(b0) =  Buffer(id = 0, vector at 0x02865fa0, data at 0x02855b30, size = 6)\r\n    repr(y)  =  Buffer(id = 0, vector at 0x02865fa0, data at 0x02855b30, size = 6)\r\nFunny business:\r\nswig\/python detected a memory leak of type 'Buffer *', no destructor found.\r\nswig\/python detected a memory leak of type 'Buffer *', no destructor found.\r\n    repr(b2) =  Buffer(id = 2, vector at 0x028662e0, data at 0x028119f0, size = 6)\r\nswig\/python detected a memory leak of type 'Buffer *', no destructor found.\r\n    repr(b3) =  Buffer(id = 4, vector at 0x0281d930, data at 0x027f1520, size = 4)\r\nswig\/python detected a memory leak of type 'Buffer *', no destructor found.\r\nswig\/python detected a memory leak of type 'Buffer *', no destructor found.\r\n<\/pre>\n<p>Now the Python objects b2, b3 are pointing to ids 2, 4 and they are valid. But this approach leaks memory, so this is not a solution.<\/p>\n<hr \/>\n<p>&nbsp;<\/p>\n<p><strong>Experiment 2: Using A Typemap<\/strong><\/p>\n<p>My next experiment involves using a typemap like so:<\/p>\n<pre class=\"lang:default decode:true\">%module swig_test\r\n\r\n%include \"std_string.i\"\r\n\r\n%{\r\n#include \"Buffer.hpp\"\r\n#include &lt;iostream&gt;\r\n%}\r\n\r\n%ignore Buffer::operator=;\r\n\r\n%typemap(out) Buffer &amp;\r\n{\r\n    \/\/ TYPEMAP\r\n    $result = $self;\r\n    \/\/ TYPEMAP\r\n}\r\n\r\n%include \"Buffer.hpp\"\r\n<\/pre>\n<p>If you were to compare the generated SWIG wrapper code in swig_test.cpp, you will see that this typemap appears to do the right thing, by returning the original PyObject that points to the C++ Buffer object on the left-hand-side of the expression, just like the pure Python method returns self. However, at run-time, a SWIG internal conversion error occurs and the code fails with:<\/p>\n<pre class=\"lang:default decode:true\">python go_test.py\r\n--------------------------------------------------------------------------------\r\nWorks as expected:\r\n    Deleting Buffer(id = 0)\r\nTraceback (most recent call last):\r\n  File \"go_test.py\", line 71, in \r\n    main()\r\n  File \"go_test.py\", line 38, in main\r\n    b0 = zeros(3)\r\n  File \"go_test.py\", line 12, in zeros\r\n    b &lt;&lt; 0.0\r\nTypeError: unsupported operand type(s) for &lt;&lt;: 'Buffer' and 'float'\r\nmake: *** [all] Error 1\r\n<\/pre>\n<hr \/>\n<p><strong>Asking The Community For Help<\/strong><\/p>\n<p>I&#8217;ve run out of ideas and now I need some new eyes to take a look. Hopefully a SWIG expert can quickly point out my error and I can move on with writing code.<\/p>\n<p>So I&#8217;ve posted <a title=\"this\" href=\"http:\/\/stackoverflow.com\/questions\/27727364\/swig-c-python-c-temporary-objects-deleted-too-soon\" target=\"_blank\">this<\/a> question to stackoverflow with a link to this blog post. If a solution comes up I&#8217;ll be sure to link to it from here.<\/p>\n<hr \/>\n<p><strong>A Solution!<\/strong><\/p>\n<p>After some more searching I came across this <a title=\"thread\" href=\"http:\/\/thread.gmane.org\/gmane.comp.programming.swig\/19491\/focus=19506\" target=\"_blank\">thread<\/a> which eventually lead to a working solution. Using a <code>typemap(out)<\/code> in combination of a <code>Py_INCREF()<\/code> did the trick.<\/p>\n<pre class=\"lang:default decode:true \">%module swig_test\r\n\r\n%include \"std_string.i\"\r\n\r\n%{\r\n    #include \"Buffer.hpp\"\r\n    #include &lt;iostream&gt;\r\n%}\r\n\r\n%ignore Buffer::operator=;\r\n\r\n%typemap(out) Buffer &amp; operator&lt;&lt;\r\n{\r\n    if(result) { \/* suppress unused warning *\/ }\r\n    Py_INCREF($self);\r\n    $result = $self;\r\n}\r\n\r\n%include \"Buffer.hpp\"\r\n<\/pre>\n<p>Now I get the desired behavior with no memory leaks!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m currently struggling with a bug in my open source project and it has to do with temporary object lifetimes.\u00a0 I believe the core issue is that I have a Python object that holds a pointer to an underlying C++ &hellip; <a href=\"http:\/\/weegreenblobbie.com\/?p=71\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[5,6,4],"class_list":["post-71","post","type-post","status-publish","format-standard","hentry","category-programming","tag-c","tag-python","tag-swig"],"_links":{"self":[{"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=\/wp\/v2\/posts\/71","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=71"}],"version-history":[{"count":25,"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=\/wp\/v2\/posts\/71\/revisions"}],"predecessor-version":[{"id":97,"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=\/wp\/v2\/posts\/71\/revisions\/97"}],"wp:attachment":[{"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=71"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=71"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/weegreenblobbie.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=71"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}