changeset 362:b0677c492655

Overhaul Python object storage and wrapping in pyobject * oct-py-util.cc, oct-py-util.h (pytave::py_objstore_del, pytave::py_objstore_get, pytave::py_objstore_put, pytave::pyobject_wrap_object, pytave::pyobject_unwrap_object): New functions. (pytave::get_object_from_python): Use pyobject_unwrap_object. * octave_to_python.cc (pytave::octvalue_to_pyobj): Use pyobject_unwrap_object. * python_to_octave.cc (pytave::pyobj_to_octvalue): Use pyobject_wrap_object. (pytave::pyobj_to_oct_pyobject): Delete. * __py_struct_from_dict__.cc (F__py_objstore_del__, F__py_objstore_get__, F__py_objstore_put__): New functions. * @pyobject/pyobject.m (pyobject.m_id): Rename from id. (pyobject.pyobject): Use __py_objstore_put__, simplify conditional logic. (pyobject.delete): Use __py_objstore_del__. (pyobject.id): Rename from getid. * @pyobject/display.m: Use pyobject.id method name, format as hex.
author Mike Miller <mtmiller@octave.org>
date Thu, 25 Aug 2016 09:51:58 -0700
parents 07c1b457cb6b
children 445df7f96fbc
files @pyobject/display.m @pyobject/pyobject.m __py_struct_from_dict__.cc oct-py-util.cc oct-py-util.h octave_to_python.cc python_to_octave.cc
diffstat 7 files changed, 199 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/@pyobject/display.m	Thu Aug 25 09:29:51 2016 -0700
+++ b/@pyobject/display.m	Thu Aug 25 09:51:58 2016 -0700
@@ -41,7 +41,7 @@
 
   loose = ! __compactformat__ ();
 
-  printf ("%s = [pyobject %s]\n", inputname (1), getid (x));
+  printf ("%s = [pyobject 0x%x]\n", inputname (1), id (x));
   s = char (x);
   s = make_indented (s);
   if (loose), printf ("\n"); endif
--- a/@pyobject/pyobject.m	Thu Aug 25 09:29:51 2016 -0700
+++ b/@pyobject/pyobject.m	Thu Aug 25 09:51:58 2016 -0700
@@ -29,7 +29,7 @@
 
 classdef pyobject < handle
   properties
-    id
+    m_id
   endproperties
 
 
@@ -37,42 +37,22 @@
     function obj = pyobject (x, id)
       if (nargin == 0)
         obj = pyeval ("None");
-        return
-      endif
-
-      if (nargin == 1)
+      elseif (nargin == 1)
         ## Convert the input to a pyobject
         if (isa (x, "pyobject"))
           obj = x;
         else
-          ## Ensure dict for Octave communication exists
-          cmd = [ "if not getattr(__import__('__main__'), '_in_octave', None):\n" ...
-                  "    __import__('__main__')._in_octave = dict()" ];
-          pyexec (cmd);
-
-          ## Function to insert and return the hex id
-          cmd = [ "def _in_octave_insert(x):\n" ...
-                  "    h = hex(id(x))\n" ...
-                  "    __import__('__main__')._in_octave[h] = x\n" ...
-                  "    return h" ];
-          pyexec (cmd);
-
-          id = pycall ("_in_octave_insert", x);
-          obj = pyobject (0, id);
+          obj.m_id = __py_objstore_put__ (x);
         endif
-        return
-      endif
-
-      if (nargin == 2)
+      elseif (nargin == 2)
         ## The actual constructor.  Nicer to split this off to static method
         ## like `pyobject.new` but I don't know how to call from pycall.cc.
         ## Warning: not intended for casual use: you must also insert the
-        ## object into the Python `_in_octave` dict with key `id`.
-        obj.id = id;
-        return
+        ## object into the Python object store with key `id`.
+        obj.m_id = id;
+      else
+        error ("pyobject: unexpected input to the constructor")
       endif
-
-      error ("pyobject: unexpected input to the constructor")
     endfunction
 
     function delete (x)
@@ -93,9 +73,7 @@
 
       #disp ("delete")
 
-      ## throws KeyError if it wasn't in there for some reason
-      cmd = sprintf ("__import__('__main__')._in_octave.pop('%s')", x.id);
-      pyexec (cmd)
+      __py_objstore_del__ (x.m_id);
     endfunction
 
     # methods defined in external files
@@ -103,8 +81,8 @@
     display (x)
     subsref (x, idx)
 
-    function r = getid (x)
-      r = x.id;
+    function r = id (x);
+      r = x.m_id;
     endfunction
 
     function s = char (x)
--- a/__py_struct_from_dict__.cc	Thu Aug 25 09:29:51 2016 -0700
+++ b/__py_struct_from_dict__.cc	Thu Aug 25 09:51:58 2016 -0700
@@ -134,6 +134,83 @@
   return retval;
 }
 
+DEFUN_DLD (__py_objstore_del__, args, nargout,
+           "-*- texinfo -*-\n\
+@deftypefn {} {} __py_objstore_del__ (@var{key})\n\
+Delete the Python object stored under @var{key} from the object store.\n\
+\n\
+This is a private internal function not intended for direct use.\n\
+@end deftypefn")
+{
+  if (args.length () != 1)
+    print_usage ();
+
+  Py_Initialize ();
+  uint64_t key = args(0).xuint64_scalar_value ("__py_objstore_del__: KEY must be an integer");
+  pytave::py_objstore_del (key);
+
+  return ovl ();
+}
+
+DEFUN_DLD (__py_objstore_get__, args, nargout,
+           "-*- texinfo -*-\n\
+@deftypefn {} {} __py_objstore_get__ (@var{key})\n\
+Get the Python object stored under @var{key} from the object store.\n\
+\n\
+This is a private internal function not intended for direct use.\n\
+@end deftypefn")
+{
+  if (args.length () != 1)
+    print_usage ();
+
+  Py_Initialize ();
+  uint64_t key = args(0).xuint64_scalar_value ("__py_objstore_get__: KEY must be an integer");
+  PyObject *obj = pytave::py_objstore_get (key);
+
+  if (! obj)
+    error ("__py_objstore_get__: no existing Python object found for key %ju", key);
+
+  octave_value retval = pytave::pyobject_wrap_object (obj);
+
+  return ovl (retval);
+}
+
+DEFUN_DLD (__py_objstore_put__, args, nargout,
+           "-*- texinfo -*-\n\
+@deftypefn {} {} __py_objstore_put__ (@var{value})\n\
+Convert @var{value} to a Python value and store in the object store.\n\
+\n\
+This is a private internal function not intended for direct use.\n\
+@end deftypefn")
+{
+  if (args.length () != 1)
+    print_usage ();
+
+  Py_Initialize ();
+  boost::python::numeric::array::set_module_and_type ("numpy", "ndarray");
+  _import_array ();
+  // FIXME: PyObject *obj = convert argument to Python (args(0));
+  PyObject *obj = 0;
+  try
+    {
+      boost::python::object arg;
+      pytave::octvalue_to_pyobj (arg, args(0));
+      obj = arg.ptr ();
+      Py_INCREF (obj);
+    }
+  catch (pytave::value_convert_exception const &)
+    {
+    }
+
+  if (! obj)
+    error ("__py_objstore_put__: VALUE must be convertible to a Python value");
+
+  uint64_t key = pytave::py_objstore_put (obj);
+  Py_DECREF (obj);
+
+  return ovl (octave_uint64 (key));
+}
+
 DEFUN_DLD (__py_struct_from_dict__, args, nargout,
            "-*- texinfo -*-\n\
 @deftypefn  {} {} __py_struct_from_dict__ (@var{dict})\n\
--- a/oct-py-util.cc	Thu Aug 25 09:29:51 2016 -0700
+++ b/oct-py-util.cc	Thu Aug 25 09:51:58 2016 -0700
@@ -51,10 +51,8 @@
 {
   if (oct_value.is_object () && oct_value.class_name () == "pyobject")
     {
-      octave_value_list tmp = feval ("getid", ovl (oct_value), 1);
-      std::string hexid = tmp(0).string_value ();
-      object main_module = import ("__main__");
-      py_object = main_module.attr ("_in_octave")[hexid];
+      PyObject *obj = pyobject_unwrap_object (oct_value);
+      py_object = boost::python::object (boost::python::handle<> (obj));
     }
   else
     py_object = boost::python::object (); // None
@@ -149,6 +147,95 @@
   return name_ ? extract_py_str (name_): "";
 }
 
+// FIXME: could make this into a class/singleton wrapper a la Octave core
+PyObject *objstore = 0;
+
+inline PyObject *
+py_objstore ()
+{
+  if (! objstore)
+    {
+      PyObject *main = py_import_module ("__main__");
+      PyObject *ns = main ? PyObject_GetAttrString (main, "__dict__") : 0;
+      PyObject *dict = ns ? PyDict_GetItemString (ns, "_in_octave") : 0;
+
+      if (dict)
+        Py_INCREF (dict);
+
+      if (! dict)
+        {
+          dict = PyDict_New ();
+          if (dict && ns)
+            PyDict_SetItemString (ns, "_in_octave", dict);
+        }
+
+      if (! dict)
+        throw boost::python::error_already_set ();
+
+      objstore = dict;
+    }
+  return objstore;
+}
+
+void
+py_objstore_del (uint64_t key)
+{
+  PyObject *store = py_objstore ();
+  PyObject *key_obj = make_py_int (key);
+  PyObject *key_fmt = PyNumber_ToBase (key_obj, 16);
+  PyDict_DelItem (store, key_fmt);
+  Py_DECREF (key_fmt);
+  Py_DECREF (key_obj);
+}
+
+PyObject *
+py_objstore_get (uint64_t key)
+{
+  PyObject *store = py_objstore ();
+  PyObject *key_obj = make_py_int (key);
+  PyObject *key_fmt = PyNumber_ToBase (key_obj, 16);
+  PyObject *obj = PyDict_GetItem (store, key_fmt);
+  Py_DECREF (key_fmt);
+  Py_DECREF (key_obj);
+  if (obj)
+    Py_INCREF (obj);
+  return obj;
+}
+
+uint64_t
+py_objstore_put (PyObject *obj)
+{
+  PyObject *store = py_objstore ();
+  uint64_t key = reinterpret_cast<uint64_t> (obj);
+  PyObject *key_obj = make_py_int (key);
+  PyObject *key_fmt = PyNumber_ToBase (key_obj, 16);
+  PyDict_SetItem (store, key_fmt, obj);
+  Py_DECREF (key_fmt);
+  Py_DECREF (key_obj);
+  return key;
+}
+
+octave_value
+pyobject_wrap_object (PyObject *obj)
+{
+  uint64_t key = py_objstore_put (obj);
+  octave_value_list out = feval ("pyobject", ovl (0, octave_uint64 (key)), 1);
+  return out(0);
+}
+
+PyObject *
+pyobject_unwrap_object (const octave_value& value)
+{
+  if (value.is_object () && value.class_name () == "pyobject")
+    {
+      octave_value_list out = feval ("id", ovl (value), 1);
+      uint64_t key = out(0).uint64_scalar_value ();
+      return py_objstore_get (key);
+    }
+
+  return 0;
+}
+
 bool
 is_py_kwargs_argument (PyObject *obj)
 {
--- a/oct-py-util.h	Thu Aug 25 09:29:51 2016 -0700
+++ b/oct-py-util.h	Thu Aug 25 09:51:58 2016 -0700
@@ -92,6 +92,21 @@
 std::string
 py_object_class_name (PyObject *obj);
 
+void
+py_objstore_del (uint64_t key);
+
+PyObject *
+py_objstore_get (uint64_t key);
+
+uint64_t
+py_objstore_put (PyObject *obj);
+
+octave_value
+pyobject_wrap_object (PyObject *obj);
+
+PyObject *
+pyobject_unwrap_object (const octave_value& value);
+
 bool
 is_py_kwargs_argument (PyObject *obj);
 
--- a/octave_to_python.cc	Thu Aug 25 09:29:51 2016 -0700
+++ b/octave_to_python.cc	Thu Aug 25 09:51:58 2016 -0700
@@ -38,6 +38,7 @@
 #include "exceptions.h"
 #include "octave_to_python.h"
 #include "oct-py-types.h"
+#include "oct-py-util.h"
 
 using namespace boost::python;
 
@@ -194,9 +195,8 @@
       }
     else if (octvalue.is_object () && octvalue.class_name () == "pyobject")
       {
-        octave_value_list tmp = feval ("getid", ovl (octvalue), 1);
-        std::string hexid = tmp(0).string_value ();
-        py_object = boost::python::import ("__main__").attr ("_in_octave")[hexid];
+        PyObject *obj = pyobject_unwrap_object (octvalue);
+        py_object = object (handle<PyObject> (obj));
       }
     else
       throw value_convert_exception (
--- a/python_to_octave.cc	Thu Aug 25 09:29:51 2016 -0700
+++ b/python_to_octave.cc	Thu Aug 25 09:51:58 2016 -0700
@@ -310,27 +310,6 @@
       }
   }
 
-  static void
-  pyobj_to_oct_pyobject (octave_value& oct_value,
-                         const boost::python::object& py_object)
-  {
-    object main_module = import ("__main__");
-    object builtins_module;
-    pytave::get_builtins_module (builtins_module);
-    object hex_function = builtins_module.attr ("hex");
-    object id_function = builtins_module.attr ("id");
-    object idtmp = hex_function (id_function (py_object));
-    std::string id = extract<std::string> (idtmp);
-
-    // Ensure dict for Octave communication exists
-    if (! PyObject_HasAttrString (main_module.ptr (), "_in_octave"))
-      main_module.attr ("_in_octave") = boost::python::dict ();
-
-    main_module.attr ("_in_octave")[id] = py_object;
-    // Create @pyobject
-    oct_value = feval ("pyobject", ovl (0, id), 1)(0);
-  }
-
   void pyobj_to_octvalue (octave_value& oct_value,
                           const boost::python::object& py_object)
   {
@@ -351,7 +330,7 @@
     else if (PyBytes_Check (py_object.ptr ()) || PyUnicode_Check (py_object.ptr ()))
       oct_value = extract_py_str (py_object.ptr ());
     else
-      pyobj_to_oct_pyobject (oct_value, py_object);
+      oct_value = pyobject_wrap_object (py_object.ptr ());
   }
 
   void pytuple_to_octlist (octave_value_list& octave_list,