Repository: chainer/chainer

Issue:
Make it easier to debug code which mixes up Variable and ndarray
I often encounter a bug caused by mixing up Variable and ndarray. There is one caveat on it; since both have `.data`, an error often occurs far later than the point that we should fix. I want to make it easier to debug such a case.

One idea is to add an alias of `Variable.data`, like `Variable.array`, so that we can avoid such a situation by letting us use the new alias instead of `.data`. If we always use this alias, an error immediately occurs once we try to access the attribute of an ndarray. It is still later than the point we start mixing up Variable and ndarray, though.


Discussion:
I think we should consider writing more `assert` statements.
In my test, `assert isinstance(...)` is as costly as a single method call (~100ns).
(I'm not sure if we can say it's cheap enough)

`assert` statements can also work as additional documentation.
It's much easier to read and debug code with `assert` statements.
(which I think is good for encouraging more external contributions)


👍  for adding `.array`. Writing assert is a good practice, but it should not be forced for users to write assert.

Resolving patch:
diff --git a/chainer/variable.py b/chainer/variable.py
--- a/chainer/variable.py
+++ b/chainer/variable.py
@@ -413,12 +413,6 @@ class Variable(object):
         requires_grad (bool): Boolean indicating whether ``grad`` will be set
             in backward calculation.
 
-    Attributes:
-        ~Variable.data: Data array of type either :class:`numpy.ndarray` or
-            :class:`cupy.ndarray`. If it is None, the variable is left in an
-            uninitialized state.
-        ~Variable.grad_var (Variable): Gradient variable.
-
     """  # NOQA
 
     def __init__(self, data=None, **kwargs):
@@ -574,8 +568,32 @@ def creator_node(self):
     def creator_node(self, func):
         self._node.creator_node = func
 
+    @property
+    def array(self):
+        """The underlying data array.
+
+        It is either :class:`numpy.ndarray` or :class:`cupy.ndarray` object,
+        or ``None`` if the variable in in an uninitialized state.
+
+        """
+        return self._data[0]
+
+    @array.setter
+    def array(self, d):
+        self._data[0] = d
+        self._node._set_data_type(d)
+
     @property
     def data(self):
+        """The underlying data array (equivalent to :attr:`array`).
+
+        Note that using this attribute directly is discouraged; use
+        :attr:`array` instead. Using :attr:`array`, you can find an error
+        earlier when your code mixes up Variable and ndarray because
+        ndarray does not have an attribute ``.array`` while it has
+        ``.data``.
+
+        """
         return self._data[0]
 
     @data.setter
@@ -601,6 +619,7 @@ def grad(self, g):
 
     @property
     def grad_var(self):
+        """Gradient variable."""
         return self._grad_var
 
     @grad_var.setter


Test patch:
diff --git a/tests/chainer_tests/test_variable.py b/tests/chainer_tests/test_variable.py
--- a/tests/chainer_tests/test_variable.py
+++ b/tests/chainer_tests/test_variable.py
@@ -60,10 +60,12 @@ def setUp(self):
         self.c = np.arange(self.size).reshape(self.c_shape).astype(np.float32)
 
     def check_attributes(self, gpu):
-        x = self.x
+        a = self.x
         if gpu:
-            x = cuda.to_gpu(x)
-        x = chainer.Variable(x)
+            a = cuda.to_gpu(a)
+        x = chainer.Variable(a)
+        self.assertIs(x.data, a)
+        self.assertIs(x.array, a)
         self.assertEqual(x.shape, self.x.shape)
         self.assertEqual(x.ndim, self.x.ndim)
         self.assertEqual(x.size, self.x.size)