[Cython] Assign from array to scalar

Emmanuel Gil Peyrot linkmauve at linkmauve.fr
Fri Sep 13 00:10:21 CEST 2013


Hi,

A few weeks ago I started an implementation of that simple usecase:

 cdef long a[2], x, y
 # set a[0] and a[1] to some useful values
 x, y = a

It does works, but I’m not used to the interactions between the
different parts of the code, I mainly hacked away where I could.  I’m
especially dissatisfied with the code duplication I came with (to check
the correct types and size) and the location of the checks.

I added testcases for both the array assignation and the slices of that
same assignation I plan to implement, which seems to integrate
badly with my current implementation, making the code even more
unreadable.

I tried to find a commit implementing a similar feature so I could get
some inspiration but couldn’t find one.  If someone has a hint about
that I’d be glad. :)

Btw, on a Cython.Compiler.ExprNodes.SliceIndexNode object, why does
stop_code() give an int while start_code() gives a string?  It’d be
much better to return an int in both cases, so one could interpolate
without conversion from both python or the generated C.

Preliminary patch joined.

-- 
Emmanuel Gil Peyrot
XMPP: <linkmauve at linkmauve.fr>
OpenPGP: 24B1D609
-------------- next part --------------
diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py
index 2511eaf..bf04ff1 100755
--- a/Cython/Compiler/ExprNodes.py
+++ b/Cython/Compiler/ExprNodes.py
@@ -5615,6 +5615,11 @@ class SequenceNode(ExprNode):
     def generate_assignment_code(self, rhs, code):
         if self.starred_assignment:
             self.generate_starred_assignment_code(rhs, code)
+        elif (isinstance(self, TupleNode) and
+              isinstance(rhs, NameNode) and
+              isinstance(rhs.entry.type, PyrexTypes.CArrayType)):
+            self.generate_parallel_assignment_code2(rhs, code)
+            return
         else:
             self.generate_parallel_assignment_code(rhs, code)
 
@@ -5627,6 +5632,22 @@ class SequenceNode(ExprNode):
             PyrexTypes.CFuncTypeArg("it", PyrexTypes.py_object_type, None),
             ]))
 
+    def generate_parallel_assignment_code2(self, rhs, code):
+        rhs_type = rhs.entry.type
+        base_type = rhs_type.base_type
+        size = rhs_type.size
+        lhs_args = self.args
+        assert len(lhs_args) == size
+        for arg in lhs_args:
+            if arg.entry.type is not base_type:
+                raise ValueError("Wrong type for parallel assignment of '%s' array: %s" %
+                                 (base_type, arg.entry.type))
+        for i, arg in enumerate(lhs_args):
+            code.putln("%s = %s[%s];" % (
+                            arg.result(),
+                            rhs.result(),
+                            i))
+
     def generate_parallel_assignment_code(self, rhs, code):
         # Need to work around the fact that generate_evaluation_code
         # allocates the temps in a rather hacky way -- the assignment
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
index fe7adb3..7987cc6 100644
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -4509,6 +4509,20 @@ class SingleAssignmentNode(AssignmentNode):
         self.lhs = self.lhs.analyse_target_types(env)
         self.lhs.gil_assignment_check(env)
 
+        if (isinstance(self.lhs, ExprNodes.TupleNode) and
+            isinstance(self.rhs, ExprNodes.NameNode)):
+            rhs_type = self.rhs.entry.type
+            if isinstance(rhs_type, PyrexTypes.CArrayType):
+                base_type = rhs_type.base_type
+                size = rhs_type.size
+                lhs_args = self.lhs.args
+                assert len(lhs_args) == size
+                for arg in lhs_args:
+                    if arg.entry.type is not base_type:
+                        break
+                else:
+                    return self
+
         if self.lhs.memslice_broadcast or self.rhs.memslice_broadcast:
             self.lhs.memslice_broadcast = True
             self.rhs.memslice_broadcast = True
diff --git a/tests/run/arrayassign.pyx b/tests/run/arrayassign.pyx
index 29c353e..f76ad2e 100644
--- a/tests/run/arrayassign.pyx
+++ b/tests/run/arrayassign.pyx
@@ -128,6 +128,59 @@ def test_ptr_literal_list_slice_end():
     a[:5] = [1,2,3,4,5]
     return (a[0], a[1], a[2], a[3], a[4])
 
+
+# The opposite, assignation from an array to multiple scalars.
+
+def test_array_to_scalars():
+    """
+    >>> test_array_to_scalars()
+    (1, 2, 3, 4, 5)
+    """
+    cdef int l[5], a, b, c, d, e
+    l[:] = [1,2,3,4,5]
+    a, b, c, d, e = l
+    return (a, b, c, d, e)
+
+def test_slice_all_to_scalars():
+    """
+    >>> test_slice_all_to_scalars()
+    (1, 2, 3, 4, 5)
+    """
+    cdef int l[5], a, b, c, d, e
+    l[:] = [1,2,3,4,5]
+    a, b, c, d, e = l[:]
+    return (a, b, c, d, e)
+
+def test_slice_start_to_scalars():
+    """
+    >>> test_slice_start_to_scalars()
+    (1, 2, 3, 4, 5)
+    """
+    cdef int l[7], a, b, c, d, e
+    l[:] = [6,7,1,2,3,4,5]
+    a, b, c, d, e = l[2:]
+    return (a, b, c, d, e)
+
+def test_slice_end_to_scalars():
+    """
+    >>> test_slice_end_to_scalars()
+    (1, 2, 3, 4, 5)
+    """
+    cdef int l[7], a, b, c, d, e
+    l[:] = [1,2,3,4,5,6,7]
+    a, b, c, d, e = l[:5]
+    return (a, b, c, d, e)
+
+def test_slice_start_end_to_scalars():
+    """
+    >>> test_slice_start_end_to_scalars()
+    (1, 2, 3, 4, 5)
+    """
+    cdef int l[9], a, b, c, d, e
+    l[:] = [6,7,1,2,3,4,5,8,9]
+    a, b, c, d, e = l[2:7]
+    return (a, b, c, d, e)
+
 # tuples aren't supported (yet)
 #
 #def test_literal_tuple():
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 198 bytes
Desc: not available
URL: <http://mail.python.org/pipermail/cython-devel/attachments/20130913/f3127c67/attachment.sig>


More information about the cython-devel mailing list