Repository: chainer/chainer

Issue:
SplitAxis should return a tuple with a single element
`Function.__call__` returns a `Variable` when its forward function returns only one return value. When the number of return values depends on input values, such as `split_axis`, a user need to check the type of the return value of `Function`. A user needs to write a complicated source code.

I have two design choice:
- Only `SplitAxis` always returns a tuple of `Variable`s
- Add keyword argument `force_tuple` to `Function.__call__`



Discussion:

Resolving patch:
diff --git a/chainer/functions/array/split_axis.py b/chainer/functions/array/split_axis.py
--- a/chainer/functions/array/split_axis.py
+++ b/chainer/functions/array/split_axis.py
@@ -2,6 +2,7 @@
 
 import six
 
+import chainer
 from chainer import cuda
 from chainer import function
 from chainer.utils import type_check
@@ -58,7 +59,7 @@ def backward(self, x, gys):
             return xp.concatenate(gys, axis=self.axis),
 
 
-def split_axis(x, indices_or_sections, axis):
+def split_axis(x, indices_or_sections, axis, force_tuple=False):
     """Splits given variables along an axis.
 
     Args:
@@ -68,16 +69,23 @@ def split_axis(x, indices_or_sections, axis):
             If it is a 1-D array of sorted integers, it
             indicates the positions where the array is split.
         axis (int): Axis that the input array is split along.
+        force_tuple (bool): If ``True``, this method returns a tuple even when
+            the number of outputs is one.
 
     Returns:
         tuple or Variable: Tuple of :class:`~chainer.Variable` objects
              if the number of outputs is more than 1 or
              :class:`~chainer.Variable` otherwise.
+             When ``force_tuple`` is ``True``, returned value is always a tuple
+             regardless of the number of outputs.
 
     .. note::
         This function raises :class:`ValueError` if at least
         one of the outputs is split to zero-size
-        (i.e. `axis`-th value of its shape is zero).
+        (i.e. ``axis``-th value of its shape is zero).
 
     """
-    return SplitAxis(indices_or_sections, axis)(x)
+    res = SplitAxis(indices_or_sections, axis)(x)
+    if force_tuple and isinstance(res, chainer.Variable):
+        res = (res,)
+    return res


Test patch:
diff --git a/tests/chainer_tests/functions_tests/array_tests/test_split_axis.py b/tests/chainer_tests/functions_tests/array_tests/test_split_axis.py
--- a/tests/chainer_tests/functions_tests/array_tests/test_split_axis.py
+++ b/tests/chainer_tests/functions_tests/array_tests/test_split_axis.py
@@ -115,4 +115,36 @@ def test_backward_gpu(self):
             cuda.to_gpu(self.x), self.ys_section, axis=self.axis)
 
 
+class TestSplitAxisForceArray(unittest.TestCase):
+
+    def setUp(self):
+        self.x = numpy.arange(42, dtype=numpy.float32).reshape(2, 7, 3)
+        self.axis = 1
+
+    def check_forward_force_tuple(self, x_data, axis):
+        x = chainer.Variable(x_data)
+        ys = functions.split_axis(x, 1, axis, force_tuple=True)
+        self.assertIsInstance(ys, tuple)
+        self.assertEqual(len(ys), 1)
+
+    def test_forward_force_tuple_cpu(self):
+        self.check_forward_force_tuple(self.x, self.axis)
+
+    @attr.gpu
+    def test_forward_force_tuple_gpu(self):
+        self.check_forward_force_tuple(cuda.to_gpu(self.x), axis=self.axis)
+
+    def check_forward_single(self, x_data, axis):
+        x = chainer.Variable(x_data)
+        ys = functions.split_axis(x, 1, axis)
+        self.assertIsInstance(ys, chainer.Variable)
+
+    def test_forward_single_cpu(self):
+        self.check_forward_single(self.x, self.axis)
+
+    @attr.gpu
+    def test_forward_single_gpu(self):
+        self.check_forward_single(cuda.to_gpu(self.x), axis=self.axis)
+
+
 testing.run_module(__name__, __file__)