
On Thu, Feb 21, 2019 at 6:51 PM Ben Rudiak-Gould <benrudiak@gmail.com> wrote:
On Wed, Feb 20, 2019 at 2:43 AM Chris Angelico <rosuav@gmail.com> wrote:
That's because a generator function conceptually has three ways to provide data (yield, return, and raise), but mechanically, one of them is implemented over the other ("return" is "raise StopIteration with a value"). For other raised exceptions, this isn't a problem.
Other functions also conceptually have three ways of returning: ordinary return with a value, a documented special return like KeyError, and pass-through exceptions. If the pass-through exception is KeyError, it gets conflated with the documented exceptional return, but correct code should handle them differently. It doesn't matter whether the syntax for the documented special return is "return x" or "raise KeyError(x)".
Not sure what you mean here. If the documented special return is "return x", then it's a value that's being returned. That's completely different from raising some sort of exception. You have a reasonable point about raising KeyError. It's hard to catch bugs inside __getitem__ that result in the leakage of a KeyError. But it's not uncommon to implement getitem over some other object's getitem, which means that leakage is absolutely correct. I'm not sure what could really be done about that, but I'm also not sure it's necessary; those special methods tend to be very short. If you're writing a large and complex __getitem__, it might be worth adding some overhead to help with potential debugging, maybe wrapping most of your logic in a generator and having "yield" become "return", "return" become "raise KeyError", and "raise KeyError" become "raise RuntimeError". Easy enough with a decorator if you want it. Unnecessary overhead for most classes though. ChrisA