Merge pull request #8934 from lewisjb:python-classes
authorLewis B <lewisjb@users.noreply.github.com>
Tue, 5 Sep 2017 05:38:17 +0000 (15:38 +1000)
committerAlexander Alekhin <alexander.a.alekhin@gmail.com>
Tue, 5 Sep 2017 05:38:17 +0000 (05:38 +0000)
* Refactor Python Classes

modules/python/src2/cv2.cpp
modules/python/src2/gen2.py

index be8965c..8949f35 100644 (file)
@@ -1609,14 +1609,20 @@ void initcv2()
     return;
 #endif
 
+
 #if PY_MAJOR_VERSION >= 3
-  Py_INCREF(&cv2_UMatWrapperType);
+#define PUBLISH_OBJECT(name, type) Py_INCREF(&type);\
+  PyModule_AddObject(m, name, (PyObject *)&type);
 #else
-  // Unrolled Py_INCREF(&cv2_UMatWrapperType) without (PyObject*) cast
-  // due to "warning: dereferencing type-punned pointer will break strict-aliasing rules"
-  _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA (&cv2_UMatWrapperType)->ob_refcnt++;
+// Unrolled Py_INCREF(&type) without (PyObject*) cast
+// due to "warning: dereferencing type-punned pointer will break strict-aliasing rules"
+#define PUBLISH_OBJECT(name, type) _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA (&type)->ob_refcnt++;\
+  PyModule_AddObject(m, name, (PyObject *)&type);
 #endif
-  PyModule_AddObject(m, "UMat", (PyObject *)&cv2_UMatWrapperType);
+
+  PUBLISH_OBJECT("UMat", cv2_UMatWrapperType);
+
+#include "pyopencv_generated_type_publish.h"
 
 #define PUBLISH(I) PyDict_SetItemString(d, #I, PyInt_FromLong(I))
 //#define PUBLISHU(I) PyDict_SetItemString(d, #I, PyLong_FromUnsignedLong(I))
index da7a923..5a754ed 100755 (executable)
@@ -25,14 +25,12 @@ gen_template_check_self_algo = Template("""    $cname* _self_ = NULL;
         return failmsgp("Incorrect type of self (must be '${name}' or its derivative)");
 """)
 
-gen_template_call_constructor_prelude = Template("""self = PyObject_NEW(pyopencv_${name}_t, &pyopencv_${name}_Type);
-        new (&(self->v)) Ptr<$cname>(); // init Ptr with placement new
+gen_template_call_constructor_prelude = Template("""new (&(self->v)) Ptr<$cname>(); // init Ptr with placement new
         if(self) """)
 
 gen_template_call_constructor = Template("""self->v.reset(new ${cname}${args})""")
 
-gen_template_simple_call_constructor_prelude = Template("""self = PyObject_NEW(pyopencv_${name}_t, &pyopencv_${name}_Type);
-        if(self) """)
+gen_template_simple_call_constructor_prelude = Template("""if(self) """)
 
 gen_template_simple_call_constructor = Template("""new (&(self->v)) ${cname}${args}""")
 
@@ -189,6 +187,7 @@ static void pyopencv_${name}_specials(void)
     pyopencv_${name}_Type.tp_dealloc = pyopencv_${name}_dealloc;
     pyopencv_${name}_Type.tp_repr = pyopencv_${name}_repr;
     pyopencv_${name}_Type.tp_getset = pyopencv_${name}_getseters;
+    pyopencv_${name}_Type.tp_init = (initproc)${constructor};
     pyopencv_${name}_Type.tp_methods = pyopencv_${name}_methods;${extra_specials}
 }
 """)
@@ -280,6 +279,7 @@ class ClassInfo(object):
         self.props = []
         self.consts = {}
         self.base = None
+        self.constructor = None
         customname = False
 
         if decl:
@@ -353,6 +353,9 @@ class ClassInfo(object):
         sorted_methods = list(self.methods.items())
         sorted_methods.sort()
 
+        if self.constructor is not None:
+            methods_code.write(self.constructor.gen_code(all_classes))
+
         for mname, m in sorted_methods:
             methods_code.write(m.gen_code(all_classes))
             methods_inits.write(m.get_tab_entry())
@@ -361,10 +364,14 @@ class ClassInfo(object):
         if self.base and self.base in all_classes:
             baseptr = "&pyopencv_" + all_classes[self.base].name + "_Type"
 
+        constructor_name = "0"
+        if self.constructor is not None:
+            constructor_name = self.constructor.get_wrapper_name()
+
         code = gen_template_type_impl.substitute(name=self.name, wname=self.wname, cname=self.cname,
             getset_code=getset_code.getvalue(), getset_inits=getset_inits.getvalue(),
             methods_code=methods_code.getvalue(), methods_inits=methods_inits.getvalue(),
-            baseptr=baseptr, extra_specials="")
+            baseptr=baseptr, constructor=constructor_name, extra_specials="")
 
         return code
 
@@ -521,12 +528,13 @@ class FuncVariant(object):
 
 
 class FuncInfo(object):
-    def __init__(self, classname, name, cname, isconstructor, namespace):
+    def __init__(self, classname, name, cname, isconstructor, namespace, isclassmethod):
         self.classname = classname
         self.name = name
         self.cname = cname
         self.isconstructor = isconstructor
         self.namespace = namespace
+        self.isclassmethod = isclassmethod
         self.variants = []
 
     def add_variant(self, decl):
@@ -540,11 +548,19 @@ class FuncInfo(object):
                 name = "getelem"
         else:
             classname = ""
+
+        if self.isclassmethod:
+            name += "_cls"
+
         return "pyopencv_" + self.namespace.replace('.','_') + '_' + classname + name
 
-    def get_wrapper_prototype(self):
+    def get_wrapper_prototype(self, all_classes):
         full_fname = self.get_wrapper_name()
-        if self.classname and not self.isconstructor:
+        if self.isconstructor:
+            return "static int {fn_name}(pyopencv_{type_name}_t* self, PyObject* args, PyObject* kw)".format(
+                    fn_name=full_fname, type_name=all_classes[self.classname].name)
+
+        if self.classname:
             self_arg = "self"
         else:
             self_arg = ""
@@ -591,12 +607,16 @@ class FuncInfo(object):
         # Convert unicode chars to xml representation, but keep as string instead of bytes
         full_docstring = full_docstring.encode('ascii', errors='xmlcharrefreplace').decode()
 
-        return Template('    {"$py_funcname", (PyCFunction)$wrap_funcname, METH_VARARGS | METH_KEYWORDS, "$py_docstring"},\n'
+        flags = ["METH_VARARGS", "METH_KEYWORDS"]
+        if self.isclassmethod:
+            flags.append("METH_CLASS")
+
+        return Template('    {"$py_funcname", (PyCFunction)$wrap_funcname, $flags, "$py_docstring"},\n'
                         ).substitute(py_funcname = self.variants[0].wname, wrap_funcname=self.get_wrapper_name(),
-                                     py_docstring = full_docstring)
+                                     flags = " | ".join(flags), py_docstring = full_docstring)
 
     def gen_code(self, all_classes):
-        proto = self.get_wrapper_prototype()
+        proto = self.get_wrapper_prototype(all_classes)
         code = "%s\n{\n" % (proto,)
         code += "    using namespace %s;\n\n" % self.namespace.replace('.', '::')
 
@@ -609,7 +629,9 @@ class FuncInfo(object):
             selfinfo = all_classes[self.classname]
             if not self.isconstructor:
                 amp = "&" if selfinfo.issimple else ""
-                if selfinfo.isalgorithm:
+                if self.isclassmethod:
+                    pass
+                elif selfinfo.isalgorithm:
                     code += gen_template_check_self_algo.substitute(name=selfinfo.name, cname=selfinfo.cname, amp=amp)
                 else:
                     get = "" if selfinfo.issimple else ".get()"
@@ -692,7 +714,6 @@ class FuncInfo(object):
             code_args += ")"
 
             if self.isconstructor:
-                code_decl += "    pyopencv_%s_t* self = 0;\n" % selfinfo.name
                 if selfinfo.issimple:
                     templ_prelude = gen_template_simple_call_constructor_prelude
                     templ = gen_template_simple_call_constructor
@@ -708,7 +729,7 @@ class FuncInfo(object):
                 if v.rettype:
                     code_decl += "    " + v.rettype + " retval;\n"
                     code_fcall += "retval = "
-                if ismethod:
+                if ismethod and not self.isclassmethod:
                     code_fcall += "_self_->" + self.cname
                 else:
                     code_fcall += self.cname
@@ -750,7 +771,7 @@ class FuncInfo(object):
                 code_ret = "Py_RETURN_NONE"
             elif len(v.py_outlist) == 1:
                 if self.isconstructor:
-                    code_ret = "return (PyObject*)self"
+                    code_ret = "return 0"
                 else:
                     aname, argno = v.py_outlist[0]
                     code_ret = "return pyopencv_from(%s)" % (aname,)
@@ -773,7 +794,11 @@ class FuncInfo(object):
         else:
             # try to execute each signature
             code += "    PyErr_Clear();\n\n".join(["    {\n" + v + "    }\n" for v in all_code_variants])
-        code += "\n    return NULL;\n}\n\n"
+
+        def_ret = "NULL"
+        if self.isconstructor:
+            def_ret = "-1"
+        code += "\n    return %s;\n}\n\n" % def_ret
         return code
 
 
@@ -796,6 +821,7 @@ class PythonWrapperGenerator(object):
         self.code_funcs = StringIO()
         self.code_type_reg = StringIO()
         self.code_ns_reg = StringIO()
+        self.code_type_publish = StringIO()
         self.class_idx = 0
 
     def add_class(self, stype, name, decl):
@@ -848,20 +874,32 @@ class PythonWrapperGenerator(object):
                 isclassmethod = True
             elif m.startswith("="):
                 name = m[1:]
-        if isclassmethod:
-            name = "_".join(classes+[name])
-            classname = ''
-        elif isconstructor:
+        if isconstructor:
             name = "_".join(classes[:-1]+[name])
 
-        if classname and not isconstructor:
-            cname = barename
+        if isclassmethod:
+            # Add it as a method to the class
             func_map = self.classes[classname].methods
-        else:
+            func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace, isclassmethod))
+            func.add_variant(decl)
+
+            # Add it as global function
+            g_name = "_".join(classes+[name])
             func_map = self.namespaces.setdefault(namespace, Namespace()).funcs
+            func = func_map.setdefault(g_name, FuncInfo("", g_name, cname, isconstructor, namespace, False))
+            func.add_variant(decl)
+        else:
+            if classname and not isconstructor:
+                cname = barename
+                func_map = self.classes[classname].methods
+            else:
+                func_map = self.namespaces.setdefault(namespace, Namespace()).funcs
+
+            func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace, isclassmethod))
+            func.add_variant(decl)
 
-        func = func_map.setdefault(name, FuncInfo(classname, name, cname, isconstructor, namespace))
-        func.add_variant(decl)
+        if classname and isconstructor:
+            self.classes[classname].constructor = func
 
 
     def gen_namespace(self, ns_name):
@@ -870,6 +908,8 @@ class PythonWrapperGenerator(object):
 
         self.code_ns_reg.write('static PyMethodDef methods_%s[] = {\n'%wname)
         for name, func in sorted(ns.funcs.items()):
+            if func.isconstructor:
+                continue
             self.code_ns_reg.write(func.get_tab_entry())
         self.code_ns_reg.write('    {NULL, NULL}\n};\n\n')
 
@@ -960,12 +1000,15 @@ class PythonWrapperGenerator(object):
             self.code_types.write(code)
             if not classinfo.ismap:
                 self.code_type_reg.write("MKTYPE2(%s);\n" % (classinfo.name,) )
+                self.code_type_publish.write("PUBLISH_OBJECT(\"{name}\", pyopencv_{name}_Type);\n".format(name=classinfo.name))
 
         # step 3: generate the code for all the global functions
         for ns_name, ns in sorted(self.namespaces.items()):
             if ns_name.split('.')[0] != 'cv':
                 continue
             for name, func in sorted(ns.funcs.items()):
+                if func.isconstructor:
+                    continue
                 code = func.gen_code(self.classes)
                 self.code_funcs.write(code)
             self.gen_namespace(ns_name)
@@ -983,6 +1026,7 @@ class PythonWrapperGenerator(object):
         self.save(output_path, "pyopencv_generated_types.h", self.code_types)
         self.save(output_path, "pyopencv_generated_type_reg.h", self.code_type_reg)
         self.save(output_path, "pyopencv_generated_ns_reg.h", self.code_ns_reg)
+        self.save(output_path, "pyopencv_generated_type_publish.h", self.code_type_publish)
 
 if __name__ == "__main__":
     srcfiles = hdr_parser.opencv_hdr_list