On Wed, Feb 20, 2019 at 9:09 PM Ben Rudiak-Gould <benrud...@gmail.com> wrote: > That problem, of an inadvertently leaked implementation detail > masquerading as a proper alternate return value, used to be a huge > issue with StopIteration, causing bugs that were very hard to track > down, until PEP 479 fixed it by translating StopIteration into > RuntimeError when it crossed an abstraction boundary.
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. > I think converting exceptions to RuntimeErrors (keeping all original > information, but bypassing catch blocks intended for specific > exceptions) is the best option. (Well, second best after ML/Haskell.) > But to make it work you probably need to support some sort of > exception specification. The trouble with that is that it makes refactoring very hard. You can't create a helper function without knowing exactly what it might be raising. > I'm rambling. I suppose my points are: > > * Error handing is inherently hard, and all approaches do badly > because it's hard and programmers hate it. Well, yeah, no kidding. :) > ... I was bitten several times by > that StopIteration problem. > There's often a completely different approach that doesn't leak StopIteration. One of my workmates started seeing RuntimeErrors, and figured he'd need a try/except in this code: def _generator(self, left, right): while True: yield self.operator(next(left), next(right)) But instead of messing with try/except, it's much simpler to use something else - in this case, zip. Generally, it's better to keep things simple rather than to complicate them with new boundaries. Unless there's a good reason to prevent leakage, I would just let exception handling do whatever it wants to. But if you want to specifically say "this function will not raise anything other than these specific exceptions", that can be done with a decorator: def raises(*exc): """Declare and enforce what a function will raise The decorated function will not raise any Exception other than the specified ones, or RuntimeError. """ def deco(func): @functools.wraps(func) def convert_exc(*a, **kw): try: return func(*a, **kw) except exc: raise except Exception as e: raise RuntimeError from e convert_exc.may_raise = exc # in case it's wanted return convert_exc return deco This way, undecorated functions behave as normal, so refactoring isn't impacted. But if you want the help of a declared list of exceptions, you can have it. @raises(ValueError, IndexError) def frob(x): ... ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/