BUG: core: ensure binop execution uses ufuncs as fallback

These changes only affect objects defining __numpy_ufunc__. Other
objects use the __array_priority__ mechanism to decide whether
NotImplemented should be returned or not.

The binops previously returned NotImplemented even if other._r<op>__ is
ndarray.__r<op>__, rather than trying to evaluate the result via ufuncs.
This causes problems in binops of two objects that both subclass from
ndarray and define __numpy_ufunc__.

The solution added here makes the total logic as follows:

    def __binop__(self, other):
        if (hasattr(other, '__numpy_ufunc__') and
            not isinstance(other, self.__class__) and
            hasattr(other, '__rop__') and
            other.__class__.__rop__ is not self.__class__.__rop__):
            return NotImplemented
        return np.binop(self, other)