Making the `cleanup_keys()` as part of `__del__` makes sense. I think that the `__del__` is not always run as soon as the object is discarded, only when Python gets around to the garbage collection. It did occur to me, that making a context manager out of `Context` [warning: name overload!] could help make sure that the cleanup happens as soon as it should.

As far as `DistArray.__del__`, this PR changes that so that `DistArray` just asks the `Context` to delete its key, and the `Context` then sends the `del` to the engines, so I think there is not a double delete.