Repository: cython/cython

Issue:
Python array support
Adapted from patch at http://trac.cython.org/cython_trac/ticket/314

Changes: reverted Py_MEM_Realloc to Py_Mem_Resize, due to warnings
removed inline keyword (not portable, and Py_LOCAL_INLINE causes warnings).
moved a C declaration to top of function to avoid "mixed declaration"

Python array objects are useful because there is no dependency like with numpy, they make sense for small 1D-arrays. With this patch you can have C-level access to Python arrays, while still having the convenience of Python taking care of garbage collection. Another advantage is that they can be pickled, so you can pass them around to other processes with multiprocessing.

I didn't really succeed in getting the tests to compile, unfortunately. But if I place these files in the directory of a project it works fine. Another thing is that I'd like to get rid of all warnings; there's still a gcc warning about **getbuffer** being declared but not used.



Discussion:
BTW I didn't succeed in removing ob_type. I tried both casting the result of type() to PyTypeObject (cannot cast temporary object to non-numeric type), and casting to arrayobject (not a type indentifier). But maybe I'm missing something obvious.

Thanks, that's better. I'll give it another couple of days to collect other opinions and then merge it.

Any news on this? Let me know if there's anything I can do to help inclusion of this patch.


Resolving patch:
diff --git a/Cython/Compiler/Code.py b/Cython/Compiler/Code.py
--- a/Cython/Compiler/Code.py
+++ b/Cython/Compiler/Code.py
@@ -484,7 +484,7 @@ def __init__(self, owner, names_taken=set()):
         self.in_try_finally = 0
         self.exc_vars = None
 
-        self.temps_allocated = [] # of (name, type, manage_ref)
+        self.temps_allocated = [] # of (name, type, manage_ref, static)
         self.temps_free = {} # (type, manage_ref) -> list of free vars with same type/managed status
         self.temps_used_type = {} # name -> (type, manage_ref)
         self.temp_counter = 0
@@ -563,7 +563,7 @@ def label_used(self, lbl):
 
     # temp handling
 
-    def allocate_temp(self, type, manage_ref):
+    def allocate_temp(self, type, manage_ref, static=False):
         """
         Allocates a temporary (which may create a new one or get a previously
         allocated and released one of the same type). Type is simply registered
@@ -578,6 +578,10 @@ def allocate_temp(self, type, manage_ref):
         still has to be passed. It is recommended to pass False by convention
         if it is known that type will never be a Python object.
 
+        static=True marks the temporary declaration with "static".
+        This is only used when allocating backing store for a module-level
+        C array literals.
+
         A C string referring to the variable is returned.
         """
         if type.is_const:
@@ -595,7 +599,7 @@ def allocate_temp(self, type, manage_ref):
                 self.temp_counter += 1
                 result = "%s%d" % (Naming.codewriter_temp_prefix, self.temp_counter)
                 if not result in self.names_taken: break
-            self.temps_allocated.append((result, type, manage_ref))
+            self.temps_allocated.append((result, type, manage_ref, static))
         self.temps_used_type[result] = (type, manage_ref)
         if DebugFlags.debug_temp_code_comments:
             self.owner.putln("/* %s allocated */" % result)
@@ -626,7 +630,7 @@ def temps_in_use(self):
         that are currently in use.
         """
         used = []
-        for name, type, manage_ref in self.temps_allocated:
+        for name, type, manage_ref, static in self.temps_allocated:
             freelist = self.temps_free.get((type, manage_ref))
             if freelist is None or name not in freelist:
                 used.append((name, type, manage_ref and type.is_pyobject))
@@ -645,7 +649,7 @@ def all_managed_temps(self):
         """Return a list of (cname, type) tuples of refcount-managed Python objects.
         """
         return [(cname, type)
-                    for cname, type, manage_ref in self.temps_allocated
+                    for cname, type, manage_ref, static in self.temps_allocated
                         if manage_ref]
 
     def all_free_managed_temps(self):
@@ -1604,7 +1608,7 @@ def put_var_declaration(self, entry, storage_class="",
         self.putln(";")
 
     def put_temp_declarations(self, func_context):
-        for name, type, manage_ref in func_context.temps_allocated:
+        for name, type, manage_ref, static in func_context.temps_allocated:
             decl = type.declaration_code(name)
             if type.is_pyobject:
                 self.putln("%s = NULL;" % decl)
@@ -1612,7 +1616,7 @@ def put_temp_declarations(self, func_context):
                 import MemoryView
                 self.putln("%s = %s;" % (decl, MemoryView.memslice_entry_init))
             else:
-                self.putln("%s;" % decl)
+                self.putln("%s%s;" % (static and "static " or "", decl))
 
     def put_h_guard(self, guard):
         self.putln("#ifndef %s" % guard)
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -1815,7 +1815,7 @@ def generate_assignment_code(self, rhs, code):
             return # There was an error earlier
 
         if (self.entry.type.is_ptr and isinstance(rhs, ListNode)
-            and not self.lhs_of_first_assignment):
+            and not self.lhs_of_first_assignment and not rhs.in_module_scope):
             error(self.pos, "Literal list must be assigned to pointer at time of declaration")
 
         # is_pyglobal seems to be True for module level-globals only.
@@ -5914,6 +5914,7 @@ class ListNode(SequenceNode):
 
     obj_conversion_errors = []
     type = list_type
+    in_module_scope = False
 
     gil_message = "Constructing Python list"
 
@@ -5934,6 +5935,8 @@ def analyse_types(self, env):
         node = SequenceNode.analyse_types(self, env)
         node.obj_conversion_errors = held_errors()
         release_errors(ignore=True)
+        if env.is_module_scope:
+            self.in_module_scope = True
         return node
 
     def coerce_to(self, dst_type, env):
@@ -5975,6 +5978,13 @@ def as_tuple(self):
             t.constant_result = tuple(self.constant_result)
         return t
 
+    def allocate_temp_result(self, code):
+        if self.type.is_array and self.in_module_scope:
+            self.temp_code = code.funcstate.allocate_temp(
+                self.type, manage_ref=False, static=True)
+        else:
+            SequenceNode.allocate_temp_result(self, code)
+
     def release_temp_result(self, env):
         if self.type.is_array:
             # To be valid C++, we must allocate the memory on the stack


Test patch:
diff --git a/tests/run/literal_lists.pyx b/tests/run/literal_lists.pyx
--- a/tests/run/literal_lists.pyx
+++ b/tests/run/literal_lists.pyx
@@ -55,6 +55,26 @@ def test_struct(int x, y):
     print_struct(aa[0])
     print_struct([1, 2, <double**>1])
 
+cdef int m_int = -1
+cdef int* m_iarray = [4, m_int]
+cdef int** m_piarray = [m_iarray, &m_int]
+cdef char** m_carray = [b"a", b"bc"]
+cdef MyStruct* m_structarray = [[m_int,0,NULL], [1,m_int+1,NULL]]
+
+def test_module_level():
+    """
+    >>> test_module_level()
+    4 -1
+    4 -1
+    True True
+    1 0 True
+    """
+    print m_iarray[0], m_iarray[1]
+    print m_piarray[0][0], m_piarray[1][0]
+    print m_carray[0] == b"a", m_carray[1] == b"bc"
+    print_struct(m_structarray[1])
+
+
 # Make sure it's still naturally an object.
 
 [0,1,2,3].append(4)