ENH: Allow fast comparisons of Index views, similar to 'is' checks.
Related: #4039 (where this started to become an issue), #4202, #4830, #4628, #4718, #4628.

Very simple: both indices get an `_id` property that can be compared with `is` and is reset every time an object is created, so it only stays set when set explicitly (like in view(), but not in copy()). Adds an `is_` method that wraps this for external use and makes it so you don't need `hasattr` checks, etc.

``` python
>>> ind = Index(range(10))
>>> ind.is_(ind)
True
>>> ind.is_(Index(range(10)))
False
>>> ind.is_(ind.view().view().view().view())
True
>>> ind.is_(ind.copy())
False
>>> ind.is_(ind.copy(deep=False))
False
>>> ind.is_(ind[:])
False
>>> ind.is_(ind.view(np.ndarray).view(Index))
False
>>> ind.is_(np.array(range(10)))
False
>>> ind.is_(ind.view().base) # quasi-implementation dependent
True
>>> ind2 = ind.view()
>>> ind2.name = 'bob'
>>> ind.is_(ind2)
True
>>> ind2.is_(ind)
True
>>> # doesn't matter if Indices are *actually* views of underlying data, e.g.
>>> ind.is_(Index(ind.values))
False
>>> arr = np.array(range(1, 11))
>>> ind1 = Index(arr, copy=False)
>>> ind2 = Index(arr, copy=False)
>>> ind1.is_(ind2)
False
```

MultiIndex behavior (same behavior for set labels and set levels)

``` python
>>> mi = MultiIndex.from_tuples(zip(range(10), range(10)))
>>> mi.is_(mi)
True
>>> mi.is_(mi.view())
True
>>> mi.is_(mi.view().view().view().view())
True
>>> mi2 = mi.view()
>>> mi2.names = ["A", "B"]
>>> mi2.is_(mi)
True
>>> mi.is_(mi2)
True
>>> mi.is_(mi.set_names(["C", "D"]))
True
>>> mi3 = mi2.set_levels([range(10), range(10)])
>>> mi3.is_(mi2)
False
>>> mi4 = mi3.view()
>>> mi4.set_levels([[1 for _ in range(10)], range(10)], inplace=True)
>>> mi4.is_(mi3)
False
>>> mi5 = mi.view()
>>> mi5.set_levels(mi5.levels, inplace=True)
>>> mi5.is_(mi)
False
```

Could make it easier to resolve #4013.
